Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Ultimaker/Cura.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/CuraDrive/src/DriveApiService.py')
-rw-r--r--plugins/CuraDrive/src/DriveApiService.py222
1 files changed, 72 insertions, 150 deletions
diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py
index d8349ccc29..2248b64389 100644
--- a/plugins/CuraDrive/src/DriveApiService.py
+++ b/plugins/CuraDrive/src/DriveApiService.py
@@ -1,90 +1,70 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-import base64
-import hashlib
-from datetime import datetime
-from tempfile import NamedTemporaryFile
-from typing import Any, Optional, List, Dict
+from typing import Any, Optional, List, Dict, Callable
-import requests
+from PyQt5.QtNetwork import QNetworkReply
from UM.Logger import Logger
-from UM.Message import Message
from UM.Signal import Signal, signalemitter
+from UM.TaskManagement.HttpRequestManager import HttpRequestManager
+from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
+from UM.i18n import i18nCatalog
from cura.CuraApplication import CuraApplication
-
-from .UploadBackupJob import UploadBackupJob
+from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
+from .CreateBackupJob import CreateBackupJob
+from .RestoreBackupJob import RestoreBackupJob
from .Settings import Settings
-from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
-## The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling.
@signalemitter
class DriveApiService:
+ """The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling."""
+
BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL)
- # Emit signal when restoring backup started or finished.
restoringStateChanged = Signal()
+ """Emits signal when restoring backup started or finished."""
- # Emit signal when creating backup started or finished.
creatingStateChanged = Signal()
+ """Emits signal when creating backup started or finished."""
def __init__(self) -> None:
self._cura_api = CuraApplication.getInstance().getCuraAPI()
-
- def getBackups(self) -> List[Dict[str, Any]]:
- access_token = self._cura_api.account.accessToken
- if not access_token:
- Logger.log("w", "Could not get access token.")
- return []
- try:
- backup_list_request = requests.get(self.BACKUP_URL, headers = {
- "Authorization": "Bearer {}".format(access_token)
- })
- except requests.exceptions.ConnectionError:
- Logger.logException("w", "Unable to connect with the server.")
- return []
-
- # HTTP status 300s mean redirection. 400s and 500s are errors.
- # Technically 300s are not errors, but the use case here relies on "requests" to handle redirects automatically.
- if backup_list_request.status_code >= 300:
- Logger.log("w", "Could not get backups list from remote: %s", backup_list_request.text)
- Message(catalog.i18nc("@info:backup_status", "There was an error listing your backups."), title = catalog.i18nc("@info:title", "Backup")).show()
- return []
-
- backup_list_response = backup_list_request.json()
- 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))
- return []
-
- return backup_list_response["data"]
+ self._json_cloud_scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance()))
+
+ def getBackups(self, changed: Callable[[List[Dict[str, Any]]], None]) -> None:
+ def callback(reply: QNetworkReply, error: Optional["QNetworkReply.NetworkError"] = None) -> 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"])
+
+ HttpRequestManager.getInstance().get(
+ self.BACKUP_URL,
+ callback= callback,
+ error_callback = callback,
+ scope=self._json_cloud_scope
+ )
def createBackup(self) -> None:
self.creatingStateChanged.emit(is_creating = True)
-
- # 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"])
- backup_upload_url = self._requestBackupUpload(backup_meta_data, len(backup_zip_file))
- if not backup_upload_url:
- self.creatingStateChanged.emit(is_creating = False, error_message ="Could not upload backup.")
- return
-
- # Upload the backup to storage.
- upload_backup_job = UploadBackupJob(backup_upload_url, backup_zip_file)
+ upload_backup_job = CreateBackupJob(self.BACKUP_URL)
upload_backup_job.finished.connect(self._onUploadFinished)
upload_backup_job.start()
- 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)
@@ -96,96 +76,38 @@ class DriveApiService:
download_url = backup.get("download_url")
if not download_url:
# If there is no download URL, we can't restore the backup.
- return self._emitRestoreError()
-
- try:
- download_package = requests.get(download_url, stream = True)
- except requests.exceptions.ConnectionError:
- Logger.logException("e", "Unable to connect with the server")
- return self._emitRestoreError()
-
- if download_package.status_code >= 300:
- # Something went wrong when attempting to download the backup.
- Logger.log("w", "Could not download backup from url %s: %s", download_url, download_package.text)
- return self._emitRestoreError()
-
- # We store the file in a temporary path fist to ensure integrity.
- temporary_backup_file = NamedTemporaryFile(delete = False)
- with open(temporary_backup_file.name, "wb") as write_backup:
- for chunk in download_package:
- write_backup.write(chunk)
-
- if not self._verifyMd5Hash(temporary_backup_file.name, backup.get("md5_hash", "")):
- # Don't restore the backup if the MD5 hashes do not match.
- # This can happen if the download was interrupted.
- Logger.log("w", "Remote and local MD5 hashes do not match, not restoring backup.")
- return self._emitRestoreError()
-
- # Tell Cura to place the backup back in the user data folder.
- with open(temporary_backup_file.name, "rb") as read_backup:
- self._cura_api.backups.restoreBackup(read_backup.read(), backup.get("metadata", {}))
- self.restoringStateChanged.emit(is_restoring = False)
-
- def _emitRestoreError(self) -> None:
- self.restoringStateChanged.emit(is_restoring = False,
- error_message = catalog.i18nc("@info:backup_status",
- "There was an error trying to restore your backup."))
-
- # Verify the MD5 hash of a file.
- # \param file_path Full path to the file.
- # \param known_hash The known MD5 hash of the file.
- # \return: Success or not.
+ Logger.warning("backup download_url is missing. Aborting backup.")
+ self.restoringStateChanged.emit(is_restoring = False,
+ error_message = catalog.i18nc("@info:backup_status",
+ "There was an error trying to restore your backup."))
+ return
+
+ restore_backup_job = RestoreBackupJob(backup)
+ restore_backup_job.finished.connect(self._onRestoreFinished)
+ restore_backup_job.start()
+
+ def _onRestoreFinished(self, job: "RestoreBackupJob") -> None:
+ if job.restore_backup_error_message != "":
+ # If the job contains an error message we pass it along so the UI can display it.
+ self.restoringStateChanged.emit(is_restoring=False)
+ else:
+ self.restoringStateChanged.emit(is_restoring = False, error_message = job.restore_backup_error_message)
+
+ def deleteBackup(self, backup_id: str, finished_callable: Callable[[bool], None]):
+
+ def finishedCallback(reply: QNetworkReply, ca: Callable[[bool], None] = finished_callable) -> None:
+ self._onDeleteRequestCompleted(reply, ca)
+
+ def errorCallback(reply: QNetworkReply, error: QNetworkReply.NetworkError, ca: Callable[[bool], None] = finished_callable) -> None:
+ self._onDeleteRequestCompleted(reply, ca, error)
+
+ HttpRequestManager.getInstance().delete(
+ url = "{}/{}".format(self.BACKUP_URL, backup_id),
+ callback = finishedCallback,
+ error_callback = errorCallback,
+ scope= self._json_cloud_scope
+ )
+
@staticmethod
- def _verifyMd5Hash(file_path: str, known_hash: str) -> bool:
- with open(file_path, "rb") as read_backup:
- local_md5_hash = base64.b64encode(hashlib.md5(read_backup.read()).digest(), altchars = b"_-").decode("utf-8")
- return known_hash == local_md5_hash
-
- def deleteBackup(self, backup_id: str) -> bool:
- access_token = self._cura_api.account.accessToken
- if not access_token:
- Logger.log("w", "Could not get access token.")
- return False
-
- try:
- delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
- "Authorization": "Bearer {}".format(access_token)
- })
- except requests.exceptions.ConnectionError:
- Logger.logException("e", "Unable to connect with the server")
- return False
-
- if delete_backup.status_code >= 300:
- Logger.log("w", "Could not delete backup: %s", delete_backup.text)
- return False
- return True
-
- # 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.
- def _requestBackupUpload(self, backup_metadata: Dict[str, Any], backup_size: int) -> Optional[str]:
- access_token = self._cura_api.account.accessToken
- if not access_token:
- Logger.log("w", "Could not get access token.")
- return None
- try:
- backup_upload_request = requests.put(
- self.BACKUP_URL,
- json = {"data": {"backup_size": backup_size,
- "metadata": backup_metadata
- }
- },
- headers = {
- "Authorization": "Bearer {}".format(access_token)
- })
- except requests.exceptions.ConnectionError:
- Logger.logException("e", "Unable to connect with the server")
- return None
-
- # Any status code of 300 or above indicates an error.
- if backup_upload_request.status_code >= 300:
- Logger.log("w", "Could not request backup upload: %s", backup_upload_request.text)
- return None
-
- return backup_upload_request.json()["data"]["upload_url"]
+ def _onDeleteRequestCompleted(reply: QNetworkReply, callable: Callable[[bool], None], error: Optional["QNetworkReply.NetworkError"] = None) -> None:
+ callable(HttpRequestManager.replyIndicatesSuccess(reply, error))