SendMaterialJob.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import json #To understand the list of materials from the printer reply.
  4. import os #To walk over material files.
  5. import os.path #To filter on material files.
  6. from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest #To listen to the reply from the printer.
  7. from typing import Any, Dict, Set, TYPE_CHECKING
  8. import urllib.parse #For getting material IDs from their file names.
  9. from UM.Job import Job #The interface we're implementing.
  10. from UM.Logger import Logger
  11. from UM.MimeTypeDatabase import MimeTypeDatabase #To strip the extensions of the material profile files.
  12. from UM.Resources import Resources
  13. from UM.Settings.ContainerRegistry import ContainerRegistry #To find the GUIDs of materials.
  14. from cura.CuraApplication import CuraApplication #For the resource types.
  15. if TYPE_CHECKING:
  16. from .ClusterUM3OutputDevice import ClusterUM3OutputDevice
  17. ## Asynchronous job to send material profiles to the printer.
  18. #
  19. # This way it won't freeze up the interface while sending those materials.
  20. class SendMaterialJob(Job):
  21. def __init__(self, device: "ClusterUM3OutputDevice"):
  22. super().__init__()
  23. self.device = device #type: ClusterUM3OutputDevice
  24. def run(self) -> None:
  25. self.device.get("materials/", on_finished = self.sendMissingMaterials)
  26. def sendMissingMaterials(self, reply: QNetworkReply) -> None:
  27. if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: #Got an error from the HTTP request.
  28. Logger.log("e", "Couldn't request current material storage on printer. Not syncing materials.")
  29. return
  30. remote_materials_list = reply.readAll().data().decode("utf-8")
  31. try:
  32. remote_materials_list = json.loads(remote_materials_list)
  33. except json.JSONDecodeError:
  34. Logger.log("e", "Current material storage on printer was a corrupted reply.")
  35. return
  36. try:
  37. remote_materials_by_guid = {material["guid"]: material for material in remote_materials_list} #Index by GUID.
  38. except KeyError:
  39. Logger.log("e", "Current material storage on printer was an invalid reply (missing GUIDs).")
  40. return
  41. container_registry = ContainerRegistry.getInstance()
  42. local_materials_list = filter(lambda material: ("GUID" in material and "version" in material and "id" in material), container_registry.findContainersMetadata(type = "material"))
  43. local_materials_by_guid = {material["GUID"]: material for material in local_materials_list if material["id"] == material["base_file"]}
  44. for material in local_materials_list: #For each GUID get the material with the highest version number.
  45. try:
  46. if int(material["version"]) > local_materials_by_guid[material["GUID"]]["version"]:
  47. local_materials_by_guid[material["GUID"]] = material
  48. except ValueError:
  49. Logger.log("e", "Material {material_id} has invalid version number {number}.".format(material_id = material["id"], number = material["version"]))
  50. continue
  51. materials_to_send = set() #type: Set[Dict[str, Any]]
  52. for guid, material in local_materials_by_guid.items():
  53. if guid not in remote_materials_by_guid:
  54. materials_to_send.add(material["id"])
  55. continue
  56. try:
  57. if int(material["version"]) > remote_materials_by_guid[guid]["version"]:
  58. materials_to_send.add(material["id"])
  59. continue
  60. except KeyError:
  61. Logger.log("e", "Current material storage on printer was an invalid reply (missing version).")
  62. return
  63. for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.MaterialInstanceContainer):
  64. try:
  65. mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path)
  66. except MimeTypeDatabase.MimeTypeNotFoundError:
  67. continue #Not the sort of file we'd like to send then.
  68. _, file_name = os.path.split(file_path)
  69. material_id = urllib.parse.unquote_plus(mime_type.stripExtension(file_name))
  70. if material_id not in materials_to_send:
  71. continue
  72. parts = []
  73. with open(file_path, "rb") as f:
  74. parts.append(self.device._createFormPart("name=\"file\"; filename=\"{file_name}\"".format(file_name = file_name), f.read()))
  75. signature_file_path = file_path + ".sig"
  76. if os.path.exists(signature_file_path):
  77. _, signature_file_name = os.path.split(signature_file_path)
  78. with open(signature_file_path, "rb") as f:
  79. parts.append(self.device._createFormPart("name=\"signature_file\"; filename=\"{file_name}\"".format(file_name = signature_file_name), f.read()))
  80. Logger.log("d", "Syncing material {material_id} with cluster.".format(material_id = material_id))
  81. self.device.postFormWithParts(target = "materials/", parts = parts, on_finished = self.sendingFinished)
  82. def sendingFinished(self, reply: QNetworkReply):
  83. if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
  84. Logger.log("e", "Received error code from printer when syncing material: {code}".format(code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)))
  85. Logger.log("e", reply.readAll().data().decode("utf-8"))