diff options
author | Nino van Hooff <ninovanhooff@gmail.com> | 2020-03-10 15:34:18 +0300 |
---|---|---|
committer | Nino van Hooff <ninovanhooff@gmail.com> | 2020-03-10 15:35:37 +0300 |
commit | ed5c2b3f43c281ca10b9f6f57ae8120b9c57aada (patch) | |
tree | 8980c409e96f55d2942a65a4a911ba234ae93922 /plugins/CuraDrive | |
parent | 244d018a2eedb41d92339c6c3d46a60424fc30fe (diff) |
Refactor the create backup implementation to CreateBackupJob
Diffstat (limited to 'plugins/CuraDrive')
-rw-r--r-- | plugins/CuraDrive/src/CreateBackupJob.py | 122 | ||||
-rw-r--r-- | plugins/CuraDrive/src/DriveApiService.py | 69 | ||||
-rw-r--r-- | plugins/CuraDrive/src/UploadBackupJob.py | 39 |
3 files changed, 129 insertions, 101 deletions
diff --git a/plugins/CuraDrive/src/CreateBackupJob.py b/plugins/CuraDrive/src/CreateBackupJob.py new file mode 100644 index 0000000000..603733137b --- /dev/null +++ b/plugins/CuraDrive/src/CreateBackupJob.py @@ -0,0 +1,122 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. +import json +import threading +from typing import Any, Dict, Optional + +from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest +from datetime import datetime + +from UM.Job import Job +from UM.Logger import Logger +from UM.Message import Message +from UM.TaskManagement.HttpRequestManager import HttpRequestManager +from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope + +from UM.i18n import i18nCatalog +from cura.CuraApplication import CuraApplication +from plugins.Toolbox.src.UltimakerCloudScope import UltimakerCloudScope + +catalog = i18nCatalog("cura") + + +class CreateBackupJob(Job): + """Creates backup zip, requests upload url and uploads the backup file to cloud storage.""" + + MESSAGE_TITLE = catalog.i18nc("@info:title", "Backups") + + def __init__(self, api_backup_url: str) -> None: + """ Create a new backup Job. start the job by calling start() + + :param api_backup_url: The url of the 'backups' endpoint of the Cura Drive Api + """ + + super().__init__() + + self._api_backup_url = api_backup_url + self._jsonCloudScope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) + + + self._backup_zip = None + self._upload_success = False + self._upload_success_available = threading.Event() + self.backup_upload_error_message = "" + + def run(self) -> None: + upload_message = Message(catalog.i18nc("@info:backup_status", "Creating your backup..."), title = self.MESSAGE_TITLE, progress = -1) + upload_message.show() + CuraApplication.getInstance().processEvents() + cura_api = CuraApplication.getInstance().getCuraAPI() + self._backup_zip, backup_meta_data = cura_api.backups.createBackup() + + if not self._backup_zip or not backup_meta_data: + self.backup_upload_error_message = "Could not create backup." + upload_message.hide() + return + + upload_message.setText(catalog.i18nc("@info:backup_status", "Uploading your backup...")) + CuraApplication.getInstance().processEvents() + + # Create an upload entry for the backup. + timestamp = datetime.now().isoformat() + backup_meta_data["description"] = "{}.backup.{}.cura.zip".format(timestamp, backup_meta_data["cura_release"]) + self._requestUploadSlot(backup_meta_data, len(self._backup_zip)) + + self._upload_success_available.wait() + upload_message.hide() + + def _requestUploadSlot(self, backup_metadata: Dict[str, Any], backup_size: int) -> None: + """Request a backup upload slot from the API. + + :param backup_metadata: A dict containing some meta data about the backup. + :param backup_size: The size of the backup file in bytes. + :return: The upload URL for the actual backup file if successful, otherwise None. + """ + + payload = json.dumps({"data": {"backup_size": backup_size, + "metadata": backup_metadata + } + }).encode() + + HttpRequestManager.getInstance().put( + self._api_backup_url, + data = payload, + callback = self._onUploadSlotCompleted, + error_callback = self._onUploadSlotCompleted, + scope = self._jsonCloudScope) + + def _onUploadSlotCompleted(self, reply: QNetworkReply, error: Optional["QNetworkReply.NetworkError"] = None) -> None: + if error is not None: + Logger.warning(str(error)) + self.backup_upload_error_message = "Could not upload backup." + self._upload_success_available.set() + return + if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) >= 300: + Logger.warning("Could not request backup upload: %s", HttpRequestManager.readText(reply)) + self.backup_upload_error_message = "Could not upload backup." + self._upload_success_available.set() + return + + backup_upload_url = HttpRequestManager.readJSON(reply)["data"]["upload_url"] + + # Upload the backup to storage. + HttpRequestManager.getInstance().put( + backup_upload_url, + data=self._backup_zip, + callback=self._uploadFinishedCallback, + error_callback=self._uploadFinishedCallback + ) + + def _uploadFinishedCallback(self, reply: QNetworkReply, error: QNetworkReply.NetworkError = None): + self.backup_upload_error_text = HttpRequestManager.readText(reply) + + if HttpRequestManager.replyIndicatesSuccess(reply, error): + self._upload_success = True + Message(catalog.i18nc("@info:backup_status", "Your backup has finished uploading."), title = self.MESSAGE_TITLE).show() + else: + self.backup_upload_error_text = self.backup_upload_error_text + Logger.log("w", "Could not upload backup file: %s", self.backup_upload_error_text) + Message(catalog.i18nc("@info:backup_status", "There was an error while uploading your backup."), + title=self.MESSAGE_TITLE).show() + + self._upload_success_available.set() diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index 922ca8afa7..04f935268b 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -3,8 +3,6 @@ import base64 import hashlib -import json -from datetime import datetime from tempfile import NamedTemporaryFile from typing import Any, Optional, List, Dict, Callable @@ -17,7 +15,7 @@ from plugins.Toolbox.src.UltimakerCloudScope import UltimakerCloudScope from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest -from .UploadBackupJob import UploadBackupJob +from .CreateBackupJob import CreateBackupJob from .Settings import Settings from UM.i18n import i18nCatalog @@ -40,21 +38,20 @@ class DriveApiService: def __init__(self) -> None: self._cura_api = CuraApplication.getInstance().getCuraAPI() self._jsonCloudScope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) - self._current_backup_zip_file = None - - self.creatingStateChanged.connect(self._creatingStateChanged) def getBackups(self, changed: Callable[[List], None]): def callback(reply: QNetworkReply, error: Optional["QNetworkReply.NetworkError"] = None): if error is not None: Logger.log("w", "Could not get backups: " + str(error)) changed([]) + return backup_list_response = HttpRequestManager.readJSON(reply) if "data" not in backup_list_response: Logger.log("w", "Could not get backups from remote, actual response body was: %s", str(backup_list_response)) changed([]) # empty list of backups + return changed(backup_list_response["data"]) @@ -67,20 +64,11 @@ class DriveApiService: def createBackup(self) -> None: self.creatingStateChanged.emit(is_creating = True) + upload_backup_job = CreateBackupJob(self.BACKUP_URL) + upload_backup_job.finished.connect(self._onUploadFinished) + upload_backup_job.start() - # Create the backup. - backup_zip_file, backup_meta_data = self._cura_api.backups.createBackup() - if not backup_zip_file or not backup_meta_data: - self.creatingStateChanged.emit(is_creating = False, error_message ="Could not create backup.") - return - - # Create an upload entry for the backup. - timestamp = datetime.now().isoformat() - backup_meta_data["description"] = "{}.backup.{}.cura.zip".format(timestamp, backup_meta_data["cura_release"]) - self._requestBackupUpload(backup_meta_data, len(backup_zip_file)) - self._current_backup_zip_file = backup_zip_file - - def _onUploadFinished(self, job: "UploadBackupJob") -> None: + def _onUploadFinished(self, job: "CreateBackupJob") -> None: if job.backup_upload_error_message != "": # If the job contains an error message we pass it along so the UI can display it. self.creatingStateChanged.emit(is_creating = False, error_message = job.backup_upload_error_message) @@ -167,46 +155,3 @@ class DriveApiService: def _onDeleteRequestCompleted(self, reply: QNetworkReply, error: Optional["QNetworkReply.NetworkError"] = None, callable = None): callable(HttpRequestManager.replyIndicatesSuccess(reply, error)) - - def _requestBackupUpload(self, backup_metadata: Dict[str, Any], backup_size: int) -> None: - """Request a backup upload slot from the API. - - :param backup_metadata: A dict containing some meta data about the backup. - :param backup_size: The size of the backup file in bytes. - :return: The upload URL for the actual backup file if successful, otherwise None. - """ - - payload = json.dumps({"data": {"backup_size": backup_size, - "metadata": backup_metadata - } - }).encode() - - HttpRequestManager.getInstance().put( - self.BACKUP_URL, - data = payload, - callback = self._onBackupUploadSlotCompleted, - error_callback = self._onBackupUploadSlotCompleted, - scope = self._jsonCloudScope) - - def _onBackupUploadSlotCompleted(self, reply: QNetworkReply, error: Optional["QNetworkReply.NetworkError"] = None) -> None: - if error is not None: - Logger.warning(str(error)) - self.creatingStateChanged.emit(is_creating=False, error_message="Could not upload backup.") - return - if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) >= 300: - Logger.warning("Could not request backup upload: %s", HttpRequestManager.readText(reply)) - self.creatingStateChanged.emit(is_creating=False, error_message="Could not upload backup.") - return - - backup_upload_url = HttpRequestManager.readJSON(reply)["data"]["upload_url"] - - # Upload the backup to storage. - upload_backup_job = UploadBackupJob(backup_upload_url, self._current_backup_zip_file) - upload_backup_job.finished.connect(self._onUploadFinished) - upload_backup_job.start() - - def _creatingStateChanged(self, is_creating: bool = False, error_message: str = None) -> None: - """Cleanup after a backup is not needed anymore""" - - if not is_creating: - self._current_backup_zip_file = None diff --git a/plugins/CuraDrive/src/UploadBackupJob.py b/plugins/CuraDrive/src/UploadBackupJob.py deleted file mode 100644 index 8ade697cb3..0000000000 --- a/plugins/CuraDrive/src/UploadBackupJob.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) 2018 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -import requests - -from UM.Job import Job -from UM.Logger import Logger -from UM.Message import Message - -from UM.i18n import i18nCatalog -catalog = i18nCatalog("cura") - - -class UploadBackupJob(Job): - MESSAGE_TITLE = catalog.i18nc("@info:title", "Backups") - - # This job is responsible for uploading the backup file to cloud storage. - # As it can take longer than some other tasks, we schedule this using a Cura Job. - def __init__(self, signed_upload_url: str, backup_zip: bytes) -> None: - super().__init__() - self._signed_upload_url = signed_upload_url - self._backup_zip = backup_zip - self._upload_success = False - self.backup_upload_error_message = "" - - def run(self) -> None: - upload_message = Message(catalog.i18nc("@info:backup_status", "Uploading your backup..."), title = self.MESSAGE_TITLE, progress = -1) - upload_message.show() - - backup_upload = requests.put(self._signed_upload_url, data = self._backup_zip) - upload_message.hide() - - if backup_upload.status_code >= 300: - self.backup_upload_error_message = backup_upload.text - Logger.log("w", "Could not upload backup file: %s", backup_upload.text) - Message(catalog.i18nc("@info:backup_status", "There was an error while uploading your backup."), title = self.MESSAGE_TITLE).show() - else: - self._upload_success = True - Message(catalog.i18nc("@info:backup_status", "Your backup has finished uploading."), title = self.MESSAGE_TITLE).show() |