Cura/cura/PrinterOutput/UploadMaterialsJob.py
Ghostkeeper 4ccd4caaad
Store material profile ID received from the upload request
We'll need this later to be able to tell the server which material archive it should send to certain printers.

Contributes to issue CURA-8609.
2021-10-12 13:04:22 +02:00

111 lines
No EOL
4.8 KiB
Python

# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QUrl
import os # To delete the archive when we're done.
import tempfile # To create an archive before we upload it.
import enum
import cura.CuraApplication # Imported like this to prevent circular imports.
from cura.UltimakerCloud import UltimakerCloudConstants # To know where the API is.
from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To know how to communicate with this server.
from UM.Job import Job
from UM.Logger import Logger
from UM.Signal import Signal
from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To call the API.
from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from PyQt5.QtNetwork import QNetworkReply
from cura.UltimakerCloud.CloudMaterialSync import CloudMaterialSync
class UploadMaterialsJob(Job):
"""
Job that uploads a set of materials to the Digital Factory.
"""
UPLOAD_REQUEST_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/connect/v1/materials/upload"
class Result(enum.IntEnum):
SUCCCESS = 0
FAILED = 1
def __init__(self, material_sync: "CloudMaterialSync"):
super().__init__()
self._material_sync = material_sync
self._scope = JsonDecoratorScope(UltimakerCloudScope(cura.CuraApplication.CuraApplication.getInstance())) # type: JsonDecoratorScope
self._archive_filename = None # type: Optional[str]
self._archive_remote_id = None # type: Optional[str] # ID that the server gives to this archive. Used to communicate about the archive to the server.
uploadCompleted = Signal()
uploadProgressChanged = Signal()
def run(self):
archive_file = tempfile.NamedTemporaryFile("wb", delete = False)
archive_file.close()
self._archive_filename = archive_file.name
self._material_sync.exportAll(QUrl.fromLocalFile(self._archive_filename), notify_progress = self.uploadProgressChanged)
file_size = os.path.getsize(self._archive_filename)
http = HttpRequestManager.getInstance()
http.get(
url = self.UPLOAD_REQUEST_URL + f"?file_size={file_size}&file_name=cura.umm", # File name can be anything as long as it's .umm. It's not used by Cloud or firmware.
callback = self.onUploadRequestCompleted,
error_callback = self.onError,
scope = self._scope
)
def onUploadRequestCompleted(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"]):
if error is not None:
Logger.error(f"Could not request URL to upload material archive to: {error}")
self.setResult(self.Result.FAILED)
return
response_data = HttpRequestManager.readJSON(reply)
if response_data is None:
Logger.error(f"Invalid response to material upload request. Could not parse JSON data.")
self.setResult(self.Result.FAILED)
return
if "upload_url" not in response_data:
Logger.error(f"Invalid response to material upload request: Missing 'upload_url' field to upload archive to.")
self.setResult(self.Result.FAILED)
return
if "material_profile_id" not in response_data:
Logger.error(f"Invalid response to material upload request: Missing 'material_profile_id' to communicate about the materials with the server.")
self.setResult(self.Result.FAILED)
return
upload_url = response_data["upload_url"]
self._archive_remote_id = response_data["material_profile_id"]
file_data = open(self._archive_filename, "rb").read()
http = HttpRequestManager.getInstance()
http.put(
url = upload_url,
data = file_data,
callback = self.onUploadCompleted,
error_callback = self.onError,
scope = self._scope
)
def onUploadCompleted(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"]):
if error is not None:
Logger.error(f"Failed to upload material archive: {error}")
self.setError(UploadMaterialsError(error))
self.setResult(self.Result.FAILED)
else:
self.setResult(self.Result.SUCCESS)
self.uploadCompleted.emit(self.getResult(), self.getError())
def onError(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"]):
Logger.error(f"Failed to upload material archive: {error}")
self.setResult(self.Result.FAILED)
self.setError(UploadMaterialsError(error))
self.uploadCompleted.emit(self.getResult(), self.getError())
class UploadMaterialsError(Exception):
"""
Marker class to indicate something went wrong while uploading.
"""
pass