diff --git a/plugins/DigitalLibrary/resources/qml/SaveProjectFilesPage.qml b/plugins/DigitalLibrary/resources/qml/SaveProjectFilesPage.qml index 90b9bd8b05..0a94a4f48a 100644 --- a/plugins/DigitalLibrary/resources/qml/SaveProjectFilesPage.qml +++ b/plugins/DigitalLibrary/resources/qml/SaveProjectFilesPage.qml @@ -14,6 +14,9 @@ import DigitalFactory 1.0 as DF Item { id: base + + property variant catalog: UM.I18nCatalog { name: "cura" } + width: parent.width height: parent.height @@ -190,53 +193,29 @@ Item text: "Save" enabled: (asProjectCheckbox.checked || asSlicedCheckbox.checked) && dfFilenameTextfield.text.length >= 1 && dfFilenameTextfield.state !== 'invalid' - onClicked: - { - let saveAsFormats = []; - if (asProjectCheckbox.checked) - { - saveAsFormats.push("3mf"); - } - if (asSlicedCheckbox.checked) - { - saveAsFormats.push("ufp"); - } - manager.saveFileToSelectedProject(dfFilenameTextfield.text, saveAsFormats); - } + onClicked: manager.saveFileToSelectedProject(dfFilenameTextfield.text, asProjectComboBox.currentValue) busy: false } - Row + Cura.ComboBox { + id: asProjectComboBox - id: saveAsFormatRow + width: UM.Theme.getSize("combobox_wide").width + height: saveButton.height anchors.verticalCenter: saveButton.verticalCenter anchors.right: saveButton.left anchors.rightMargin: UM.Theme.getSize("thin_margin").height - width: childrenRect.width - spacing: UM.Theme.getSize("default_margin").width - UM.CheckBox - { - id: asProjectCheckbox - height: UM.Theme.getSize("checkbox").height - anchors.verticalCenter: parent.verticalCenter - checked: true - text: "Save Cura project" - font: UM.Theme.getFont("medium") - } + enabled: UM.Backend.state == UM.Backend.Done + currentIndex: UM.Backend.state == UM.Backend.Done ? 0 : 1 + textRole: "text" + valueRole: "value" - UM.CheckBox - { - id: asSlicedCheckbox - height: UM.Theme.getSize("checkbox").height - anchors.verticalCenter: parent.verticalCenter - - enabled: UM.Backend.state == UM.Backend.Done - checked: UM.Backend.state == UM.Backend.Done - text: "Save print file" - font: UM.Theme.getFont("medium") - } + model: [ + { text: catalog.i18nc("@option", "Save Cura project and print file"), key: "3mf_ufp", value: ["3mf", "ufp"] }, + { text: catalog.i18nc("@option", "Save Cura project"), key: "3mf", value: ["3mf"] }, + ] } Component.onCompleted: diff --git a/plugins/DigitalLibrary/src/DFFileExportAndUploadManager.py b/plugins/DigitalLibrary/src/DFFileExportAndUploadManager.py index 940711f19c..69bf4b1233 100644 --- a/plugins/DigitalLibrary/src/DFFileExportAndUploadManager.py +++ b/plugins/DigitalLibrary/src/DFFileExportAndUploadManager.py @@ -37,17 +37,18 @@ class DFFileExportAndUploadManager: formats: List[str], on_upload_error: Callable[[], Any], on_upload_success: Callable[[], Any], - on_upload_finished: Callable[[], Any] , + on_upload_finished: Callable[[], Any], on_upload_progress: Callable[[int], Any]) -> None: - self._file_handlers = file_handlers # type: Dict[str, FileHandler] - self._nodes = nodes # type: List[SceneNode] - self._library_project_id = library_project_id # type: str - self._library_project_name = library_project_name # type: str - self._file_name = file_name # type: str - self._upload_jobs = [] # type: List[ExportFileJob] - self._formats = formats # type: List[str] + self._file_handlers: Dict[str, FileHandler] = file_handlers + self._nodes: List[SceneNode] = nodes + self._library_project_id: str = library_project_id + self._library_project_name: str = library_project_name + self._file_name: str = file_name + self._upload_jobs: List[ExportFileJob] = [] + self._formats: List[str] = formats self._api = DigitalFactoryApiClient(application = CuraApplication.getInstance(), on_error = lambda error: Logger.log("e", str(error))) + self._source_file_id: Optional[str] = None # Functions of the parent class that should be called based on the upload process output self._on_upload_error = on_upload_error @@ -59,7 +60,7 @@ class DFFileExportAndUploadManager: # show the success message (once both upload jobs are done) self._message_lock = threading.Lock() - self._file_upload_job_metadata = self.initializeFileUploadJobMetadata() # type: Dict[str, Dict[str, Any]] + self._file_upload_job_metadata: Dict[str, Dict[str, Any]] = self.initializeFileUploadJobMetadata() self.progress_message = Message( title = "Uploading...", @@ -113,7 +114,8 @@ class DFFileExportAndUploadManager: content_type = job.getMimeType(), job_name = job.getFileName(), file_size = len(job.getOutput()), - library_project_id = self._library_project_id + library_project_id = self._library_project_id, + source_file_id = self._source_file_id ) self._api.requestUploadUFP(request, on_finished = self._uploadFileData, on_error = self._onRequestUploadPrintFileFailed) @@ -125,6 +127,9 @@ class DFFileExportAndUploadManager: """ if isinstance(file_upload_response, DFLibraryFileUploadResponse): file_name = file_upload_response.file_name + + # store the `file_id` so it can be as `source_file_id` when uploading the print file + self._source_file_id = file_upload_response.file_id elif isinstance(file_upload_response, DFPrintJobUploadResponse): file_name = file_upload_response.job_name if file_upload_response.job_name is not None else "" else: @@ -145,6 +150,8 @@ class DFFileExportAndUploadManager: on_progress = self._onUploadProgress, on_error = self._onUploadError) + self._handleNextUploadJob() + def _onUploadProgress(self, filename: str, progress: int) -> None: """ Updates the progress message according to the total progress of the two files and displays it to the user. It is @@ -325,8 +332,13 @@ class DFFileExportAndUploadManager: message.hide() def start(self) -> None: - for job in self._upload_jobs: - job.start() + self._handleNextUploadJob() + + def _handleNextUploadJob(self): + match self._upload_jobs: + case [job, *jobs]: + job.start() + self._upload_jobs = jobs def initializeFileUploadJobMetadata(self) -> Dict[str, Any]: metadata = {} diff --git a/plugins/DigitalLibrary/src/DFFileUploader.py b/plugins/DigitalLibrary/src/DFFileUploader.py index 6cad7828e2..23a4620bcf 100644 --- a/plugins/DigitalLibrary/src/DFFileUploader.py +++ b/plugins/DigitalLibrary/src/DFFileUploader.py @@ -39,8 +39,8 @@ class DFFileUploader: :param on_error: The method to be called when an error occurs. """ - self._http = http # type: HttpRequestManager - self._df_file = df_file # type: Union[DFLibraryFileUploadResponse, DFPrintJobUploadResponse] + self._http: HttpRequestManager = http + self._df_file: Union[DFLibraryFileUploadResponse, DFPrintJobUploadResponse] = df_file self._file_name = "" if isinstance(self._df_file, DFLibraryFileUploadResponse): self._file_name = self._df_file.file_name @@ -51,7 +51,7 @@ class DFFileUploader: self._file_name = "" else: raise TypeError("Incorrect input type") - self._data = data # type: bytes + self._data: bytes = data self._on_finished = on_finished self._on_success = on_success diff --git a/plugins/DigitalLibrary/src/DFPrintJobUploadRequest.py b/plugins/DigitalLibrary/src/DFPrintJobUploadRequest.py index ab434e3f04..b49336a2f2 100644 --- a/plugins/DigitalLibrary/src/DFPrintJobUploadRequest.py +++ b/plugins/DigitalLibrary/src/DFPrintJobUploadRequest.py @@ -1,12 +1,14 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from typing import Optional + from .BaseModel import BaseModel # Model that represents the request to upload a print job to the cloud class DFPrintJobUploadRequest(BaseModel): - def __init__(self, job_name: str, file_size: int, content_type: str, library_project_id: str, **kwargs) -> None: + def __init__(self, job_name: str, file_size: int, content_type: str, library_project_id: str, source_file_id: str, **kwargs) -> None: """Creates a new print job upload request. :param job_name: The name of the print job. @@ -18,4 +20,5 @@ class DFPrintJobUploadRequest(BaseModel): self.file_size = file_size self.content_type = content_type self.library_project_id = library_project_id + self.source_file_id = source_file_id super().__init__(**kwargs) diff --git a/plugins/DigitalLibrary/src/DigitalFactoryApiClient.py b/plugins/DigitalLibrary/src/DigitalFactoryApiClient.py index 3456638ba6..de09ea2a09 100644 --- a/plugins/DigitalLibrary/src/DigitalFactoryApiClient.py +++ b/plugins/DigitalLibrary/src/DigitalFactoryApiClient.py @@ -40,7 +40,7 @@ class DigitalFactoryApiClient: DEFAULT_REQUEST_TIMEOUT = 10 # seconds # In order to avoid garbage collection we keep the callbacks in this list. - _anti_gc_callbacks = [] # type: List[Callable[[Any], None]] + _anti_gc_callbacks: List[Callable[[Any], None]] = [] def __init__(self, application: CuraApplication, on_error: Callable[[List[CloudError]], None], projects_limit_per_page: Optional[int] = None) -> None: """Initializes a new digital factory API client. @@ -54,7 +54,7 @@ class DigitalFactoryApiClient: self._scope = JsonDecoratorScope(UltimakerCloudScope(application)) self._http = HttpRequestManager.getInstance() self._on_error = on_error - self._file_uploader = None # type: Optional[DFFileUploader] + self._file_uploader: Optional[DFFileUploader] = None self._library_max_private_projects: Optional[int] = None self._projects_pagination_mgr = PaginationManager(limit = projects_limit_per_page) if projects_limit_per_page else None # type: Optional[PaginationManager]