NetworkedPrinterOutputDevice.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. from UM.Application import Application
  2. from UM.Logger import Logger
  3. from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
  4. from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply
  5. from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, pyqtSignal, QUrl
  6. from time import time
  7. from typing import Callable, Any, Optional
  8. from enum import IntEnum
  9. class AuthState(IntEnum):
  10. NotAuthenticated = 1
  11. AuthenticationRequested = 2
  12. Authenticated = 3
  13. AuthenticationDenied = 4
  14. AuthenticationReceived = 5
  15. class NetworkedPrinterOutputDevice(PrinterOutputDevice):
  16. authenticationStateChanged = pyqtSignal()
  17. def __init__(self, device_id, address: str, properties, parent = None):
  18. super().__init__(device_id = device_id, parent = parent)
  19. self._manager = None
  20. self._last_manager_create_time = None
  21. self._recreate_network_manager_time = 30
  22. self._timeout_time = 10 # After how many seconds of no response should a timeout occur?
  23. self._last_response_time = None
  24. self._last_request_time = None
  25. self._api_prefix = ""
  26. self._address = address
  27. self._properties = properties
  28. self._user_agent = "%s/%s " % (Application.getInstance().getApplicationName(), Application.getInstance().getVersion())
  29. self._onFinishedCallbacks = {}
  30. self._authentication_state = AuthState.NotAuthenticated
  31. self._cached_multiparts = {}
  32. def setAuthenticationState(self, authentication_state):
  33. if self._authentication_state != authentication_state:
  34. self._authentication_state = authentication_state
  35. self.authenticationStateChanged.emit()
  36. @pyqtProperty(int, notify=authenticationStateChanged)
  37. def authenticationState(self):
  38. return self._authentication_state
  39. def _update(self):
  40. if self._last_response_time:
  41. time_since_last_response = time() - self._last_response_time
  42. else:
  43. time_since_last_response = 0
  44. if self._last_request_time:
  45. time_since_last_request = time() - self._last_request_time
  46. else:
  47. time_since_last_request = float("inf") # An irrelevantly large number of seconds
  48. if time_since_last_response > self._timeout_time >= time_since_last_request:
  49. # Go (or stay) into timeout.
  50. self.setConnectionState(ConnectionState.closed)
  51. # We need to check if the manager needs to be re-created. If we don't, we get some issues when OSX goes to
  52. # sleep.
  53. if time_since_last_response > self._recreate_network_manager_time:
  54. if self._last_manager_create_time is None:
  55. self._createNetworkManager()
  56. if time() - self._last_manager_create_time > self._recreate_network_manager_time:
  57. self._createNetworkManager()
  58. return True
  59. def _createEmptyRequest(self, target):
  60. url = QUrl("http://" + self._address + self._api_prefix + target)
  61. request = QNetworkRequest(url)
  62. request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
  63. request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
  64. return request
  65. def _clearCachedMultiPart(self, reply):
  66. if id(reply) in self._cached_multiparts:
  67. del self._cached_multiparts[id(reply)]
  68. def _put(self, target: str, data: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]]):
  69. if self._manager is None:
  70. self._createNetworkManager()
  71. request = self._createEmptyRequest(target)
  72. self._last_request_time = time()
  73. reply = self._manager.put(request, data.encode())
  74. if onFinished is not None:
  75. self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished
  76. def _get(self, target: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]]):
  77. if self._manager is None:
  78. self._createNetworkManager()
  79. request = self._createEmptyRequest(target)
  80. self._last_request_time = time()
  81. reply = self._manager.get(request)
  82. if onFinished is not None:
  83. self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished
  84. def _delete(self, target: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]]):
  85. if self._manager is None:
  86. self._createNetworkManager()
  87. self._last_request_time = time()
  88. pass
  89. def _post(self, target: str, data: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None):
  90. if self._manager is None:
  91. self._createNetworkManager()
  92. request = self._createEmptyRequest(target)
  93. self._last_request_time = time()
  94. reply = self._manager.post(request, data)
  95. if onProgress is not None:
  96. reply.uploadProgress.connect(onProgress)
  97. if onFinished is not None:
  98. self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished
  99. def _postForm(self, target: str, header_data: str, body_data: bytes, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None):
  100. if self._manager is None:
  101. self._createNetworkManager()
  102. request = self._createEmptyRequest(target)
  103. multi_post_part = QHttpMultiPart()
  104. post_part = QHttpPart()
  105. post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data)
  106. post_part.setBody(body_data)
  107. multi_post_part.append(post_part)
  108. self._last_request_time = time()
  109. reply = self._manager.post(request, multi_post_part)
  110. # Due to garbage collection on python doing some weird stuff, we need to keep hold of a reference
  111. self._cached_multiparts[id(reply)] = (post_part, multi_post_part, reply)
  112. if onProgress is not None:
  113. reply.uploadProgress.connect(onProgress)
  114. if onFinished is not None:
  115. self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished
  116. def _onAuthenticationRequired(self, reply, authenticator):
  117. Logger.log("w", "Request to {url} required authentication, which was not implemented".format(url = reply.url().toString()))
  118. def _createNetworkManager(self):
  119. Logger.log("d", "Creating network manager")
  120. if self._manager:
  121. self._manager.finished.disconnect(self.__handleOnFinished)
  122. #self._manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged)
  123. self._manager.authenticationRequired.disconnect(self._onAuthenticationRequired)
  124. self._manager = QNetworkAccessManager()
  125. self._manager.finished.connect(self.__handleOnFinished)
  126. self._last_manager_create_time = time()
  127. self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
  128. #self._manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged) # for debug purposes
  129. def __handleOnFinished(self, reply: QNetworkReply):
  130. # Due to garbage collection, we need to cache certain bits of post operations.
  131. # As we don't want to keep them around forever, delete them if we get a reply.
  132. if reply.operation() == QNetworkAccessManager.PostOperation:
  133. self._clearCachedMultiPart(reply)
  134. if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) is None:
  135. # No status code means it never even reached remote.
  136. return
  137. self._last_response_time = time()
  138. if self._connection_state == ConnectionState.connecting:
  139. self.setConnectionState(ConnectionState.connected)
  140. callback_key = reply.url().toString() + str(reply.operation())
  141. try:
  142. if callback_key in self._onFinishedCallbacks:
  143. self._onFinishedCallbacks[callback_key](reply)
  144. except Exception:
  145. Logger.logException("w", "something went wrong with callback")
  146. @pyqtSlot(str, result=str)
  147. def getProperty(self, key):
  148. key = key.encode("utf-8")
  149. if key in self._properties:
  150. return self._properties.get(key, b"").decode("utf-8")
  151. else:
  152. return ""
  153. ## Get the unique key of this machine
  154. # \return key String containing the key of the machine.
  155. @pyqtProperty(str, constant=True)
  156. def key(self):
  157. return self._id
  158. ## The IP address of the printer.
  159. @pyqtProperty(str, constant=True)
  160. def address(self):
  161. return self._properties.get(b"address", b"").decode("utf-8")
  162. ## Name of the printer (as returned from the ZeroConf properties)
  163. @pyqtProperty(str, constant=True)
  164. def name(self):
  165. return self._properties.get(b"name", b"").decode("utf-8")
  166. ## Firmware version (as returned from the ZeroConf properties)
  167. @pyqtProperty(str, constant=True)
  168. def firmwareVersion(self):
  169. return self._properties.get(b"firmware_version", b"").decode("utf-8")
  170. ## IPadress of this printer
  171. @pyqtProperty(str, constant=True)
  172. def ipAddress(self):
  173. return self._address