From c62cb84c75c158a21ea703fd853fb5f68f3d2789 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Mon, 10 Dec 2018 14:18:10 +0100 Subject: [PATCH 01/51] Added CuraDirve plugin to Cura build CURA-6005 --- .gitignore | 1 - plugins/CuraDrive/__init__.py | 14 ++ plugins/CuraDrive/plugin.json | 8 + plugins/CuraDrive/src/DriveApiService.py | 185 ++++++++++++++++ plugins/CuraDrive/src/DrivePluginExtension.py | 200 ++++++++++++++++++ plugins/CuraDrive/src/Settings.py | 36 ++++ plugins/CuraDrive/src/UploadBackupJob.py | 39 ++++ plugins/CuraDrive/src/__init__.py | 0 .../CuraDrive/src/models/BackupListModel.py | 38 ++++ plugins/CuraDrive/src/models/__init__.py | 0 .../src/qml/components/ActionButton.qml | 67 ++++++ .../src/qml/components/ActionCheckBox.qml | 49 +++++ .../src/qml/components/ActionToolTip.qml | 29 +++ .../src/qml/components/BackupList.qml | 31 +++ .../src/qml/components/BackupListFooter.qml | 42 ++++ .../src/qml/components/BackupListItem.qml | 112 ++++++++++ .../qml/components/BackupListItemDetails.qml | 61 ++++++ .../components/BackupListItemDetailsRow.qml | 52 +++++ .../CuraDrive/src/qml/components/Divider.qml | 11 + plugins/CuraDrive/src/qml/components/Icon.qml | 56 +++++ .../src/qml/components/RightSideScrollBar.qml | 13 ++ .../src/qml/images/avatar_default.png | Bin 0 -> 3115 bytes .../CuraDrive/src/qml/images/background.svg | 12 ++ plugins/CuraDrive/src/qml/images/backup.svg | 3 + plugins/CuraDrive/src/qml/images/cura.svg | 7 + .../CuraDrive/src/qml/images/cura_logo.jpg | Bin 0 -> 19308 bytes .../CuraDrive/src/qml/images/cura_logo.png | Bin 0 -> 13258 bytes plugins/CuraDrive/src/qml/images/delete.svg | 7 + plugins/CuraDrive/src/qml/images/folder.svg | 7 + plugins/CuraDrive/src/qml/images/home.svg | 3 + plugins/CuraDrive/src/qml/images/icon.png | Bin 0 -> 21924 bytes plugins/CuraDrive/src/qml/images/info.svg | 4 + .../src/qml/images/inverted_circle.png | Bin 0 -> 1608 bytes plugins/CuraDrive/src/qml/images/loading.gif | Bin 0 -> 6762 bytes plugins/CuraDrive/src/qml/images/material.svg | 7 + plugins/CuraDrive/src/qml/images/plugin.svg | 7 + .../src/qml/images/preview_banner.png | Bin 0 -> 8324 bytes plugins/CuraDrive/src/qml/images/printer.svg | 14 ++ plugins/CuraDrive/src/qml/images/profile.svg | 3 + plugins/CuraDrive/src/qml/images/restore.svg | 7 + plugins/CuraDrive/src/qml/main.qml | 42 ++++ .../CuraDrive/src/qml/pages/BackupsPage.qml | 73 +++++++ .../CuraDrive/src/qml/pages/WelcomePage.qml | 48 +++++ 43 files changed, 1287 insertions(+), 1 deletion(-) create mode 100644 plugins/CuraDrive/__init__.py create mode 100644 plugins/CuraDrive/plugin.json create mode 100644 plugins/CuraDrive/src/DriveApiService.py create mode 100644 plugins/CuraDrive/src/DrivePluginExtension.py create mode 100644 plugins/CuraDrive/src/Settings.py create mode 100644 plugins/CuraDrive/src/UploadBackupJob.py create mode 100644 plugins/CuraDrive/src/__init__.py create mode 100644 plugins/CuraDrive/src/models/BackupListModel.py create mode 100644 plugins/CuraDrive/src/models/__init__.py create mode 100644 plugins/CuraDrive/src/qml/components/ActionButton.qml create mode 100644 plugins/CuraDrive/src/qml/components/ActionCheckBox.qml create mode 100644 plugins/CuraDrive/src/qml/components/ActionToolTip.qml create mode 100644 plugins/CuraDrive/src/qml/components/BackupList.qml create mode 100644 plugins/CuraDrive/src/qml/components/BackupListFooter.qml create mode 100644 plugins/CuraDrive/src/qml/components/BackupListItem.qml create mode 100644 plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml create mode 100644 plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml create mode 100644 plugins/CuraDrive/src/qml/components/Divider.qml create mode 100644 plugins/CuraDrive/src/qml/components/Icon.qml create mode 100644 plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml create mode 100644 plugins/CuraDrive/src/qml/images/avatar_default.png create mode 100644 plugins/CuraDrive/src/qml/images/background.svg create mode 100644 plugins/CuraDrive/src/qml/images/backup.svg create mode 100644 plugins/CuraDrive/src/qml/images/cura.svg create mode 100644 plugins/CuraDrive/src/qml/images/cura_logo.jpg create mode 100644 plugins/CuraDrive/src/qml/images/cura_logo.png create mode 100644 plugins/CuraDrive/src/qml/images/delete.svg create mode 100644 plugins/CuraDrive/src/qml/images/folder.svg create mode 100644 plugins/CuraDrive/src/qml/images/home.svg create mode 100644 plugins/CuraDrive/src/qml/images/icon.png create mode 100644 plugins/CuraDrive/src/qml/images/info.svg create mode 100644 plugins/CuraDrive/src/qml/images/inverted_circle.png create mode 100644 plugins/CuraDrive/src/qml/images/loading.gif create mode 100644 plugins/CuraDrive/src/qml/images/material.svg create mode 100644 plugins/CuraDrive/src/qml/images/plugin.svg create mode 100644 plugins/CuraDrive/src/qml/images/preview_banner.png create mode 100644 plugins/CuraDrive/src/qml/images/printer.svg create mode 100644 plugins/CuraDrive/src/qml/images/profile.svg create mode 100644 plugins/CuraDrive/src/qml/images/restore.svg create mode 100644 plugins/CuraDrive/src/qml/main.qml create mode 100644 plugins/CuraDrive/src/qml/pages/BackupsPage.qml create mode 100644 plugins/CuraDrive/src/qml/pages/WelcomePage.qml diff --git a/.gitignore b/.gitignore index 0a66b6eb33..60b59e6829 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,6 @@ plugins/cura-siemensnx-plugin plugins/CuraBlenderPlugin plugins/CuraCloudPlugin plugins/CuraDrivePlugin -plugins/CuraDrive plugins/CuraLiveScriptingPlugin plugins/CuraOpenSCADPlugin plugins/CuraPrintProfileCreator diff --git a/plugins/CuraDrive/__init__.py b/plugins/CuraDrive/__init__.py new file mode 100644 index 0000000000..6612a5d614 --- /dev/null +++ b/plugins/CuraDrive/__init__.py @@ -0,0 +1,14 @@ +# Copyright (c) 2017 Ultimaker B.V. +import os + +is_testing = os.getenv('ENV_NAME', "development") == "testing" + +# Only load the whole plugin when not running tests as __init__.py is automatically loaded by PyTest +if not is_testing: + from .src.DrivePluginExtension import DrivePluginExtension + + def getMetaData(): + return {} + + def register(app): + return {"extension": DrivePluginExtension(app)} diff --git a/plugins/CuraDrive/plugin.json b/plugins/CuraDrive/plugin.json new file mode 100644 index 0000000000..134cd31a77 --- /dev/null +++ b/plugins/CuraDrive/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "Cura Backups", + "author": "Ultimaker B.V.", + "description": "Backup and restore your configuration.", + "version": "1.2.1", + "api": 5, + "i18n-catalog": "cura_drive" +} \ No newline at end of file diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py new file mode 100644 index 0000000000..a677466838 --- /dev/null +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -0,0 +1,185 @@ +# Copyright (c) 2017 Ultimaker B.V. +import base64 +import hashlib +from datetime import datetime +from tempfile import NamedTemporaryFile +from typing import Optional, List, Dict + +import requests + +from UM.Logger import Logger +from UM.Message import Message +from UM.Signal import Signal + +from .UploadBackupJob import UploadBackupJob +from .Settings import Settings + + +class DriveApiService: + """ + The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling. + """ + + GET_BACKUPS_URL = "{}/backups".format(Settings.DRIVE_API_URL) + PUT_BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL) + DELETE_BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL) + + # Emit signal when restoring backup started or finished. + onRestoringStateChanged = Signal() + + # Emit signal when creating backup started or finished. + onCreatingStateChanged = Signal() + + def __init__(self, cura_api) -> None: + """Create a new instance of the Drive API service and set the cura_api object.""" + self._cura_api = cura_api + + def getBackups(self) -> List[Dict[str, any]]: + """Get all backups from the API.""" + access_token = self._cura_api.account.accessToken + if not access_token: + Logger.log("w", "Could not get access token.") + return [] + + backup_list_request = requests.get(self.GET_BACKUPS_URL, headers={ + "Authorization": "Bearer {}".format(access_token) + }) + if backup_list_request.status_code > 299: + Logger.log("w", "Could not get backups list from remote: %s", backup_list_request.text) + Message(Settings.translatable_messages["get_backups_error"], title = Settings.MESSAGE_TITLE, + lifetime = 10).show() + return [] + return backup_list_request.json()["data"] + + def createBackup(self) -> None: + """Create a backup and upload it to CuraDrive cloud storage.""" + self.onCreatingStateChanged.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.onCreatingStateChanged.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.onCreatingStateChanged.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.finished.connect(self._onUploadFinished) + upload_backup_job.start() + + def _onUploadFinished(self, job: "UploadBackupJob") -> None: + """ + Callback handler for the upload job. + :param job: The executed job. + """ + if job.backup_upload_error_message != "": + # If the job contains an error message we pass it along so the UI can display it. + self.onCreatingStateChanged.emit(is_creating=False, error_message=job.backup_upload_error_message) + else: + self.onCreatingStateChanged.emit(is_creating=False) + + def restoreBackup(self, backup: Dict[str, any]) -> None: + """ + Restore a previously exported backup from cloud storage. + :param backup: A dict containing an entry from the API list response. + """ + self.onRestoringStateChanged.emit(is_restoring=True) + 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() + + download_package = requests.get(download_url, stream=True) + if download_package.status_code != 200: + # 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("data")) + self.onRestoringStateChanged.emit(is_restoring=False) + + def _emitRestoreError(self, error_message: str = Settings.translatable_messages["backup_restore_error_message"]): + """Helper method for emitting a signal when restoring failed.""" + self.onRestoringStateChanged.emit( + is_restoring=False, + error_message=error_message + ) + + @staticmethod + def _verifyMd5Hash(file_path: str, known_hash: str) -> bool: + """ + 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. + """ + 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: + """ + Delete a backup from the server by ID. + :param backup_id: The ID of the backup to delete. + :return: Success bool. + """ + access_token = self._cura_api.account.accessToken + if not access_token: + Logger.log("w", "Could not get access token.") + return False + + delete_backup = requests.delete("{}/{}".format(self.DELETE_BACKUP_URL, backup_id), headers = { + "Authorization": "Bearer {}".format(access_token) + }) + if delete_backup.status_code > 299: + Logger.log("w", "Could not delete backup: %s", delete_backup.text) + return False + return True + + def _requestBackupUpload(self, backup_metadata: Dict[str, any], backup_size: int) -> Optional[str]: + """ + 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. + """ + access_token = self._cura_api.account.accessToken + if not access_token: + Logger.log("w", "Could not get access token.") + return None + + backup_upload_request = requests.put(self.PUT_BACKUP_URL, json={ + "data": { + "backup_size": backup_size, + "metadata": backup_metadata + } + }, headers={ + "Authorization": "Bearer {}".format(access_token) + }) + + if backup_upload_request.status_code > 299: + Logger.log("w", "Could not request backup upload: %s", backup_upload_request.text) + return None + + return backup_upload_request.json()["data"]["upload_url"] diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py new file mode 100644 index 0000000000..556fb187df --- /dev/null +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -0,0 +1,200 @@ +# Copyright (c) 2017 Ultimaker B.V. +import os +from datetime import datetime +from typing import Optional + +from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal + +from UM.Extension import Extension +from UM.Message import Message + +from .Settings import Settings +from .DriveApiService import DriveApiService +from .models.BackupListModel import BackupListModel + + +class DrivePluginExtension(QObject, Extension): + """ + The DivePluginExtension provides functionality to backup and restore your Cura configuration to Ultimaker's cloud. + """ + + # Signal emitted when the list of backups changed. + backupsChanged = pyqtSignal() + + # Signal emitted when restoring has started. Needed to prevent parallel restoring. + restoringStateChanged = pyqtSignal() + + # Signal emitted when creating has started. Needed to prevent parallel creation of backups. + creatingStateChanged = pyqtSignal() + + # Signal emitted when preferences changed (like auto-backup). + preferencesChanged = pyqtSignal() + + DATE_FORMAT = "%d/%m/%Y %H:%M:%S" + + def __init__(self, application): + super(DrivePluginExtension, self).__init__() + + # Re-usable instance of application. + self._application = application + + # Local data caching for the UI. + self._drive_window = None # type: Optional[QObject] + self._backups_list_model = BackupListModel() + self._is_restoring_backup = False + self._is_creating_backup = False + + # Initialize services. + self._preferences = self._application.getPreferences() + self._cura_api = self._application.getCuraAPI() + self._drive_api_service = DriveApiService(self._cura_api) + + # Attach signals. + self._cura_api.account.loginStateChanged.connect(self._onLoginStateChanged) + self._drive_api_service.onRestoringStateChanged.connect(self._onRestoringStateChanged) + self._drive_api_service.onCreatingStateChanged.connect(self._onCreatingStateChanged) + + # Register preferences. + self._preferences.addPreference(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, False) + self._preferences.addPreference(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, datetime.now() + .strftime(self.DATE_FORMAT)) + + # Register menu items. + self._updateMenuItems() + + # Make auto-backup on boot if required. + self._application.engineCreatedSignal.connect(self._autoBackup) + + def showDriveWindow(self) -> None: + """Show the Drive UI popup window.""" + if not self._drive_window: + self._drive_window = self.createDriveWindow() + self.refreshBackups() + self._drive_window.show() + + def createDriveWindow(self) -> Optional["QObject"]: + """ + Create an instance of the Drive UI popup window. + :return: The popup window object. + """ + path = os.path.join(os.path.dirname(__file__), "qml", "main.qml") + return self._application.createQmlComponent(path, {"CuraDrive": self}) + + def _updateMenuItems(self) -> None: + """Update the menu items.""" + self.addMenuItem(Settings.translatable_messages["extension_menu_entry"], self.showDriveWindow) + + def _autoBackup(self) -> None: + """Automatically make a backup on boot if enabled.""" + if self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._lastBackupTooLongAgo(): + self.createBackup() + + def _lastBackupTooLongAgo(self) -> bool: + """Check if the last backup was longer than 1 day ago.""" + current_date = datetime.now() + last_backup_date = self._getLastBackupDate() + date_diff = current_date - last_backup_date + return date_diff.days > 1 + + def _getLastBackupDate(self) -> "datetime": + """Get the last backup date as datetime object.""" + last_backup_date = self._preferences.getValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY) + return datetime.strptime(last_backup_date, self.DATE_FORMAT) + + def _storeBackupDate(self) -> None: + """Store the current date as last backup date.""" + backup_date = datetime.now().strftime(self.DATE_FORMAT) + self._preferences.setValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, backup_date) + + def _onLoginStateChanged(self, logged_in: bool = False) -> None: + """Callback handler for changes in the login state.""" + if logged_in: + self.refreshBackups() + + def _onRestoringStateChanged(self, is_restoring: bool = False, error_message: str = None) -> None: + """Callback handler for changes in the restoring state.""" + self._is_restoring_backup = is_restoring + self.restoringStateChanged.emit() + if error_message: + Message(error_message, title = Settings.MESSAGE_TITLE, lifetime = 5).show() + + def _onCreatingStateChanged(self, is_creating: bool = False, error_message: str = None) -> None: + """Callback handler for changes in the creation state.""" + self._is_creating_backup = is_creating + self.creatingStateChanged.emit() + if error_message: + Message(error_message, title = Settings.MESSAGE_TITLE, lifetime = 5).show() + else: + self._storeBackupDate() + if not is_creating: + # We've finished creating a new backup, to the list has to be updated. + self.refreshBackups() + + @pyqtSlot(bool, name = "toggleAutoBackup") + def toggleAutoBackup(self, enabled: bool) -> None: + """Enable or disable the auto-backup feature.""" + self._preferences.setValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, enabled) + self.preferencesChanged.emit() + + @pyqtProperty(bool, notify = preferencesChanged) + def autoBackupEnabled(self) -> bool: + """Check if auto-backup is enabled or not.""" + return bool(self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY)) + + @pyqtProperty(QObject, notify = backupsChanged) + def backups(self) -> BackupListModel: + """ + Get a list of the backups. + :return: The backups as Qt List Model. + """ + return self._backups_list_model + + @pyqtSlot(name = "refreshBackups") + def refreshBackups(self) -> None: + """ + Forcefully refresh the backups list. + """ + self._backups_list_model.loadBackups(self._drive_api_service.getBackups()) + self.backupsChanged.emit() + + @pyqtProperty(bool, notify = restoringStateChanged) + def isRestoringBackup(self) -> bool: + """ + Get the current restoring state. + :return: Boolean if we are restoring or not. + """ + return self._is_restoring_backup + + @pyqtProperty(bool, notify = creatingStateChanged) + def isCreatingBackup(self) -> bool: + """ + Get the current creating state. + :return: Boolean if we are creating or not. + """ + return self._is_creating_backup + + @pyqtSlot(str, name = "restoreBackup") + def restoreBackup(self, backup_id: str) -> None: + """ + Download and restore a backup by ID. + :param backup_id: The ID of the backup. + """ + index = self._backups_list_model.find("backup_id", backup_id) + backup = self._backups_list_model.getItem(index) + self._drive_api_service.restoreBackup(backup) + + @pyqtSlot(name = "createBackup") + def createBackup(self) -> None: + """ + Create a new backup. + """ + self._drive_api_service.createBackup() + + @pyqtSlot(str, name = "deleteBackup") + def deleteBackup(self, backup_id: str) -> None: + """ + Delete a backup by ID. + :param backup_id: The ID of the backup. + """ + self._drive_api_service.deleteBackup(backup_id) + self.refreshBackups() diff --git a/plugins/CuraDrive/src/Settings.py b/plugins/CuraDrive/src/Settings.py new file mode 100644 index 0000000000..277a976cc7 --- /dev/null +++ b/plugins/CuraDrive/src/Settings.py @@ -0,0 +1,36 @@ +# Copyright (c) 2018 Ultimaker B.V. +from UM import i18nCatalog + + +class Settings: + """ + Keeps the application settings. + """ + UM_CLOUD_API_ROOT = "https://api.ultimaker.com" + DRIVE_API_VERSION = 1 + DRIVE_API_URL = "{}/cura-drive/v{}".format(UM_CLOUD_API_ROOT, str(DRIVE_API_VERSION)) + + AUTO_BACKUP_ENABLED_PREFERENCE_KEY = "cura_drive/auto_backup_enabled" + AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date" + + I18N_CATALOG_ID = "cura_drive" + I18N_CATALOG = i18nCatalog(I18N_CATALOG_ID) + + MESSAGE_TITLE = I18N_CATALOG.i18nc("@info:title", "Backups"), + + # Translatable messages for the entire plugin. + translatable_messages = { + + # Menu items. + "extension_menu_entry": I18N_CATALOG.i18nc("@item:inmenu", "Manage backups"), + + # Notification messages. + "backup_failed": I18N_CATALOG.i18nc("@info:backup_status", "There was an error while creating your backup."), + "uploading_backup": I18N_CATALOG.i18nc("@info:backup_status", "Uploading your backup..."), + "uploading_backup_success": I18N_CATALOG.i18nc("@info:backup_status", "Your backup has finished uploading."), + "uploading_backup_error": I18N_CATALOG.i18nc("@info:backup_status", + "There was an error while uploading your backup."), + "get_backups_error": I18N_CATALOG.i18nc("@info:backup_status", "There was an error listing your backups."), + "backup_restore_error_message": I18N_CATALOG.i18nc("@info:backup_status", + "There was an error trying to restore your backup.") + } diff --git a/plugins/CuraDrive/src/UploadBackupJob.py b/plugins/CuraDrive/src/UploadBackupJob.py new file mode 100644 index 0000000000..039e6d1a09 --- /dev/null +++ b/plugins/CuraDrive/src/UploadBackupJob.py @@ -0,0 +1,39 @@ +# Copyright (c) 2018 Ultimaker B.V. +import requests + +from UM.Job import Job +from UM.Logger import Logger +from UM.Message import Message + +from .Settings import Settings + + +class UploadBackupJob(Job): + """ + 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): + 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): + Message(Settings.translatable_messages["uploading_backup"], title = Settings.MESSAGE_TITLE, + lifetime = 10).show() + + backup_upload = requests.put(self._signed_upload_url, data = self._backup_zip) + if backup_upload.status_code not in (200, 201): + self.backup_upload_error_message = backup_upload.text + Logger.log("w", "Could not upload backup file: %s", backup_upload.text) + Message(Settings.translatable_messages["uploading_backup_error"], title = Settings.MESSAGE_TITLE, + lifetime = 10).show() + else: + self._upload_success = True + Message(Settings.translatable_messages["uploading_backup_success"], title = Settings.MESSAGE_TITLE, + lifetime = 10).show() + + self.finished.emit(self) diff --git a/plugins/CuraDrive/src/__init__.py b/plugins/CuraDrive/src/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/CuraDrive/src/models/BackupListModel.py b/plugins/CuraDrive/src/models/BackupListModel.py new file mode 100644 index 0000000000..9567b3d255 --- /dev/null +++ b/plugins/CuraDrive/src/models/BackupListModel.py @@ -0,0 +1,38 @@ +# Copyright (c) 2018 Ultimaker B.V. +from typing import List, Dict + +from UM.Qt.ListModel import ListModel + +from PyQt5.QtCore import Qt + + +class BackupListModel(ListModel): + """ + The BackupListModel transforms the backups data that came from the server so it can be served to the Qt UI. + """ + + def __init__(self, parent=None): + super().__init__(parent) + self.addRoleName(Qt.UserRole + 1, "backup_id") + self.addRoleName(Qt.UserRole + 2, "download_url") + self.addRoleName(Qt.UserRole + 3, "generated_time") + self.addRoleName(Qt.UserRole + 4, "md5_hash") + self.addRoleName(Qt.UserRole + 5, "data") + + def loadBackups(self, data: List[Dict[str, any]]) -> None: + """ + Populate the model with server data. + :param data: + """ + items = [] + for backup in data: + # We do this loop because we only want to append these specific fields. + # Without this, ListModel will break. + items.append({ + "backup_id": backup["backup_id"], + "download_url": backup["download_url"], + "generated_time": backup["generated_time"], + "md5_hash": backup["md5_hash"], + "data": backup["metadata"] + }) + self.setItems(items) diff --git a/plugins/CuraDrive/src/models/__init__.py b/plugins/CuraDrive/src/models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/CuraDrive/src/qml/components/ActionButton.qml b/plugins/CuraDrive/src/qml/components/ActionButton.qml new file mode 100644 index 0000000000..843079ed88 --- /dev/null +++ b/plugins/CuraDrive/src/qml/components/ActionButton.qml @@ -0,0 +1,67 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 + +import UM 1.1 as UM + +Button +{ + id: button + property alias cursorShape: mouseArea.cursorShape + property var iconSource: "" + property var busy: false + property var color: UM.Theme.getColor("primary") + property var hoverColor: UM.Theme.getColor("primary_hover") + property var disabledColor: color + property var textColor: UM.Theme.getColor("button_text") + property var textHoverColor: UM.Theme.getColor("button_text_hover") + property var textDisabledColor: textColor + property var textFont: UM.Theme.getFont("action_button") + + contentItem: RowLayout + { + Icon + { + id: buttonIcon + iconSource: button.iconSource + width: 16 * screenScaleFactor + color: button.hovered ? button.textHoverColor : button.textColor + visible: button.iconSource != "" && !loader.visible + } + + Icon + { + id: loader + iconSource: "../images/loading.gif" + width: 16 * screenScaleFactor + color: button.hovered ? button.textHoverColor : button.textColor + visible: button.busy + animated: true + } + + Label + { + id: buttonText + text: button.text + color: button.enabled ? (button.hovered ? button.textHoverColor : button.textColor): button.textDisabledColor + font: button.textFont + visible: button.text != "" + renderType: Text.NativeRendering + } + } + + background: Rectangle + { + color: button.enabled ? (button.hovered ? button.hoverColor : button.color) : button.disabledColor + } + + MouseArea + { + id: mouseArea + anchors.fill: parent + onPressed: mouse.accepted = false + hoverEnabled: true + cursorShape: button.enabled ? (hovered ? Qt.PointingHandCursor : Qt.ArrowCursor) : Qt.ForbiddenCursor + } +} diff --git a/plugins/CuraDrive/src/qml/components/ActionCheckBox.qml b/plugins/CuraDrive/src/qml/components/ActionCheckBox.qml new file mode 100644 index 0000000000..71f5e6035d --- /dev/null +++ b/plugins/CuraDrive/src/qml/components/ActionCheckBox.qml @@ -0,0 +1,49 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 + +import UM 1.3 as UM + +CheckBox +{ + id: checkbox + hoverEnabled: true + + property var label: "" + + indicator: Rectangle { + implicitWidth: 30 * screenScaleFactor + implicitHeight: 30 * screenScaleFactor + x: 0 + y: Math.round(parent.height / 2 - height / 2) + color: UM.Theme.getColor("sidebar") + border.color: UM.Theme.getColor("text") + + Rectangle { + width: 14 * screenScaleFactor + height: 14 * screenScaleFactor + x: 8 * screenScaleFactor + y: 8 * screenScaleFactor + color: UM.Theme.getColor("primary") + visible: checkbox.checked + } + } + + contentItem: Label { + anchors + { + left: checkbox.indicator.right + leftMargin: 5 * screenScaleFactor + } + text: catalog.i18nc("@checkbox:description", "Auto Backup") + color: UM.Theme.getColor("text") + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + } + + ActionToolTip + { + text: checkbox.label + } +} diff --git a/plugins/CuraDrive/src/qml/components/ActionToolTip.qml b/plugins/CuraDrive/src/qml/components/ActionToolTip.qml new file mode 100644 index 0000000000..93b92bc2df --- /dev/null +++ b/plugins/CuraDrive/src/qml/components/ActionToolTip.qml @@ -0,0 +1,29 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 + +import UM 1.1 as UM + +ToolTip +{ + id: tooltip + visible: parent.hovered + opacity: 0.9 + delay: 500 + + background: Rectangle + { + color: UM.Theme.getColor("sidebar") + border.color: UM.Theme.getColor("primary") + border.width: 1 * screenScaleFactor + } + + contentItem: Label + { + text: tooltip.text + color: UM.Theme.getColor("text") + font: UM.Theme.getFont("very_small") + renderType: Text.NativeRendering + } +} diff --git a/plugins/CuraDrive/src/qml/components/BackupList.qml b/plugins/CuraDrive/src/qml/components/BackupList.qml new file mode 100644 index 0000000000..231f25afc8 --- /dev/null +++ b/plugins/CuraDrive/src/qml/components/BackupList.qml @@ -0,0 +1,31 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 + +import UM 1.1 as UM + +ListView +{ + id: backupList + width: parent.width + clip: true + delegate: Item + { + width: parent.width + height: childrenRect.height + + BackupListItem + { + id: backupListItem + width: parent.width + } + + Divider + { + width: parent.width + anchors.top: backupListItem.bottom + } + } + ScrollBar.vertical: RightSideScrollBar {} +} diff --git a/plugins/CuraDrive/src/qml/components/BackupListFooter.qml b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml new file mode 100644 index 0000000000..80f47d6cba --- /dev/null +++ b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml @@ -0,0 +1,42 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 + +import UM 1.3 as UM + +import "../components" + +RowLayout +{ + id: backupListFooter + width: parent.width + property bool showInfoButton: false + + ActionButton + { + id: infoButton + text: catalog.i18nc("@button", "Want more?") + iconSource: "../images/info.svg" + onClicked: Qt.openUrlExternally("https://goo.gl/forms/QACEP8pP3RV60QYG2") + visible: backupListFooter.showInfoButton + } + + ActionButton + { + id: createBackupButton + text: catalog.i18nc("@button", "Backup Now") + iconSource: "../images/backup.svg" + enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup + onClicked: CuraDrive.createBackup() + busy: CuraDrive.isCreatingBackup + } + + ActionCheckBox + { + id: autoBackupEnabled + checked: CuraDrive.autoBackupEnabled + onClicked: CuraDrive.toggleAutoBackup(autoBackupEnabled.checked) + label: catalog.i18nc("@checkbox:description", "Automatically create a backup each day that Cura is started.") + } +} diff --git a/plugins/CuraDrive/src/qml/components/BackupListItem.qml b/plugins/CuraDrive/src/qml/components/BackupListItem.qml new file mode 100644 index 0000000000..abe9a1acf9 --- /dev/null +++ b/plugins/CuraDrive/src/qml/components/BackupListItem.qml @@ -0,0 +1,112 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.1 + +import UM 1.1 as UM + +Item +{ + id: backupListItem + width: parent.width + height: showDetails ? dataRow.height + backupDetails.height : dataRow.height + property bool showDetails: false + + // Backup details toggle animation. + Behavior on height + { + PropertyAnimation + { + duration: 70 + } + } + + RowLayout + { + id: dataRow + spacing: UM.Theme.getSize("default_margin").width * 2 + width: parent.width + height: 50 * screenScaleFactor + + ActionButton + { + color: "transparent" + hoverColor: "transparent" + textColor: UM.Theme.getColor("text") + textHoverColor: UM.Theme.getColor("primary") + iconSource: "../images/info.svg" + onClicked: backupListItem.showDetails = !backupListItem.showDetails + } + + Label + { + text: new Date(model["generated_time"]).toLocaleString(UM.Preferences.getValue("general/language")) + color: UM.Theme.getColor("text") + elide: Text.ElideRight + Layout.minimumWidth: 100 * screenScaleFactor + Layout.maximumWidth: 500 * screenScaleFactor + Layout.fillWidth: true + renderType: Text.NativeRendering + } + + Label + { + text: model["data"]["description"] + color: UM.Theme.getColor("text") + elide: Text.ElideRight + Layout.minimumWidth: 100 * screenScaleFactor + Layout.maximumWidth: 500 * screenScaleFactor + Layout.fillWidth: true + renderType: Text.NativeRendering + } + + ActionButton + { + text: catalog.i18nc("@button", "Restore") + color: "transparent" + hoverColor: "transparent" + textColor: UM.Theme.getColor("text") + textHoverColor: UM.Theme.getColor("text_link") + enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup + onClicked: confirmRestoreDialog.visible = true + } + + ActionButton + { + color: "transparent" + hoverColor: "transparent" + textColor: UM.Theme.getColor("setting_validation_error") + textHoverColor: UM.Theme.getColor("setting_validation_error") + iconSource: "../images/delete.svg" + onClicked: confirmDeleteDialog.visible = true + } + } + + BackupListItemDetails + { + id: backupDetails + backupDetailsData: model + width: parent.width + visible: parent.showDetails + anchors.top: dataRow.bottom + } + + MessageDialog + { + id: confirmDeleteDialog + title: catalog.i18nc("@dialog:title", "Delete Backup") + text: catalog.i18nc("@dialog:info", "Are you sure you want to delete this backup? This cannot be undone.") + standardButtons: StandardButton.Yes | StandardButton.No + onYes: CuraDrive.deleteBackup(model["backup_id"]) + } + + MessageDialog + { + id: confirmRestoreDialog + title: catalog.i18nc("@dialog:title", "Restore Backup") + text: catalog.i18nc("@dialog:info", "You will need to restart Cura before your backup is restored. Do you want to close Cura now?") + standardButtons: StandardButton.Yes | StandardButton.No + onYes: CuraDrive.restoreBackup(model["backup_id"]) + } +} diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml new file mode 100644 index 0000000000..74d4c5ab57 --- /dev/null +++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml @@ -0,0 +1,61 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 + +import UM 1.1 as UM + +ColumnLayout +{ + id: backupDetails + width: parent.width + spacing: 10 * screenScaleFactor + property var backupDetailsData + + // Cura version + BackupListItemDetailsRow + { + iconSource: "../images/cura.svg" + label: catalog.i18nc("@backuplist:label", "Cura Version") + value: backupDetailsData["data"]["cura_release"] + } + + // Machine count. + BackupListItemDetailsRow + { + iconSource: "../images/printer.svg" + label: catalog.i18nc("@backuplist:label", "Machines") + value: backupDetailsData["data"]["machine_count"] + } + + // Meterial count. + BackupListItemDetailsRow + { + iconSource: "../images/material.svg" + label: catalog.i18nc("@backuplist:label", "Materials") + value: backupDetailsData["data"]["material_count"] + } + + // Meterial count. + BackupListItemDetailsRow + { + iconSource: "../images/profile.svg" + label: catalog.i18nc("@backuplist:label", "Profiles") + value: backupDetailsData["data"]["profile_count"] + } + + // Meterial count. + BackupListItemDetailsRow + { + iconSource: "../images/plugin.svg" + label: catalog.i18nc("@backuplist:label", "Plugins") + value: backupDetailsData["data"]["plugin_count"] + } + + // Spacer. + Item + { + width: parent.width + height: 10 * screenScaleFactor + } +} diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml new file mode 100644 index 0000000000..dad1674fe7 --- /dev/null +++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml @@ -0,0 +1,52 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 + +import UM 1.3 as UM + +RowLayout +{ + id: detailsRow + width: parent.width + height: 40 * screenScaleFactor + + property var iconSource + property var label + property var value + + // Spacing. + Item + { + width: 40 * screenScaleFactor + } + + Icon + { + width: 18 * screenScaleFactor + iconSource: detailsRow.iconSource + color: UM.Theme.getColor("text") + } + + Label + { + text: detailsRow.label + color: UM.Theme.getColor("text") + elide: Text.ElideRight + Layout.minimumWidth: 50 * screenScaleFactor + Layout.maximumWidth: 100 * screenScaleFactor + Layout.fillWidth: true + renderType: Text.NativeRendering + } + + Label + { + text: detailsRow.value + color: UM.Theme.getColor("text") + elide: Text.ElideRight + Layout.minimumWidth: 50 * screenScaleFactor + Layout.maximumWidth: 100 * screenScaleFactor + Layout.fillWidth: true + renderType: Text.NativeRendering + } +} diff --git a/plugins/CuraDrive/src/qml/components/Divider.qml b/plugins/CuraDrive/src/qml/components/Divider.qml new file mode 100644 index 0000000000..bba2f2f29c --- /dev/null +++ b/plugins/CuraDrive/src/qml/components/Divider.qml @@ -0,0 +1,11 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 + +import UM 1.3 as UM + +Rectangle +{ + id: divider + color: UM.Theme.getColor("lining") + height: UM.Theme.getSize("default_lining").height +} diff --git a/plugins/CuraDrive/src/qml/components/Icon.qml b/plugins/CuraDrive/src/qml/components/Icon.qml new file mode 100644 index 0000000000..3cb822bf82 --- /dev/null +++ b/plugins/CuraDrive/src/qml/components/Icon.qml @@ -0,0 +1,56 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtGraphicalEffects 1.0 + +Item +{ + id: icon + width: parent.height + height: width + property var color: "transparent" + property var iconSource + property bool animated: false + + Image + { + id: iconImage + width: parent.height + height: width + smooth: true + source: icon.iconSource + sourceSize.width: width + sourceSize.height: height + antialiasing: true + visible: !icon.animated + } + + AnimatedImage + { + id: animatedIconImage + width: parent.height + height: width + smooth: true + antialiasing: true + source: "../images/loading.gif" + visible: icon.animated + } + + ColorOverlay + { + anchors.fill: iconImage + source: iconImage + color: icon.color + antialiasing: true + visible: !icon.animated + } + + ColorOverlay + { + anchors.fill: animatedIconImage + source: animatedIconImage + color: icon.color + antialiasing: true + visible: icon.animated + } +} diff --git a/plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml b/plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml new file mode 100644 index 0000000000..5ac5df15ff --- /dev/null +++ b/plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml @@ -0,0 +1,13 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 + +ScrollBar +{ + active: true + size: parent.height + anchors.top: parent.top + anchors.right: parent.right + anchors.bottom: parent.bottom +} diff --git a/plugins/CuraDrive/src/qml/images/avatar_default.png b/plugins/CuraDrive/src/qml/images/avatar_default.png new file mode 100644 index 0000000000000000000000000000000000000000..0c306680f721dba0de73fb7be90fe5c548608692 GIT binary patch literal 3115 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 + + + Polygon 18 + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/backup.svg b/plugins/CuraDrive/src/qml/images/backup.svg new file mode 100644 index 0000000000..51f6be4cba --- /dev/null +++ b/plugins/CuraDrive/src/qml/images/backup.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/cura.svg b/plugins/CuraDrive/src/qml/images/cura.svg new file mode 100644 index 0000000000..6b1b6c0c79 --- /dev/null +++ b/plugins/CuraDrive/src/qml/images/cura.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/cura_logo.jpg b/plugins/CuraDrive/src/qml/images/cura_logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..621c03f035cddb3c9337099c25bccf19a5866968 GIT binary patch literal 19308 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 V{_(E9+wT8(SKsZ~f4r-A{|^$iZYBT# literal 0 HcmV?d00001 diff --git a/plugins/CuraDrive/src/qml/images/cura_logo.png b/plugins/CuraDrive/src/qml/images/cura_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f846f2a0f0934f4843c3279ed741c149c77b2e1e GIT binary patch literal 13258 zcmeHu;4V*)BSRu^XaTvbLN?uz4uJ)2XzHpY!LRVSFdoD6lJwuy+WyZdCve~ygc*u(2ZXX z6n8BJ=~tEGlzXpU@kS`gN`3M{In2g&`K-Ng?48+YOUK6%Q!&j{8UO%f(B`2OjGe6E z=pFl$ws^S=Htqi&Eu+nt`f5zkAyuM|<9+lUC7ObBbhAJ)l>)Q49oq{+g+>xugU({Y z!PhW}n#xpMueQi{{k(Xgq^qIfb|t>U@SfRMr>zd5Z{lC^?1wlPJ#bg}3RdVQbU~*? z`TxiNdl#7g_x2?FIG|)>n5~LZ=;-U{r$9xWcyi_~xxLz49$qf=d`tG6X zIStGm9v<#dC-s0$b#qRMgsHFQp^FeRDg%|8>pO3^vnue#Z%ltH62Zo)abYu)lLUvz zhB9bh6s{bH^OfNC;bPHuwsUnckKTeN_+-^P3q;Rl_BXG`4v(a2lIvSvy%OYFLvt*Z39TCgr_a$F=Mp8M>*tj%kWC~v)94VFN5 z1qXZl?GEiRi;f`o`qcv{-e76%eFdNVsQd3-SZTWP)QI7xn+|JN!Q5xxAATLlA*Y)u z8pe!KMjd{a`|_;R{4@b4QtZ`tcX#VkMd;L<4<)IL{^KurzKG;6p8r3E0*#`BRc1%Z4{vHItk9179tO)8Rr)A_% z^@S+;h%bJ(TWOfDQ?c0pN|yDfn~1yLxnevG5wggjcb=tILHvFOzr@qF%`oJMOSJ+{ zvx|e^`kBeW*=VvqpXlg0-=GhY0MM;vt$z~ATSwB$WVr;veV2t$H*iB= z9Qz*R+wd#7o~OW+AvB*R9cy`8kkDbTmzQzWzP^*cskOBg%~EfKP~j8Ris_&aPFLcN zUi#uzboWne?!(MqzJEh8^?ZVidSPC0n3@71p>*8%Z)6$;fFKy%0QQAvykQl%ipl86D zp6LP5w;D0G`6p$=CEh&yyO5|8W$0;Ch)sve$3xGmuBW@dNk;N!XYs*xus)kGap#&jjLnRa>E$AcC6{{L0hB2p z(d5UzFVqZCgKNN3ck+4~>(E_#s08-L_qzM?y77I))HmP8By}+g-%woX6(1QP(8wUT z8_$T)hmPi*wF8sEf`U=ZECL~5A8}a?!?V`I{7*d4ZwX7Lx}uJP?o@<9_<=V#dT;p< zkU)+;E_S^HTK+lpxLcUa<4?$|o6Zp!vVcu&0D#Dwt;O`Ed(S5i;&3`TIyU^WPfJ>o zMJ{QDzv6}BI!lIRtO*usuLGIrp1YIl0XL^Co4C=jrpf69aHA(>N7tQyTpu5(GS~d5%UqzuqpMu?ZR; zn5YT+op(z-ohE~l$Y}oqN`(%5@MOS)F7%$_xUvh1wzo$U0txB7mObYxX2K4(z=BQ| zLm$e;sDrk?iu{re_7*`Ura)P?pJy5L-*KN1(;Z=9a~CixFsrkfDi}Z}`u3eqNJx

{tidI^DPfL*th*F*+_ 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 literal 0 HcmV?d00001 diff --git a/plugins/CuraDrive/src/qml/images/delete.svg b/plugins/CuraDrive/src/qml/images/delete.svg new file mode 100644 index 0000000000..2f6190ad43 --- /dev/null +++ b/plugins/CuraDrive/src/qml/images/delete.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/folder.svg b/plugins/CuraDrive/src/qml/images/folder.svg new file mode 100644 index 0000000000..f66f83a888 --- /dev/null +++ b/plugins/CuraDrive/src/qml/images/folder.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/home.svg b/plugins/CuraDrive/src/qml/images/home.svg new file mode 100644 index 0000000000..9d0e4d802c --- /dev/null +++ b/plugins/CuraDrive/src/qml/images/home.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/icon.png b/plugins/CuraDrive/src/qml/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3f75491786dcc0939175ffbab5e791e27eaefc2c GIT binary patch literal 21924 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�>#6>;@A&7;Ub9*u@xRReL>D;wp-rSVGg8b(Ftu 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 d{(EOoFjyv&`}RG)N<`qZ!PU# + + + \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/inverted_circle.png b/plugins/CuraDrive/src/qml/images/inverted_circle.png new file mode 100644 index 0000000000000000000000000000000000000000..3612b37d4d38cd7b1218be42c943932a79f7b465 GIT binary patch literal 1608 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 F;r|kGDY5_n literal 0 HcmV?d00001 diff --git a/plugins/CuraDrive/src/qml/images/material.svg b/plugins/CuraDrive/src/qml/images/material.svg new file mode 100644 index 0000000000..eac724e471 --- /dev/null +++ b/plugins/CuraDrive/src/qml/images/material.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/plugin.svg b/plugins/CuraDrive/src/qml/images/plugin.svg new file mode 100644 index 0000000000..674eb99a54 --- /dev/null +++ b/plugins/CuraDrive/src/qml/images/plugin.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/preview_banner.png b/plugins/CuraDrive/src/qml/images/preview_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..414019531beed2e503e05cc394fc7e6de246830a GIT binary patch literal 8324 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 E2O#6GiU0rr literal 0 HcmV?d00001 diff --git a/plugins/CuraDrive/src/qml/images/printer.svg b/plugins/CuraDrive/src/qml/images/printer.svg new file mode 100644 index 0000000000..f7dc83987d --- /dev/null +++ b/plugins/CuraDrive/src/qml/images/printer.svg @@ -0,0 +1,14 @@ + + + + icn_singlePrinter + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/profile.svg b/plugins/CuraDrive/src/qml/images/profile.svg new file mode 100644 index 0000000000..ec2130f3d6 --- /dev/null +++ b/plugins/CuraDrive/src/qml/images/profile.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/restore.svg b/plugins/CuraDrive/src/qml/images/restore.svg new file mode 100644 index 0000000000..803215eada --- /dev/null +++ b/plugins/CuraDrive/src/qml/images/restore.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/main.qml b/plugins/CuraDrive/src/qml/main.qml new file mode 100644 index 0000000000..4a2219cf1f --- /dev/null +++ b/plugins/CuraDrive/src/qml/main.qml @@ -0,0 +1,42 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Window 2.2 + +import UM 1.3 as UM +import Cura 1.1 as Cura + +import "components" +import "pages" + +Window +{ + id: curaDriveDialog + minimumWidth: Math.round(UM.Theme.getSize("modal_window_minimum").width) + minimumHeight: Math.round(UM.Theme.getSize("modal_window_minimum").height) + maximumWidth: minimumWidth * 1.2 + maximumHeight: minimumHeight * 1.2 + width: minimumWidth + height: minimumHeight + color: UM.Theme.getColor("sidebar") + title: catalog.i18nc("@title:window", "Cura Backups") + + // Globally available. + UM.I18nCatalog + { + id: catalog + name: "cura_drive" + } + + WelcomePage + { + id: welcomePage + visible: !Cura.API.account.isLoggedIn + } + + BackupsPage + { + id: backupsPage + visible: Cura.API.account.isLoggedIn + } +} diff --git a/plugins/CuraDrive/src/qml/pages/BackupsPage.qml b/plugins/CuraDrive/src/qml/pages/BackupsPage.qml new file mode 100644 index 0000000000..88ce766383 --- /dev/null +++ b/plugins/CuraDrive/src/qml/pages/BackupsPage.qml @@ -0,0 +1,73 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 + +import UM 1.3 as UM +import Cura 1.1 as Cura + +import "../components" + +Item +{ + id: backupsPage + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width * 3 + + ColumnLayout + { + spacing: UM.Theme.getSize("default_margin").height * 2 + width: parent.width + anchors.fill: parent + + Label + { + id: backupTitle + text: catalog.i18nc("@title", "My Backups") + font: UM.Theme.getFont("large") + color: UM.Theme.getColor("text") + Layout.fillWidth: true + renderType: Text.NativeRendering + } + + Label + { + text: catalog.i18nc("@empty_state", + "You don't have any backups currently. Use the 'Backup Now' button to create one.") + width: parent.width + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + wrapMode: Label.WordWrap + visible: backupList.count == 0 + Layout.fillWidth: true + Layout.fillHeight: true + renderType: Text.NativeRendering + } + + BackupList + { + id: backupList + model: CuraDrive.backups + Layout.fillWidth: true + Layout.fillHeight: true + } + + Label + { + text: catalog.i18nc("@backup_limit_info", + "During the preview phase, you'll be limited to 5 visible backups. Remove a backup to see older ones.") + width: parent.width + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + wrapMode: Label.WordWrap + visible: backupList.count > 4 + renderType: Text.NativeRendering + } + + BackupListFooter + { + id: backupListFooter + showInfoButton: backupList.count > 4 + } + } +} diff --git a/plugins/CuraDrive/src/qml/pages/WelcomePage.qml b/plugins/CuraDrive/src/qml/pages/WelcomePage.qml new file mode 100644 index 0000000000..882656dc4a --- /dev/null +++ b/plugins/CuraDrive/src/qml/pages/WelcomePage.qml @@ -0,0 +1,48 @@ +// Copyright (c) 2018 Ultimaker B.V. +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Window 2.2 + +import UM 1.3 as UM +import Cura 1.1 as Cura + +import "../components" + +Column +{ + id: welcomePage + spacing: UM.Theme.getSize("wide_margin").height + width: parent.width + topPadding: 150 * screenScaleFactor + + Image + { + id: profileImage + fillMode: Image.PreserveAspectFit + source: "../images/icon.png" + anchors.horizontalCenter: parent.horizontalCenter + width: Math.round(parent.width / 4) + } + + Label + { + id: welcomeTextLabel + text: catalog.i18nc("@description", "Backup and synchronize your Cura settings.") + width: Math.round(parent.width / 2) + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter + wrapMode: Label.WordWrap + renderType: Text.NativeRendering + } + + ActionButton + { + id: loginButton + onClicked: Cura.API.account.login() + text: catalog.i18nc("@button", "Sign In") + anchors.horizontalCenter: parent.horizontalCenter + } +} From badf2962bc90c910fc10b09ecb309b10604d5090 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 11 Dec 2018 10:34:00 +0100 Subject: [PATCH 02/51] Change CuraDrive version to 1.2.0 CURA-6005 Because the previously version is 1.1.1 --- plugins/CuraDrive/plugin.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/CuraDrive/plugin.json b/plugins/CuraDrive/plugin.json index 134cd31a77..44bf955f41 100644 --- a/plugins/CuraDrive/plugin.json +++ b/plugins/CuraDrive/plugin.json @@ -2,7 +2,7 @@ "name": "Cura Backups", "author": "Ultimaker B.V.", "description": "Backup and restore your configuration.", - "version": "1.2.1", + "version": "1.2.0", "api": 5, "i18n-catalog": "cura_drive" -} \ No newline at end of file +} From 9618e8d4e096aaaeaf6e510a00b7436bd4cc852f Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 11 Dec 2018 10:34:49 +0100 Subject: [PATCH 03/51] Add CuraDrive 1.2.0 to bundled_packages CURA-6005 --- resources/bundled_packages/cura.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/resources/bundled_packages/cura.json b/resources/bundled_packages/cura.json index d8a7df2478..33c6304fc5 100644 --- a/resources/bundled_packages/cura.json +++ b/resources/bundled_packages/cura.json @@ -50,6 +50,23 @@ } } }, + "CuraDrive": { + "package_info": { + "package_id": "CuraDrive", + "package_type": "plugin", + "display_name": "Cura Backups", + "description": "Backup and restore your configuration.", + "package_version": "1.2.0", + "sdk_version": 5, + "website": "https://ultimaker.com", + "author": { + "author_id": "UltimakerPackages", + "display_name": "Ultimaker B.V.", + "email": "plugins@ultimaker.com", + "website": "https://ultimaker.com" + } + } + }, "CuraEngineBackend": { "package_info": { "package_id": "CuraEngineBackend", From 76b9fa79986d2622eca7a7316ba65a05aaaa95bd Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 11 Dec 2018 11:53:12 +0100 Subject: [PATCH 04/51] Use "cura" as i18n catalog for CuraDrive CURA-6005 --- plugins/CuraDrive/plugin.json | 2 +- plugins/CuraDrive/src/Settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/CuraDrive/plugin.json b/plugins/CuraDrive/plugin.json index 44bf955f41..6cf1fa273c 100644 --- a/plugins/CuraDrive/plugin.json +++ b/plugins/CuraDrive/plugin.json @@ -4,5 +4,5 @@ "description": "Backup and restore your configuration.", "version": "1.2.0", "api": 5, - "i18n-catalog": "cura_drive" + "i18n-catalog": "cura" } diff --git a/plugins/CuraDrive/src/Settings.py b/plugins/CuraDrive/src/Settings.py index 277a976cc7..f10a7d3bf7 100644 --- a/plugins/CuraDrive/src/Settings.py +++ b/plugins/CuraDrive/src/Settings.py @@ -13,7 +13,7 @@ class Settings: AUTO_BACKUP_ENABLED_PREFERENCE_KEY = "cura_drive/auto_backup_enabled" AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date" - I18N_CATALOG_ID = "cura_drive" + I18N_CATALOG_ID = "cura" I18N_CATALOG = i18nCatalog(I18N_CATALOG_ID) MESSAGE_TITLE = I18N_CATALOG.i18nc("@info:title", "Backups"), From 8e4ad23da104ead8ee5ce3658e7132d3d8e9eaec Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 11 Dec 2018 12:12:32 +0100 Subject: [PATCH 05/51] Add CuraConstants CURA-6005 Put constant values into a separate file CuraConstants.py --- cura/CuraConstants.py | 54 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 cura/CuraConstants.py diff --git a/cura/CuraConstants.py b/cura/CuraConstants.py new file mode 100644 index 0000000000..331937a0c2 --- /dev/null +++ b/cura/CuraConstants.py @@ -0,0 +1,54 @@ +# +# This file contains all constant values in Cura +# + +# ------------- +# Cura Versions +# ------------- +DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura" +DEFAULT_CURA_VERSION = "master" +DEFAULT_CURA_BUILD_TYPE = "" +DEFAULT_CURA_DEBUG_MODE = False +DEFAULT_CURA_SDK_VERSION = "5.0.0" + +try: + from cura.CuraVersion import CuraAppDisplayName # type: ignore +except ImportError: + CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME + +try: + from cura.CuraVersion import CuraVersion # type: ignore +except ImportError: + CuraVersion = DEFAULT_CURA_VERSION # [CodeStyle: Reflecting imported value] + +try: + from cura.CuraVersion import CuraBuildType # type: ignore +except ImportError: + CuraBuildType = DEFAULT_CURA_BUILD_TYPE + +try: + from cura.CuraVersion import CuraDebugMode # type: ignore +except ImportError: + CuraDebugMode = DEFAULT_CURA_DEBUG_MODE + +try: + from cura.CuraVersion import CuraSDKVersion # type: ignore +except ImportError: + CuraSDKVersion = DEFAULT_CURA_SDK_VERSION + + +# --------- +# Cloud API +# --------- +DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str +DEFAULT_CLOUD_API_VERSION = 1 # type: int + +try: + from cura.CuraVersion import CuraCloudAPIRoot # type: ignore +except ImportError: + CuraCloudAPIRoot = DEFAULT_CLOUD_API_ROOT + +try: + from cura.CuraVersion import CuraCloudAPIVersion # type: ignore +except ImportError: + CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION From 2275e5c71f2ddd2181bdb54bb7c775165f14f1a6 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 11 Dec 2018 12:13:34 +0100 Subject: [PATCH 06/51] Refactor code to use constants in CuraConstants CURA-6005 --- cura/CuraApplication.py | 23 +++++++----------- plugins/CuraDrive/src/Settings.py | 9 +++---- plugins/Toolbox/src/Toolbox.py | 40 ++++--------------------------- 3 files changed, 17 insertions(+), 55 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index dfdb50515f..95b94c01c7 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -115,6 +115,8 @@ from cura.ObjectsModel import ObjectsModel from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage +from cura import CuraConstants + from UM.FlameProfiler import pyqtSlot from UM.Decorators import override @@ -127,15 +129,6 @@ if TYPE_CHECKING: numpy.seterr(all = "ignore") -try: - from cura.CuraVersion import CuraAppDisplayName, CuraVersion, CuraBuildType, CuraDebugMode, CuraSDKVersion # type: ignore -except ImportError: - CuraAppDisplayName = "Ultimaker Cura" - CuraVersion = "master" # [CodeStyle: Reflecting imported value] - CuraBuildType = "" - CuraDebugMode = False - CuraSDKVersion = "5.0.0" - class CuraApplication(QtApplication): # SettingVersion represents the set of settings available in the machine/extruder definitions. @@ -162,11 +155,11 @@ class CuraApplication(QtApplication): def __init__(self, *args, **kwargs): super().__init__(name = "cura", - app_display_name = CuraAppDisplayName, - version = CuraVersion, - api_version = CuraSDKVersion, - buildtype = CuraBuildType, - is_debug_mode = CuraDebugMode, + app_display_name = CuraConstants.CuraAppDisplayName, + version = CuraConstants.CuraVersion, + api_version = CuraConstants.CuraSDKVersion, + buildtype = CuraConstants.CuraBuildType, + is_debug_mode = CuraConstants.CuraDebugMode, tray_icon_name = "cura-icon-32.png", **kwargs) @@ -937,7 +930,7 @@ class CuraApplication(QtApplication): engine.rootContext().setContextProperty("CuraApplication", self) engine.rootContext().setContextProperty("PrintInformation", self._print_information) engine.rootContext().setContextProperty("CuraActions", self._cura_actions) - engine.rootContext().setContextProperty("CuraSDKVersion", CuraSDKVersion) + engine.rootContext().setContextProperty("CuraSDKVersion", CuraConstants.CuraSDKVersion) qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type") diff --git a/plugins/CuraDrive/src/Settings.py b/plugins/CuraDrive/src/Settings.py index f10a7d3bf7..c0df66b950 100644 --- a/plugins/CuraDrive/src/Settings.py +++ b/plugins/CuraDrive/src/Settings.py @@ -1,21 +1,22 @@ # Copyright (c) 2018 Ultimaker B.V. from UM import i18nCatalog +from cura import CuraConstants + class Settings: """ Keeps the application settings. """ - UM_CLOUD_API_ROOT = "https://api.ultimaker.com" DRIVE_API_VERSION = 1 - DRIVE_API_URL = "{}/cura-drive/v{}".format(UM_CLOUD_API_ROOT, str(DRIVE_API_VERSION)) - + DRIVE_API_URL = "{}/cura-drive/v{}".format(CuraConstants.CuraCloudAPIRoot, str(DRIVE_API_VERSION)) + AUTO_BACKUP_ENABLED_PREFERENCE_KEY = "cura_drive/auto_backup_enabled" AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date" I18N_CATALOG_ID = "cura" I18N_CATALOG = i18nCatalog(I18N_CATALOG_ID) - + MESSAGE_TITLE = I18N_CATALOG.i18nc("@info:title", "Backups"), # Translatable messages for the entire plugin. diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 562a964f01..667b28c018 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -18,6 +18,7 @@ from UM.i18n import i18nCatalog from UM.Version import Version import cura +from cura import CuraConstants from cura.CuraApplication import CuraApplication from .AuthorsModel import AuthorsModel @@ -39,9 +40,9 @@ class Toolbox(QObject, Extension): self._application = application # type: CuraApplication - self._sdk_version = None # type: Optional[Union[str, int]] - self._cloud_api_version = None # type: Optional[int] - self._cloud_api_root = None # type: Optional[str] + self._sdk_version = CuraConstants.CuraSDKVersion # type: Union[str, int] + self._cloud_api_version = CuraConstants.CuraCloudAPIVersion # type: int + self._cloud_api_root = CuraConstants.CuraCloudAPIRoot # type: str self._api_url = None # type: Optional[str] # Network: @@ -168,9 +169,6 @@ class Toolbox(QObject, Extension): def _onAppInitialized(self) -> None: self._plugin_registry = self._application.getPluginRegistry() self._package_manager = self._application.getPackageManager() - self._sdk_version = self._getSDKVersion() - self._cloud_api_version = self._getCloudAPIVersion() - self._cloud_api_root = self._getCloudAPIRoot() self._api_url = "{cloud_api_root}/cura-packages/v{cloud_api_version}/cura/v{sdk_version}".format( cloud_api_root = self._cloud_api_root, cloud_api_version = self._cloud_api_version, @@ -186,36 +184,6 @@ class Toolbox(QObject, Extension): "materials_generic": QUrl("{base_url}/packages?package_type=material&tags=generic".format(base_url = self._api_url)) } - # Get the API root for the packages API depending on Cura version settings. - def _getCloudAPIRoot(self) -> str: - if not hasattr(cura, "CuraVersion"): - return self.DEFAULT_CLOUD_API_ROOT - if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"): # type: ignore - return self.DEFAULT_CLOUD_API_ROOT - if not cura.CuraVersion.CuraCloudAPIRoot: # type: ignore - return self.DEFAULT_CLOUD_API_ROOT - return cura.CuraVersion.CuraCloudAPIRoot # type: ignore - - # Get the cloud API version from CuraVersion - def _getCloudAPIVersion(self) -> int: - if not hasattr(cura, "CuraVersion"): - return self.DEFAULT_CLOUD_API_VERSION - if not hasattr(cura.CuraVersion, "CuraCloudAPIVersion"): # type: ignore - return self.DEFAULT_CLOUD_API_VERSION - if not cura.CuraVersion.CuraCloudAPIVersion: # type: ignore - return self.DEFAULT_CLOUD_API_VERSION - return cura.CuraVersion.CuraCloudAPIVersion # type: ignore - - # Get the packages version depending on Cura version settings. - def _getSDKVersion(self) -> Union[int, str]: - if not hasattr(cura, "CuraVersion"): - return self._application.getAPIVersion().getMajor() - if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore - return self._application.getAPIVersion().getMajor() - if not cura.CuraVersion.CuraSDKVersion: # type: ignore - return self._application.getAPIVersion().getMajor() - return cura.CuraVersion.CuraSDKVersion # type: ignore - @pyqtSlot() def browsePackages(self) -> None: # Create the network manager: From 8c07a6e89b97c6a545720e481a606f4f2a7e29f7 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 12 Dec 2018 10:35:36 +0100 Subject: [PATCH 07/51] Fix typing issues CURA-6005 --- plugins/CuraDrive/src/DriveApiService.py | 8 ++++---- plugins/CuraDrive/src/DrivePluginExtension.py | 3 ++- plugins/CuraDrive/src/UploadBackupJob.py | 4 ++-- plugins/CuraDrive/src/models/BackupListModel.py | 6 +++--- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index a677466838..6963e595b5 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -3,7 +3,7 @@ import base64 import hashlib from datetime import datetime from tempfile import NamedTemporaryFile -from typing import Optional, List, Dict +from typing import Any, Optional, List, Dict import requests @@ -34,7 +34,7 @@ class DriveApiService: """Create a new instance of the Drive API service and set the cura_api object.""" self._cura_api = cura_api - def getBackups(self) -> List[Dict[str, any]]: + def getBackups(self) -> List[Dict[str, Any]]: """Get all backups from the API.""" access_token = self._cura_api.account.accessToken if not access_token: @@ -85,7 +85,7 @@ class DriveApiService: else: self.onCreatingStateChanged.emit(is_creating=False) - def restoreBackup(self, backup: Dict[str, any]) -> None: + def restoreBackup(self, backup: Dict[str, Any]) -> None: """ Restore a previously exported backup from cloud storage. :param backup: A dict containing an entry from the API list response. @@ -157,7 +157,7 @@ class DriveApiService: return False return True - def _requestBackupUpload(self, backup_metadata: Dict[str, any], backup_size: int) -> Optional[str]: + def _requestBackupUpload(self, backup_metadata: Dict[str, Any], backup_size: int) -> Optional[str]: """ Request a backup upload slot from the API. :param backup_metadata: A dict containing some meta data about the backup. diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index 556fb187df..7e1472b988 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -70,7 +70,8 @@ class DrivePluginExtension(QObject, Extension): if not self._drive_window: self._drive_window = self.createDriveWindow() self.refreshBackups() - self._drive_window.show() + if self._drive_window: + self._drive_window.show() def createDriveWindow(self) -> Optional["QObject"]: """ diff --git a/plugins/CuraDrive/src/UploadBackupJob.py b/plugins/CuraDrive/src/UploadBackupJob.py index 039e6d1a09..bcecce554a 100644 --- a/plugins/CuraDrive/src/UploadBackupJob.py +++ b/plugins/CuraDrive/src/UploadBackupJob.py @@ -14,14 +14,14 @@ class UploadBackupJob(Job): 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): + 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): + def run(self) -> None: Message(Settings.translatable_messages["uploading_backup"], title = Settings.MESSAGE_TITLE, lifetime = 10).show() diff --git a/plugins/CuraDrive/src/models/BackupListModel.py b/plugins/CuraDrive/src/models/BackupListModel.py index 9567b3d255..93b0c4c48c 100644 --- a/plugins/CuraDrive/src/models/BackupListModel.py +++ b/plugins/CuraDrive/src/models/BackupListModel.py @@ -1,5 +1,5 @@ # Copyright (c) 2018 Ultimaker B.V. -from typing import List, Dict +from typing import Any, List, Dict from UM.Qt.ListModel import ListModel @@ -11,7 +11,7 @@ class BackupListModel(ListModel): The BackupListModel transforms the backups data that came from the server so it can be served to the Qt UI. """ - def __init__(self, parent=None): + def __init__(self, parent = None) -> None: super().__init__(parent) self.addRoleName(Qt.UserRole + 1, "backup_id") self.addRoleName(Qt.UserRole + 2, "download_url") @@ -19,7 +19,7 @@ class BackupListModel(ListModel): self.addRoleName(Qt.UserRole + 4, "md5_hash") self.addRoleName(Qt.UserRole + 5, "data") - def loadBackups(self, data: List[Dict[str, any]]) -> None: + def loadBackups(self, data: List[Dict[str, Any]]) -> None: """ Populate the model with server data. :param data: From a6663ea0e8cf80cfc6bcd4d71db593009b83f514 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 12 Dec 2018 10:51:11 +0100 Subject: [PATCH 08/51] Fix typing issues CURA-6005 --- cura/CuraConstants.py | 2 +- plugins/CuraDrive/src/DriveApiService.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/CuraConstants.py b/cura/CuraConstants.py index 331937a0c2..c573d550c5 100644 --- a/cura/CuraConstants.py +++ b/cura/CuraConstants.py @@ -41,7 +41,7 @@ except ImportError: # Cloud API # --------- DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str -DEFAULT_CLOUD_API_VERSION = 1 # type: int +DEFAULT_CLOUD_API_VERSION = "1" # type: str try: from cura.CuraVersion import CuraCloudAPIRoot # type: ignore diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index 6963e595b5..98199c91cf 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -108,7 +108,7 @@ class DriveApiService: for chunk in download_package: write_backup.write(chunk) - if not self._verifyMd5Hash(temporary_backup_file.name, backup.get("md5_hash")): + 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.") From f72b58386b39d1140ffdc489127eb396604afb77 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 13 Dec 2018 11:46:11 +0100 Subject: [PATCH 09/51] Also use CuraConstants for account API root --- cura/API/Account.py | 10 ++++++---- cura/CuraConstants.py | 6 ++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cura/API/Account.py b/cura/API/Account.py index 397e220478..7b4bc32e99 100644 --- a/cura/API/Account.py +++ b/cura/API/Account.py @@ -6,6 +6,7 @@ from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty from UM.i18n import i18nCatalog from UM.Message import Message +from cura import CuraConstants from cura.OAuth2.AuthorizationService import AuthorizationService from cura.OAuth2.Models import OAuth2Settings @@ -37,15 +38,16 @@ class Account(QObject): self._logged_in = False self._callback_port = 32118 - self._oauth_root = "https://account.ultimaker.com" - self._cloud_api_root = "https://api.ultimaker.com" + self._oauth_root = CuraConstants.CuraCloudAccountAPIRoot self._oauth_settings = OAuth2Settings( OAUTH_SERVER_URL= self._oauth_root, CALLBACK_PORT=self._callback_port, CALLBACK_URL="http://localhost:{}/callback".format(self._callback_port), - CLIENT_ID="um---------------ultimaker_cura_drive_plugin", - CLIENT_SCOPES="account.user.read drive.backup.read drive.backup.write packages.download packages.rating.read packages.rating.write", + CLIENT_ID="um----------------------------ultimaker_cura", + CLIENT_SCOPES="account.user.read drive.backup.read drive.backup.write packages.download " + "packages.rating.read packages.rating.write connect.cluster.read connect.cluster.write " + "cura.printjob.read cura.printjob.write cura.mesh.read cura.mesh.write", AUTH_DATA_PREFERENCE_KEY="general/ultimaker_auth_data", AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(self._oauth_root), AUTH_FAILED_REDIRECT="{}/app/auth-error".format(self._oauth_root) diff --git a/cura/CuraConstants.py b/cura/CuraConstants.py index c573d550c5..7ca8ea865b 100644 --- a/cura/CuraConstants.py +++ b/cura/CuraConstants.py @@ -42,6 +42,7 @@ except ImportError: # --------- DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str DEFAULT_CLOUD_API_VERSION = "1" # type: str +DEFAULT_CLOUD_ACCOUNT_API_ROOT = "https://account.ultimaker.com" # type: str try: from cura.CuraVersion import CuraCloudAPIRoot # type: ignore @@ -52,3 +53,8 @@ try: from cura.CuraVersion import CuraCloudAPIVersion # type: ignore except ImportError: CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION + +try: + from cura.CuraVersion import CuraCloudAccountAPIRoot # type: ignore +except ImportError: + CuraCloudAccountAPIRoot = DEFAULT_CLOUD_ACCOUNT_API_ROOT From b7904d6e056b1edbac2ac8b75755704d4ad47f75 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 17 Dec 2018 13:04:23 +0100 Subject: [PATCH 10/51] Unify the CuraDrive plugin with the rest of the items in Cura For instance, the buttons were converted to either primary buttons or secondary buttons. A new CheckBox component was created in Cura in order to reuse it in the future. Contributes to CURA-6005. --- .../src/qml/components/ActionButton.qml | 67 ------------------- .../src/qml/components/ActionCheckBox.qml | 49 -------------- .../src/qml/components/ActionToolTip.qml | 4 +- .../src/qml/components/BackupListFooter.qml | 10 +-- .../src/qml/components/BackupListItem.qml | 15 +++-- plugins/CuraDrive/src/qml/main.qml | 6 +- .../CuraDrive/src/qml/pages/WelcomePage.qml | 9 ++- resources/qml/ActionButton.qml | 15 +++++ resources/qml/CheckBox.qml | 61 +++++++++++++++++ resources/qml/Cura.qml | 11 +++ resources/qml/qmldir | 3 +- resources/themes/cura-light/styles.qml | 2 +- 12 files changed, 115 insertions(+), 137 deletions(-) delete mode 100644 plugins/CuraDrive/src/qml/components/ActionButton.qml delete mode 100644 plugins/CuraDrive/src/qml/components/ActionCheckBox.qml create mode 100644 resources/qml/CheckBox.qml diff --git a/plugins/CuraDrive/src/qml/components/ActionButton.qml b/plugins/CuraDrive/src/qml/components/ActionButton.qml deleted file mode 100644 index 843079ed88..0000000000 --- a/plugins/CuraDrive/src/qml/components/ActionButton.qml +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -import QtQuick 2.7 -import QtQuick.Controls 2.1 -import QtQuick.Layouts 1.3 - -import UM 1.1 as UM - -Button -{ - id: button - property alias cursorShape: mouseArea.cursorShape - property var iconSource: "" - property var busy: false - property var color: UM.Theme.getColor("primary") - property var hoverColor: UM.Theme.getColor("primary_hover") - property var disabledColor: color - property var textColor: UM.Theme.getColor("button_text") - property var textHoverColor: UM.Theme.getColor("button_text_hover") - property var textDisabledColor: textColor - property var textFont: UM.Theme.getFont("action_button") - - contentItem: RowLayout - { - Icon - { - id: buttonIcon - iconSource: button.iconSource - width: 16 * screenScaleFactor - color: button.hovered ? button.textHoverColor : button.textColor - visible: button.iconSource != "" && !loader.visible - } - - Icon - { - id: loader - iconSource: "../images/loading.gif" - width: 16 * screenScaleFactor - color: button.hovered ? button.textHoverColor : button.textColor - visible: button.busy - animated: true - } - - Label - { - id: buttonText - text: button.text - color: button.enabled ? (button.hovered ? button.textHoverColor : button.textColor): button.textDisabledColor - font: button.textFont - visible: button.text != "" - renderType: Text.NativeRendering - } - } - - background: Rectangle - { - color: button.enabled ? (button.hovered ? button.hoverColor : button.color) : button.disabledColor - } - - MouseArea - { - id: mouseArea - anchors.fill: parent - onPressed: mouse.accepted = false - hoverEnabled: true - cursorShape: button.enabled ? (hovered ? Qt.PointingHandCursor : Qt.ArrowCursor) : Qt.ForbiddenCursor - } -} diff --git a/plugins/CuraDrive/src/qml/components/ActionCheckBox.qml b/plugins/CuraDrive/src/qml/components/ActionCheckBox.qml deleted file mode 100644 index 71f5e6035d..0000000000 --- a/plugins/CuraDrive/src/qml/components/ActionCheckBox.qml +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -import QtQuick 2.7 -import QtQuick.Controls 2.1 -import QtQuick.Layouts 1.3 - -import UM 1.3 as UM - -CheckBox -{ - id: checkbox - hoverEnabled: true - - property var label: "" - - indicator: Rectangle { - implicitWidth: 30 * screenScaleFactor - implicitHeight: 30 * screenScaleFactor - x: 0 - y: Math.round(parent.height / 2 - height / 2) - color: UM.Theme.getColor("sidebar") - border.color: UM.Theme.getColor("text") - - Rectangle { - width: 14 * screenScaleFactor - height: 14 * screenScaleFactor - x: 8 * screenScaleFactor - y: 8 * screenScaleFactor - color: UM.Theme.getColor("primary") - visible: checkbox.checked - } - } - - contentItem: Label { - anchors - { - left: checkbox.indicator.right - leftMargin: 5 * screenScaleFactor - } - text: catalog.i18nc("@checkbox:description", "Auto Backup") - color: UM.Theme.getColor("text") - renderType: Text.NativeRendering - verticalAlignment: Text.AlignVCenter - } - - ActionToolTip - { - text: checkbox.label - } -} diff --git a/plugins/CuraDrive/src/qml/components/ActionToolTip.qml b/plugins/CuraDrive/src/qml/components/ActionToolTip.qml index 93b92bc2df..7401221328 100644 --- a/plugins/CuraDrive/src/qml/components/ActionToolTip.qml +++ b/plugins/CuraDrive/src/qml/components/ActionToolTip.qml @@ -14,7 +14,7 @@ ToolTip background: Rectangle { - color: UM.Theme.getColor("sidebar") + color: UM.Theme.getColor("main_background") border.color: UM.Theme.getColor("primary") border.width: 1 * screenScaleFactor } @@ -23,7 +23,7 @@ ToolTip { text: tooltip.text color: UM.Theme.getColor("text") - font: UM.Theme.getFont("very_small") + font: UM.Theme.getFont("default") renderType: Text.NativeRendering } } diff --git a/plugins/CuraDrive/src/qml/components/BackupListFooter.qml b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml index 80f47d6cba..1e7fc16801 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListFooter.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml @@ -4,6 +4,7 @@ import QtQuick.Controls 2.1 import QtQuick.Layouts 1.3 import UM 1.3 as UM +import Cura 1.0 as Cura import "../components" @@ -13,7 +14,7 @@ RowLayout width: parent.width property bool showInfoButton: false - ActionButton + Cura.PrimaryButton { id: infoButton text: catalog.i18nc("@button", "Want more?") @@ -22,7 +23,7 @@ RowLayout visible: backupListFooter.showInfoButton } - ActionButton + Cura.PrimaryButton { id: createBackupButton text: catalog.i18nc("@button", "Backup Now") @@ -32,11 +33,12 @@ RowLayout busy: CuraDrive.isCreatingBackup } - ActionCheckBox + Cura.CheckBox { id: autoBackupEnabled checked: CuraDrive.autoBackupEnabled onClicked: CuraDrive.toggleAutoBackup(autoBackupEnabled.checked) - label: catalog.i18nc("@checkbox:description", "Automatically create a backup each day that Cura is started.") + text: catalog.i18nc("@checkbox:description", "Auto Backup") + tooltip: catalog.i18nc("@checkbox:description", "Automatically create a backup each day that Cura is started.") } } diff --git a/plugins/CuraDrive/src/qml/components/BackupListItem.qml b/plugins/CuraDrive/src/qml/components/BackupListItem.qml index abe9a1acf9..ad1ce5f9df 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItem.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItem.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts 1.3 import QtQuick.Dialogs 1.1 import UM 1.1 as UM +import Cura 1.0 as Cura Item { @@ -29,7 +30,7 @@ Item width: parent.width height: 50 * screenScaleFactor - ActionButton + Cura.ActionButton { color: "transparent" hoverColor: "transparent" @@ -61,18 +62,18 @@ Item renderType: Text.NativeRendering } - ActionButton + Cura.SecondaryButton { text: catalog.i18nc("@button", "Restore") - color: "transparent" - hoverColor: "transparent" - textColor: UM.Theme.getColor("text") - textHoverColor: UM.Theme.getColor("text_link") +// color: "transparent" +// hoverColor: "transparent" +// textColor: UM.Theme.getColor("text") +// textHoverColor: UM.Theme.getColor("text_link") enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup onClicked: confirmRestoreDialog.visible = true } - ActionButton + Cura.ActionButton { color: "transparent" hoverColor: "transparent" diff --git a/plugins/CuraDrive/src/qml/main.qml b/plugins/CuraDrive/src/qml/main.qml index 4a2219cf1f..359f8dd725 100644 --- a/plugins/CuraDrive/src/qml/main.qml +++ b/plugins/CuraDrive/src/qml/main.qml @@ -14,11 +14,11 @@ Window id: curaDriveDialog minimumWidth: Math.round(UM.Theme.getSize("modal_window_minimum").width) minimumHeight: Math.round(UM.Theme.getSize("modal_window_minimum").height) - maximumWidth: minimumWidth * 1.2 - maximumHeight: minimumHeight * 1.2 + maximumWidth: Math.round(minimumWidth * 1.2) + maximumHeight: Math.round(minimumHeight * 1.2) width: minimumWidth height: minimumHeight - color: UM.Theme.getColor("sidebar") + color: UM.Theme.getColor("main_background") title: catalog.i18nc("@title:window", "Cura Backups") // Globally available. diff --git a/plugins/CuraDrive/src/qml/pages/WelcomePage.qml b/plugins/CuraDrive/src/qml/pages/WelcomePage.qml index 882656dc4a..b7f96d162a 100644 --- a/plugins/CuraDrive/src/qml/pages/WelcomePage.qml +++ b/plugins/CuraDrive/src/qml/pages/WelcomePage.qml @@ -38,11 +38,14 @@ Column renderType: Text.NativeRendering } - ActionButton + Cura.PrimaryButton { id: loginButton - onClicked: Cura.API.account.login() - text: catalog.i18nc("@button", "Sign In") + width: UM.Theme.getSize("account_button").width + height: UM.Theme.getSize("account_button").height anchors.horizontalCenter: parent.horizontalCenter + text: catalog.i18nc("@button", "Sign in") + onClicked: Cura.API.account.login() + fixedWidthMode: true } } diff --git a/resources/qml/ActionButton.qml b/resources/qml/ActionButton.qml index 3a9552cd9c..573ead2910 100644 --- a/resources/qml/ActionButton.qml +++ b/resources/qml/ActionButton.qml @@ -4,6 +4,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.1 import QtGraphicalEffects 1.0 // For the dropshadow + import UM 1.1 as UM import Cura 1.0 as Cura @@ -30,6 +31,7 @@ Button property color outlineDisabledColor: outlineColor property alias shadowColor: shadow.color property alias shadowEnabled: shadow.visible + property alias busy: busyIndicator.visible // This property is used to indicate whether the button has a fixed width or the width would depend on the contents // Be careful when using fixedWidthMode, the translated texts can be too long that they won't fit. In any case, @@ -117,4 +119,17 @@ Button delay: 500 visible: text != "" && button.hovered } + + BusyIndicator { + id: busyIndicator + + anchors { + centerIn: parent + } + + width: height + height: parent.height + + visible: false + } } \ No newline at end of file diff --git a/resources/qml/CheckBox.qml b/resources/qml/CheckBox.qml new file mode 100644 index 0000000000..7a79182a4b --- /dev/null +++ b/resources/qml/CheckBox.qml @@ -0,0 +1,61 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.7 +import QtQuick.Controls 2.1 + +import UM 1.3 as UM + +CheckBox +{ + id: checkbox + hoverEnabled: true + + property alias tooltip: tooltip.text + + indicator: Rectangle { + implicitWidth: UM.Theme.getSize("checkbox").width + implicitHeight: UM.Theme.getSize("checkbox").height + x: 0 + y: Math.round(parent.height / 2 - height / 2) + color: UM.Theme.getColor("main_background") + radius: UM.Theme.getSize("checkbox_radius").width + border.width: UM.Theme.getSize("default_lining").width + border.color: checkbox.hovered ? UM.Theme.getColor("checkbox_border_hover") : UM.Theme.getColor("checkbox_border") + + UM.RecolorImage + { + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + width: Math.round(parent.width / 2.5) + height: Math.round(parent.height / 2.5) + sourceSize.height: width + color: UM.Theme.getColor("checkbox_mark") + source: UM.Theme.getIcon("check") + opacity: checkbox.checked + Behavior on opacity { NumberAnimation { duration: 100; } } + } + } + + contentItem: Label { + anchors + { + left: checkbox.indicator.right + leftMargin: UM.Theme.getSize("narrow_margin").width + } + text: checkbox.text + color: UM.Theme.getColor("checkbox_text") + font: UM.Theme.getFont("default") + renderType: Text.NativeRendering + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + ToolTip + { + id: tooltip + text: "" + delay: 500 + visible: text != "" && checkbox.hovered + } +} diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 8ab943b93b..71b97fa6f4 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -123,6 +123,17 @@ UM.MainWindow } } } + + // This is a placehoder for adding a pattern in the header + Image + { + id: backgroundPattern + anchors.fill: parent + fillMode: Image.Tile + source: UM.Theme.getImage("header_pattern") + horizontalAlignment: Image.AlignLeft + verticalAlignment: Image.AlignTop + } } MainWindowHeader diff --git a/resources/qml/qmldir b/resources/qml/qmldir index 1dc21150ce..4e28e12f62 100644 --- a/resources/qml/qmldir +++ b/resources/qml/qmldir @@ -14,4 +14,5 @@ PrinterTypeLabel 1.0 PrinterTypeLabel.qml ViewsSelector 1.0 ViewsSelector.qml ToolbarButton 1.0 ToolbarButton.qml SettingView 1.0 SettingView.qml -ProfileMenu 1.0 ProfileMenu.qml \ No newline at end of file +ProfileMenu 1.0 ProfileMenu.qml +CheckBox 1.0 CheckBox.qml \ No newline at end of file diff --git a/resources/themes/cura-light/styles.qml b/resources/themes/cura-light/styles.qml index 89729fc08c..95f811c3aa 100755 --- a/resources/themes/cura-light/styles.qml +++ b/resources/themes/cura-light/styles.qml @@ -478,7 +478,7 @@ QtObject color: (control.hovered || control._hovered) ? Theme.getColor("checkbox_hover") : (control.enabled ? Theme.getColor("checkbox") : Theme.getColor("checkbox_disabled")) Behavior on color { ColorAnimation { duration: 50; } } - radius: control.exclusiveGroup ? Math.round(Theme.getSize("checkbox").width / 2) : UM.Theme.getSize("checkbox_radius").width + radius: control.exclusiveGroup ? Math.round(Theme.getSize("checkbox").width / 2) : Theme.getSize("checkbox_radius").width border.width: Theme.getSize("default_lining").width border.color: (control.hovered || control._hovered) ? Theme.getColor("checkbox_border_hover") : Theme.getColor("checkbox_border") From 02825a062fd4e2413938fb8eee3969d7c3cbdc06 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 17 Dec 2018 13:38:41 +0100 Subject: [PATCH 11/51] Change the remove and the info buttons Contributes to CURA-6005. --- .../src/qml/components/ActionToolTip.qml | 29 ------------------ .../src/qml/components/BackupList.qml | 2 +- .../src/qml/components/BackupListFooter.qml | 4 +-- .../src/qml/components/BackupListItem.qml | 30 ++++++++----------- plugins/CuraDrive/src/qml/images/delete.svg | 7 ----- plugins/CuraDrive/src/qml/images/info.svg | 4 --- resources/qml/ActionButton.qml | 2 +- 7 files changed, 17 insertions(+), 61 deletions(-) delete mode 100644 plugins/CuraDrive/src/qml/components/ActionToolTip.qml delete mode 100644 plugins/CuraDrive/src/qml/images/delete.svg delete mode 100644 plugins/CuraDrive/src/qml/images/info.svg diff --git a/plugins/CuraDrive/src/qml/components/ActionToolTip.qml b/plugins/CuraDrive/src/qml/components/ActionToolTip.qml deleted file mode 100644 index 7401221328..0000000000 --- a/plugins/CuraDrive/src/qml/components/ActionToolTip.qml +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -import QtQuick 2.7 -import QtQuick.Controls 2.1 -import QtQuick.Layouts 1.3 - -import UM 1.1 as UM - -ToolTip -{ - id: tooltip - visible: parent.hovered - opacity: 0.9 - delay: 500 - - background: Rectangle - { - color: UM.Theme.getColor("main_background") - border.color: UM.Theme.getColor("primary") - border.width: 1 * screenScaleFactor - } - - contentItem: Label - { - text: tooltip.text - color: UM.Theme.getColor("text") - font: UM.Theme.getFont("default") - renderType: Text.NativeRendering - } -} diff --git a/plugins/CuraDrive/src/qml/components/BackupList.qml b/plugins/CuraDrive/src/qml/components/BackupList.qml index 231f25afc8..a19d1f0ae7 100644 --- a/plugins/CuraDrive/src/qml/components/BackupList.qml +++ b/plugins/CuraDrive/src/qml/components/BackupList.qml @@ -18,7 +18,7 @@ ListView BackupListItem { id: backupListItem - width: parent.width + width: parent.width - UM.Theme.getSize("default_margin").width // Add a margin, otherwise the scrollbar is be on top of the right most component } Divider diff --git a/plugins/CuraDrive/src/qml/components/BackupListFooter.qml b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml index 1e7fc16801..72dc3df044 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListFooter.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml @@ -18,7 +18,7 @@ RowLayout { id: infoButton text: catalog.i18nc("@button", "Want more?") - iconSource: "../images/info.svg" + iconSource: UM.Theme.getIcon("info") onClicked: Qt.openUrlExternally("https://goo.gl/forms/QACEP8pP3RV60QYG2") visible: backupListFooter.showInfoButton } @@ -28,7 +28,7 @@ RowLayout id: createBackupButton text: catalog.i18nc("@button", "Backup Now") iconSource: "../images/backup.svg" - enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup + enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup && !backupListFooter.showInfoButton onClicked: CuraDrive.createBackup() busy: CuraDrive.isCreatingBackup } diff --git a/plugins/CuraDrive/src/qml/components/BackupListItem.qml b/plugins/CuraDrive/src/qml/components/BackupListItem.qml index ad1ce5f9df..a84caeb6ab 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItem.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItem.qml @@ -26,17 +26,17 @@ Item RowLayout { id: dataRow - spacing: UM.Theme.getSize("default_margin").width * 2 + spacing: UM.Theme.getSize("wide_margin").width width: parent.width height: 50 * screenScaleFactor - Cura.ActionButton + UM.SimpleButton { - color: "transparent" - hoverColor: "transparent" - textColor: UM.Theme.getColor("text") - textHoverColor: UM.Theme.getColor("primary") - iconSource: "../images/info.svg" + width: UM.Theme.getSize("section_icon").width + height: UM.Theme.getSize("section_icon").height + color: UM.Theme.getColor("small_button_text") + hoverColor: UM.Theme.getColor("small_button_text_hover") + iconSource: UM.Theme.getIcon("info") onClicked: backupListItem.showDetails = !backupListItem.showDetails } @@ -65,21 +65,17 @@ Item Cura.SecondaryButton { text: catalog.i18nc("@button", "Restore") -// color: "transparent" -// hoverColor: "transparent" -// textColor: UM.Theme.getColor("text") -// textHoverColor: UM.Theme.getColor("text_link") enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup onClicked: confirmRestoreDialog.visible = true } - Cura.ActionButton + UM.SimpleButton { - color: "transparent" - hoverColor: "transparent" - textColor: UM.Theme.getColor("setting_validation_error") - textHoverColor: UM.Theme.getColor("setting_validation_error") - iconSource: "../images/delete.svg" + width: UM.Theme.getSize("message_close").width + height: UM.Theme.getSize("message_close").height + color: UM.Theme.getColor("small_button_text") + hoverColor: UM.Theme.getColor("small_button_text_hover") + iconSource: UM.Theme.getIcon("cross1") onClicked: confirmDeleteDialog.visible = true } } diff --git a/plugins/CuraDrive/src/qml/images/delete.svg b/plugins/CuraDrive/src/qml/images/delete.svg deleted file mode 100644 index 2f6190ad43..0000000000 --- a/plugins/CuraDrive/src/qml/images/delete.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/info.svg b/plugins/CuraDrive/src/qml/images/info.svg deleted file mode 100644 index 36154d6729..0000000000 --- a/plugins/CuraDrive/src/qml/images/info.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/resources/qml/ActionButton.qml b/resources/qml/ActionButton.qml index 573ead2910..2448b9a551 100644 --- a/resources/qml/ActionButton.qml +++ b/resources/qml/ActionButton.qml @@ -55,7 +55,7 @@ Button width: visible ? height : 0 sourceSize.width: width sourceSize.height: height - color: button.hovered ? button.textHoverColor : button.textColor + color: button.enabled ? (button.hovered ? button.textHoverColor : button.textColor) : button.textDisabledColor visible: source != "" && !button.isIconOnRightSide anchors.verticalCenter: parent.verticalCenter } From da69c16e0962231b2d8ae14cc5684d5788f57bf8 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 18 Dec 2018 16:14:13 +0100 Subject: [PATCH 12/51] Split the constants in to files, indicating which constants each file stores. Contributes to CURA-6005. --- cura/API/Account.py | 4 +- ...uraConstants.py => ApplicationMetadata.py} | 96 +++++++------------ cura/CuraApplication.py | 14 +-- cura/UltimakerCloudAuthentication.py | 24 +++++ plugins/CuraDrive/src/Settings.py | 4 +- plugins/Toolbox/src/Toolbox.py | 10 +- 6 files changed, 76 insertions(+), 76 deletions(-) rename cura/{CuraConstants.py => ApplicationMetadata.py} (50%) create mode 100644 cura/UltimakerCloudAuthentication.py diff --git a/cura/API/Account.py b/cura/API/Account.py index 7b4bc32e99..47f67af8ce 100644 --- a/cura/API/Account.py +++ b/cura/API/Account.py @@ -6,7 +6,7 @@ from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty from UM.i18n import i18nCatalog from UM.Message import Message -from cura import CuraConstants +from cura import UltimakerCloudAuthentication from cura.OAuth2.AuthorizationService import AuthorizationService from cura.OAuth2.Models import OAuth2Settings @@ -38,7 +38,7 @@ class Account(QObject): self._logged_in = False self._callback_port = 32118 - self._oauth_root = CuraConstants.CuraCloudAccountAPIRoot + self._oauth_root = UltimakerCloudAuthentication.CuraCloudAccountAPIRoot self._oauth_settings = OAuth2Settings( OAUTH_SERVER_URL= self._oauth_root, diff --git a/cura/CuraConstants.py b/cura/ApplicationMetadata.py similarity index 50% rename from cura/CuraConstants.py rename to cura/ApplicationMetadata.py index 7ca8ea865b..e2ac4453eb 100644 --- a/cura/CuraConstants.py +++ b/cura/ApplicationMetadata.py @@ -1,60 +1,36 @@ -# -# This file contains all constant values in Cura -# - -# ------------- -# Cura Versions -# ------------- -DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura" -DEFAULT_CURA_VERSION = "master" -DEFAULT_CURA_BUILD_TYPE = "" -DEFAULT_CURA_DEBUG_MODE = False -DEFAULT_CURA_SDK_VERSION = "5.0.0" - -try: - from cura.CuraVersion import CuraAppDisplayName # type: ignore -except ImportError: - CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME - -try: - from cura.CuraVersion import CuraVersion # type: ignore -except ImportError: - CuraVersion = DEFAULT_CURA_VERSION # [CodeStyle: Reflecting imported value] - -try: - from cura.CuraVersion import CuraBuildType # type: ignore -except ImportError: - CuraBuildType = DEFAULT_CURA_BUILD_TYPE - -try: - from cura.CuraVersion import CuraDebugMode # type: ignore -except ImportError: - CuraDebugMode = DEFAULT_CURA_DEBUG_MODE - -try: - from cura.CuraVersion import CuraSDKVersion # type: ignore -except ImportError: - CuraSDKVersion = DEFAULT_CURA_SDK_VERSION - - -# --------- -# Cloud API -# --------- -DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str -DEFAULT_CLOUD_API_VERSION = "1" # type: str -DEFAULT_CLOUD_ACCOUNT_API_ROOT = "https://account.ultimaker.com" # type: str - -try: - from cura.CuraVersion import CuraCloudAPIRoot # type: ignore -except ImportError: - CuraCloudAPIRoot = DEFAULT_CLOUD_API_ROOT - -try: - from cura.CuraVersion import CuraCloudAPIVersion # type: ignore -except ImportError: - CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION - -try: - from cura.CuraVersion import CuraCloudAccountAPIRoot # type: ignore -except ImportError: - CuraCloudAccountAPIRoot = DEFAULT_CLOUD_ACCOUNT_API_ROOT +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +# --------- +# Genearl constants used in Cura +# --------- +DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura" +DEFAULT_CURA_VERSION = "master" +DEFAULT_CURA_BUILD_TYPE = "" +DEFAULT_CURA_DEBUG_MODE = False +DEFAULT_CURA_SDK_VERSION = "6.0.0" + +try: + from cura.CuraVersion import CuraAppDisplayName # type: ignore +except ImportError: + CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME + +try: + from cura.CuraVersion import CuraVersion # type: ignore +except ImportError: + CuraVersion = DEFAULT_CURA_VERSION # [CodeStyle: Reflecting imported value] + +try: + from cura.CuraVersion import CuraBuildType # type: ignore +except ImportError: + CuraBuildType = DEFAULT_CURA_BUILD_TYPE + +try: + from cura.CuraVersion import CuraDebugMode # type: ignore +except ImportError: + CuraDebugMode = DEFAULT_CURA_DEBUG_MODE + +try: + from cura.CuraVersion import CuraSDKVersion # type: ignore +except ImportError: + CuraSDKVersion = DEFAULT_CURA_SDK_VERSION diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index a7845708e4..748c5c2fc4 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -115,7 +115,7 @@ from cura.ObjectsModel import ObjectsModel from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage -from cura import CuraConstants +from cura import ApplicationMetadata from UM.FlameProfiler import pyqtSlot from UM.Decorators import override @@ -164,11 +164,11 @@ class CuraApplication(QtApplication): def __init__(self, *args, **kwargs): super().__init__(name = "cura", - app_display_name = CuraConstants.CuraAppDisplayName, - version = CuraConstants.CuraVersion, - api_version = CuraConstants.CuraSDKVersion, - buildtype = CuraConstants.CuraBuildType, - is_debug_mode = CuraConstants.CuraDebugMode, + app_display_name = ApplicationMetadata.CuraAppDisplayName, + version = ApplicationMetadata.CuraVersion, + api_version = ApplicationMetadata.CuraSDKVersion, + buildtype = ApplicationMetadata.CuraBuildType, + is_debug_mode = ApplicationMetadata.CuraDebugMode, tray_icon_name = "cura-icon-32.png", **kwargs) @@ -953,7 +953,7 @@ class CuraApplication(QtApplication): engine.rootContext().setContextProperty("CuraApplication", self) engine.rootContext().setContextProperty("PrintInformation", self._print_information) engine.rootContext().setContextProperty("CuraActions", self._cura_actions) - engine.rootContext().setContextProperty("CuraSDKVersion", CuraConstants.CuraSDKVersion) + engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion) qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type") diff --git a/cura/UltimakerCloudAuthentication.py b/cura/UltimakerCloudAuthentication.py new file mode 100644 index 0000000000..7ebdfd054b --- /dev/null +++ b/cura/UltimakerCloudAuthentication.py @@ -0,0 +1,24 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +# --------- +# Constants used for the Cloud API +# --------- +DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str +DEFAULT_CLOUD_API_VERSION = "1" # type: str +DEFAULT_CLOUD_ACCOUNT_API_ROOT = "https://account.ultimaker.com" # type: str + +try: + from cura.CuraVersion import CuraCloudAPIRoot # type: ignore +except ImportError: + CuraCloudAPIRoot = DEFAULT_CLOUD_API_ROOT + +try: + from cura.CuraVersion import CuraCloudAPIVersion # type: ignore +except ImportError: + CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION + +try: + from cura.CuraVersion import CuraCloudAccountAPIRoot # type: ignore +except ImportError: + CuraCloudAccountAPIRoot = DEFAULT_CLOUD_ACCOUNT_API_ROOT diff --git a/plugins/CuraDrive/src/Settings.py b/plugins/CuraDrive/src/Settings.py index c0df66b950..4ee73ad149 100644 --- a/plugins/CuraDrive/src/Settings.py +++ b/plugins/CuraDrive/src/Settings.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. from UM import i18nCatalog -from cura import CuraConstants +from cura import UltimakerCloudAuthentication class Settings: @@ -9,7 +9,7 @@ class Settings: Keeps the application settings. """ DRIVE_API_VERSION = 1 - DRIVE_API_URL = "{}/cura-drive/v{}".format(CuraConstants.CuraCloudAPIRoot, str(DRIVE_API_VERSION)) + DRIVE_API_URL = "{}/cura-drive/v{}".format(UltimakerCloudAuthentication.CuraCloudAPIRoot, str(DRIVE_API_VERSION)) AUTO_BACKUP_ENABLED_PREFERENCE_KEY = "cura_drive/auto_backup_enabled" AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date" diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 2229ab1f67..e7e849f1d2 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -16,8 +16,8 @@ from UM.Extension import Extension from UM.i18n import i18nCatalog from UM.Version import Version -import cura -from cura import CuraConstants +from cura import ApplicationMetadata +from cura import UltimakerCloudAuthentication from cura.CuraApplication import CuraApplication from .AuthorsModel import AuthorsModel @@ -39,9 +39,9 @@ class Toolbox(QObject, Extension): self._application = application # type: CuraApplication - self._sdk_version = CuraConstants.CuraSDKVersion # type: Union[str, int] - self._cloud_api_version = CuraConstants.CuraCloudAPIVersion # type: int - self._cloud_api_root = CuraConstants.CuraCloudAPIRoot # type: str + self._sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int] + self._cloud_api_version = UltimakerCloudAuthentication.CuraCloudAPIVersion # type: int + self._cloud_api_root = UltimakerCloudAuthentication.CuraCloudAPIRoot # type: str self._api_url = None # type: Optional[str] # Network: From 8e78582b619e9e2511f57dd00162c800fcf4c43d Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 18 Dec 2018 16:17:37 +0100 Subject: [PATCH 13/51] Add correct copyright and license header to all the files in the plugin Contributes to CURA-6005. --- plugins/CuraDrive/__init__.py | 4 +++- plugins/CuraDrive/plugin.json | 2 +- plugins/CuraDrive/src/DriveApiService.py | 4 +++- plugins/CuraDrive/src/DrivePluginExtension.py | 4 +++- plugins/CuraDrive/src/Settings.py | 2 ++ plugins/CuraDrive/src/UploadBackupJob.py | 2 ++ plugins/CuraDrive/src/models/BackupListModel.py | 2 ++ plugins/CuraDrive/src/qml/components/BackupList.qml | 2 ++ plugins/CuraDrive/src/qml/components/BackupListFooter.qml | 2 ++ plugins/CuraDrive/src/qml/components/BackupListItem.qml | 2 ++ .../CuraDrive/src/qml/components/BackupListItemDetails.qml | 2 ++ .../CuraDrive/src/qml/components/BackupListItemDetailsRow.qml | 2 ++ plugins/CuraDrive/src/qml/components/Divider.qml | 2 ++ plugins/CuraDrive/src/qml/components/Icon.qml | 2 ++ plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml | 2 ++ plugins/CuraDrive/src/qml/main.qml | 2 ++ plugins/CuraDrive/src/qml/pages/BackupsPage.qml | 2 ++ plugins/CuraDrive/src/qml/pages/WelcomePage.qml | 2 ++ 18 files changed, 38 insertions(+), 4 deletions(-) diff --git a/plugins/CuraDrive/__init__.py b/plugins/CuraDrive/__init__.py index 6612a5d614..4103b0cf2e 100644 --- a/plugins/CuraDrive/__init__.py +++ b/plugins/CuraDrive/__init__.py @@ -1,4 +1,6 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + import os is_testing = os.getenv('ENV_NAME', "development") == "testing" diff --git a/plugins/CuraDrive/plugin.json b/plugins/CuraDrive/plugin.json index 6cf1fa273c..d1cab39ca5 100644 --- a/plugins/CuraDrive/plugin.json +++ b/plugins/CuraDrive/plugin.json @@ -3,6 +3,6 @@ "author": "Ultimaker B.V.", "description": "Backup and restore your configuration.", "version": "1.2.0", - "api": 5, + "api": 6, "i18n-catalog": "cura" } diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index 98199c91cf..7c3e7b7026 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -1,4 +1,6 @@ -# Copyright (c) 2017 Ultimaker B.V. +# 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 diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index 7e1472b988..a76c623fe8 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -1,4 +1,6 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + import os from datetime import datetime from typing import Optional diff --git a/plugins/CuraDrive/src/Settings.py b/plugins/CuraDrive/src/Settings.py index 4ee73ad149..04ace8af95 100644 --- a/plugins/CuraDrive/src/Settings.py +++ b/plugins/CuraDrive/src/Settings.py @@ -1,4 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + from UM import i18nCatalog from cura import UltimakerCloudAuthentication diff --git a/plugins/CuraDrive/src/UploadBackupJob.py b/plugins/CuraDrive/src/UploadBackupJob.py index bcecce554a..ae6cb13f2e 100644 --- a/plugins/CuraDrive/src/UploadBackupJob.py +++ b/plugins/CuraDrive/src/UploadBackupJob.py @@ -1,4 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + import requests from UM.Job import Job diff --git a/plugins/CuraDrive/src/models/BackupListModel.py b/plugins/CuraDrive/src/models/BackupListModel.py index 93b0c4c48c..06b256b22c 100644 --- a/plugins/CuraDrive/src/models/BackupListModel.py +++ b/plugins/CuraDrive/src/models/BackupListModel.py @@ -1,4 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + from typing import Any, List, Dict from UM.Qt.ListModel import ListModel diff --git a/plugins/CuraDrive/src/qml/components/BackupList.qml b/plugins/CuraDrive/src/qml/components/BackupList.qml index a19d1f0ae7..af7e72b6e6 100644 --- a/plugins/CuraDrive/src/qml/components/BackupList.qml +++ b/plugins/CuraDrive/src/qml/components/BackupList.qml @@ -1,4 +1,6 @@ // Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + import QtQuick 2.7 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.3 diff --git a/plugins/CuraDrive/src/qml/components/BackupListFooter.qml b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml index 72dc3df044..2a6d82bc74 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListFooter.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml @@ -1,4 +1,6 @@ // Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + import QtQuick 2.7 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.3 diff --git a/plugins/CuraDrive/src/qml/components/BackupListItem.qml b/plugins/CuraDrive/src/qml/components/BackupListItem.qml index a84caeb6ab..ba4f1a32a4 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItem.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItem.qml @@ -1,4 +1,6 @@ // Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + import QtQuick 2.7 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.3 diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml index 74d4c5ab57..38a2557c47 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml @@ -1,4 +1,6 @@ // Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + import QtQuick 2.7 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.3 diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml index dad1674fe7..d1c8dd33d2 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml @@ -1,4 +1,6 @@ // Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + import QtQuick 2.7 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.3 diff --git a/plugins/CuraDrive/src/qml/components/Divider.qml b/plugins/CuraDrive/src/qml/components/Divider.qml index bba2f2f29c..202794fe23 100644 --- a/plugins/CuraDrive/src/qml/components/Divider.qml +++ b/plugins/CuraDrive/src/qml/components/Divider.qml @@ -1,4 +1,6 @@ // Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + import QtQuick 2.7 import UM 1.3 as UM diff --git a/plugins/CuraDrive/src/qml/components/Icon.qml b/plugins/CuraDrive/src/qml/components/Icon.qml index 3cb822bf82..8d559bc2b4 100644 --- a/plugins/CuraDrive/src/qml/components/Icon.qml +++ b/plugins/CuraDrive/src/qml/components/Icon.qml @@ -1,4 +1,6 @@ // Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + import QtQuick 2.7 import QtQuick.Controls 2.1 import QtGraphicalEffects 1.0 diff --git a/plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml b/plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml index 5ac5df15ff..5ef8558ee7 100644 --- a/plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml +++ b/plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml @@ -1,4 +1,6 @@ // Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + import QtQuick 2.7 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.3 diff --git a/plugins/CuraDrive/src/qml/main.qml b/plugins/CuraDrive/src/qml/main.qml index 359f8dd725..e8a49a49e5 100644 --- a/plugins/CuraDrive/src/qml/main.qml +++ b/plugins/CuraDrive/src/qml/main.qml @@ -1,4 +1,6 @@ // Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + import QtQuick 2.7 import QtQuick.Controls 2.1 import QtQuick.Window 2.2 diff --git a/plugins/CuraDrive/src/qml/pages/BackupsPage.qml b/plugins/CuraDrive/src/qml/pages/BackupsPage.qml index 88ce766383..0a7b00d2cb 100644 --- a/plugins/CuraDrive/src/qml/pages/BackupsPage.qml +++ b/plugins/CuraDrive/src/qml/pages/BackupsPage.qml @@ -1,4 +1,6 @@ // Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + import QtQuick 2.7 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.3 diff --git a/plugins/CuraDrive/src/qml/pages/WelcomePage.qml b/plugins/CuraDrive/src/qml/pages/WelcomePage.qml index b7f96d162a..19eecedf28 100644 --- a/plugins/CuraDrive/src/qml/pages/WelcomePage.qml +++ b/plugins/CuraDrive/src/qml/pages/WelcomePage.qml @@ -1,4 +1,6 @@ // Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + import QtQuick 2.7 import QtQuick.Controls 2.1 import QtQuick.Window 2.2 From 8206fde2237930de5347a711ae6539055952888f Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 18 Dec 2018 16:24:13 +0100 Subject: [PATCH 14/51] Remove the non-load-only-when-testing check Contributes to CURA-6005. --- plugins/CuraDrive/__init__.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/plugins/CuraDrive/__init__.py b/plugins/CuraDrive/__init__.py index 4103b0cf2e..766d94752f 100644 --- a/plugins/CuraDrive/__init__.py +++ b/plugins/CuraDrive/__init__.py @@ -1,16 +1,10 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import os +from .src.DrivePluginExtension import DrivePluginExtension -is_testing = os.getenv('ENV_NAME', "development") == "testing" +def getMetaData(): + return {} -# Only load the whole plugin when not running tests as __init__.py is automatically loaded by PyTest -if not is_testing: - from .src.DrivePluginExtension import DrivePluginExtension - - def getMetaData(): - return {} - - def register(app): - return {"extension": DrivePluginExtension(app)} +def register(app): + return {"extension": DrivePluginExtension(app)} From 773190bee18dd7e18fefacfe2aca0104a0762897 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 19 Dec 2018 12:53:41 +0100 Subject: [PATCH 15/51] Fix code style Add space between binary operator. Contributes to CURA-6005. --- plugins/CuraDrive/src/DriveApiService.py | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index 7c3e7b7026..a542ac439e 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -43,7 +43,7 @@ class DriveApiService: Logger.log("w", "Could not get access token.") return [] - backup_list_request = requests.get(self.GET_BACKUPS_URL, headers={ + backup_list_request = requests.get(self.GET_BACKUPS_URL, headers = { "Authorization": "Bearer {}".format(access_token) }) if backup_list_request.status_code > 299: @@ -55,12 +55,12 @@ class DriveApiService: def createBackup(self) -> None: """Create a backup and upload it to CuraDrive cloud storage.""" - self.onCreatingStateChanged.emit(is_creating=True) + self.onCreatingStateChanged.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.onCreatingStateChanged.emit(is_creating=False, error_message="Could not create backup.") + self.onCreatingStateChanged.emit(is_creating = False, error_message = "Could not create backup.") return # Create an upload entry for the backup. @@ -68,7 +68,7 @@ class DriveApiService: 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.onCreatingStateChanged.emit(is_creating=False, error_message="Could not upload backup.") + self.onCreatingStateChanged.emit(is_creating = False, error_message = "Could not upload backup.") return # Upload the backup to storage. @@ -83,29 +83,29 @@ class DriveApiService: """ if job.backup_upload_error_message != "": # If the job contains an error message we pass it along so the UI can display it. - self.onCreatingStateChanged.emit(is_creating=False, error_message=job.backup_upload_error_message) + self.onCreatingStateChanged.emit(is_creating = False, error_message = job.backup_upload_error_message) else: - self.onCreatingStateChanged.emit(is_creating=False) + self.onCreatingStateChanged.emit(is_creating = False) def restoreBackup(self, backup: Dict[str, Any]) -> None: """ Restore a previously exported backup from cloud storage. :param backup: A dict containing an entry from the API list response. """ - self.onRestoringStateChanged.emit(is_restoring=True) + self.onRestoringStateChanged.emit(is_restoring = True) 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() - download_package = requests.get(download_url, stream=True) + download_package = requests.get(download_url, stream = True) if download_package.status_code != 200: # 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) + 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) @@ -119,13 +119,13 @@ class DriveApiService: # 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("data")) - self.onRestoringStateChanged.emit(is_restoring=False) + self.onRestoringStateChanged.emit(is_restoring = False) def _emitRestoreError(self, error_message: str = Settings.translatable_messages["backup_restore_error_message"]): """Helper method for emitting a signal when restoring failed.""" self.onRestoringStateChanged.emit( - is_restoring=False, - error_message=error_message + is_restoring = False, + error_message = error_message ) @staticmethod @@ -137,7 +137,7 @@ class DriveApiService: :return: Success or not. """ with open(file_path, "rb") as read_backup: - local_md5_hash = base64.b64encode(hashlib.md5(read_backup.read()).digest(), altchars=b"_-").decode("utf-8") + 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: @@ -171,12 +171,12 @@ class DriveApiService: Logger.log("w", "Could not get access token.") return None - backup_upload_request = requests.put(self.PUT_BACKUP_URL, json={ + backup_upload_request = requests.put(self.PUT_BACKUP_URL, json = { "data": { "backup_size": backup_size, "metadata": backup_metadata } - }, headers={ + }, headers = { "Authorization": "Bearer {}".format(access_token) }) From 136317c3c761e416ddaad78f455fb5c79062ad50 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 3 Jan 2019 11:08:01 +0100 Subject: [PATCH 16/51] Fix code styles CURA-6005 --- resources/qml/ActionButton.qml | 7 +++---- resources/qml/CheckBox.qml | 8 +++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/resources/qml/ActionButton.qml b/resources/qml/ActionButton.qml index 2448b9a551..9e963f8d3e 100644 --- a/resources/qml/ActionButton.qml +++ b/resources/qml/ActionButton.qml @@ -120,12 +120,11 @@ Button visible: text != "" && button.hovered } - BusyIndicator { + BusyIndicator + { id: busyIndicator - anchors { - centerIn: parent - } + anchors.centerIn: parent width: height height: parent.height diff --git a/resources/qml/CheckBox.qml b/resources/qml/CheckBox.qml index 7a79182a4b..403efb4d7b 100644 --- a/resources/qml/CheckBox.qml +++ b/resources/qml/CheckBox.qml @@ -13,11 +13,12 @@ CheckBox property alias tooltip: tooltip.text - indicator: Rectangle { + indicator: Rectangle + { implicitWidth: UM.Theme.getSize("checkbox").width implicitHeight: UM.Theme.getSize("checkbox").height x: 0 - y: Math.round(parent.height / 2 - height / 2) + anchors.verticalCenter: parent.verticalCenter color: UM.Theme.getColor("main_background") radius: UM.Theme.getSize("checkbox_radius").width border.width: UM.Theme.getSize("default_lining").width @@ -37,7 +38,8 @@ CheckBox } } - contentItem: Label { + contentItem: Label + { anchors { left: checkbox.indicator.right From 56c9de44f741f919214e7d7f563876944f5b9bf4 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 3 Jan 2019 11:09:10 +0100 Subject: [PATCH 17/51] Update Cura Backups sdk_version to 6 in bundled json CURA-6005 --- resources/bundled_packages/cura.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/bundled_packages/cura.json b/resources/bundled_packages/cura.json index 896033fb22..2b5750ea8d 100644 --- a/resources/bundled_packages/cura.json +++ b/resources/bundled_packages/cura.json @@ -57,7 +57,7 @@ "display_name": "Cura Backups", "description": "Backup and restore your configuration.", "package_version": "1.2.0", - "sdk_version": 5, + "sdk_version": 6, "website": "https://ultimaker.com", "author": { "author_id": "UltimakerPackages", From a14c846e294acbc169fdca3c5756222ae2088c6a Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 3 Jan 2019 11:53:17 +0100 Subject: [PATCH 18/51] Rename CheckBox.qml to CheckBoxWithTooltip.qml CURA-6005 --- plugins/CuraDrive/src/qml/components/BackupListFooter.qml | 2 +- resources/qml/{CheckBox.qml => CheckBoxWithTooltip.qml} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename resources/qml/{CheckBox.qml => CheckBoxWithTooltip.qml} (100%) diff --git a/plugins/CuraDrive/src/qml/components/BackupListFooter.qml b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml index 2a6d82bc74..a0bc212597 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListFooter.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml @@ -35,7 +35,7 @@ RowLayout busy: CuraDrive.isCreatingBackup } - Cura.CheckBox + Cura.CheckBoxWithTooltip { id: autoBackupEnabled checked: CuraDrive.autoBackupEnabled diff --git a/resources/qml/CheckBox.qml b/resources/qml/CheckBoxWithTooltip.qml similarity index 100% rename from resources/qml/CheckBox.qml rename to resources/qml/CheckBoxWithTooltip.qml From 65183ade0a53deceac8c00413e656aec79e12628 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 3 Jan 2019 16:44:08 +0100 Subject: [PATCH 19/51] No longer chache singleton objects CURA-6005 --- plugins/CuraDrive/__init__.py | 2 +- plugins/CuraDrive/src/DriveApiService.py | 11 ++++------- plugins/CuraDrive/src/DrivePluginExtension.py | 18 ++++++++---------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/plugins/CuraDrive/__init__.py b/plugins/CuraDrive/__init__.py index 766d94752f..dd7ffeaac3 100644 --- a/plugins/CuraDrive/__init__.py +++ b/plugins/CuraDrive/__init__.py @@ -7,4 +7,4 @@ def getMetaData(): return {} def register(app): - return {"extension": DrivePluginExtension(app)} + return {"extension": DrivePluginExtension()} diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index a542ac439e..3b6641cd74 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -12,16 +12,14 @@ import requests from UM.Logger import Logger from UM.Message import Message from UM.Signal import Signal +from cura.CuraApplication import CuraApplication from .UploadBackupJob import UploadBackupJob from .Settings import Settings +## The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling. class DriveApiService: - """ - The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling. - """ - GET_BACKUPS_URL = "{}/backups".format(Settings.DRIVE_API_URL) PUT_BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL) DELETE_BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL) @@ -32,9 +30,8 @@ class DriveApiService: # Emit signal when creating backup started or finished. onCreatingStateChanged = Signal() - def __init__(self, cura_api) -> None: - """Create a new instance of the Drive API service and set the cura_api object.""" - self._cura_api = cura_api + def __init__(self) -> None: + self._cura_api = CuraApplication.getInstance().getCuraAPI() def getBackups(self) -> List[Dict[str, Any]]: """Get all backups from the API.""" diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index a76c623fe8..bed54140d9 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -7,8 +7,10 @@ from typing import Optional from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal +from UM.Application import Application from UM.Extension import Extension from UM.Message import Message +from cura.CuraApplication import CuraApplication from .Settings import Settings from .DriveApiService import DriveApiService @@ -34,11 +36,8 @@ class DrivePluginExtension(QObject, Extension): DATE_FORMAT = "%d/%m/%Y %H:%M:%S" - def __init__(self, application): + def __init__(self): super(DrivePluginExtension, self).__init__() - - # Re-usable instance of application. - self._application = application # Local data caching for the UI. self._drive_window = None # type: Optional[QObject] @@ -47,12 +46,11 @@ class DrivePluginExtension(QObject, Extension): self._is_creating_backup = False # Initialize services. - self._preferences = self._application.getPreferences() - self._cura_api = self._application.getCuraAPI() - self._drive_api_service = DriveApiService(self._cura_api) + self._preferences = CuraApplication.getInstance().getPreferences() + self._drive_api_service = DriveApiService() # Attach signals. - self._cura_api.account.loginStateChanged.connect(self._onLoginStateChanged) + CuraApplication.getInstance().getCuraAPI().account.loginStateChanged.connect(self._onLoginStateChanged) self._drive_api_service.onRestoringStateChanged.connect(self._onRestoringStateChanged) self._drive_api_service.onCreatingStateChanged.connect(self._onCreatingStateChanged) @@ -65,7 +63,7 @@ class DrivePluginExtension(QObject, Extension): self._updateMenuItems() # Make auto-backup on boot if required. - self._application.engineCreatedSignal.connect(self._autoBackup) + CuraApplication.getInstance().engineCreatedSignal.connect(self._autoBackup) def showDriveWindow(self) -> None: """Show the Drive UI popup window.""" @@ -81,7 +79,7 @@ class DrivePluginExtension(QObject, Extension): :return: The popup window object. """ path = os.path.join(os.path.dirname(__file__), "qml", "main.qml") - return self._application.createQmlComponent(path, {"CuraDrive": self}) + return CuraApplication.getInstance().createQmlComponent(path, {"CuraDrive": self}) def _updateMenuItems(self) -> None: """Update the menu items.""" From ea1712df0f120985fb62fb474a698320b4da6726 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 3 Jan 2019 16:51:11 +0100 Subject: [PATCH 20/51] Codestyle fixes & simplifications CURA-6005 --- plugins/CuraDrive/src/DrivePluginExtension.py | 64 ++++--------------- 1 file changed, 11 insertions(+), 53 deletions(-) diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index bed54140d9..cd8274f83a 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -16,6 +16,9 @@ from .Settings import Settings from .DriveApiService import DriveApiService from .models.BackupListModel import BackupListModel +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + class DrivePluginExtension(QObject, Extension): """ @@ -37,7 +40,8 @@ class DrivePluginExtension(QObject, Extension): DATE_FORMAT = "%d/%m/%Y %H:%M:%S" def __init__(self): - super(DrivePluginExtension, self).__init__() + QObject.__init__(self, None) + Extension.__init__(self) # Local data caching for the UI. self._drive_window = None # type: Optional[QObject] @@ -59,8 +63,8 @@ class DrivePluginExtension(QObject, Extension): self._preferences.addPreference(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, datetime.now() .strftime(self.DATE_FORMAT)) - # Register menu items. - self._updateMenuItems() + # Register the menu item + self.addMenuItem(catalog.i18nc("@item:inmenu", "Manage backups"), self.showDriveWindow) # Make auto-backup on boot if required. CuraApplication.getInstance().engineCreatedSignal.connect(self._autoBackup) @@ -68,59 +72,41 @@ class DrivePluginExtension(QObject, Extension): def showDriveWindow(self) -> None: """Show the Drive UI popup window.""" if not self._drive_window: - self._drive_window = self.createDriveWindow() + path = os.path.join(os.path.dirname(__file__), "qml", "main.qml") + self._drive_window = CuraApplication.getInstance().createQmlComponent(path, {"CuraDrive": self}) self.refreshBackups() if self._drive_window: self._drive_window.show() - def createDriveWindow(self) -> Optional["QObject"]: - """ - Create an instance of the Drive UI popup window. - :return: The popup window object. - """ - path = os.path.join(os.path.dirname(__file__), "qml", "main.qml") - return CuraApplication.getInstance().createQmlComponent(path, {"CuraDrive": self}) - - def _updateMenuItems(self) -> None: - """Update the menu items.""" - self.addMenuItem(Settings.translatable_messages["extension_menu_entry"], self.showDriveWindow) - def _autoBackup(self) -> None: - """Automatically make a backup on boot if enabled.""" - if self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._lastBackupTooLongAgo(): + if self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._isLastBackupTooLongAgo(): self.createBackup() - def _lastBackupTooLongAgo(self) -> bool: - """Check if the last backup was longer than 1 day ago.""" + def _isLastBackupTooLongAgo(self) -> bool: current_date = datetime.now() last_backup_date = self._getLastBackupDate() date_diff = current_date - last_backup_date return date_diff.days > 1 def _getLastBackupDate(self) -> "datetime": - """Get the last backup date as datetime object.""" last_backup_date = self._preferences.getValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY) return datetime.strptime(last_backup_date, self.DATE_FORMAT) def _storeBackupDate(self) -> None: - """Store the current date as last backup date.""" backup_date = datetime.now().strftime(self.DATE_FORMAT) self._preferences.setValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, backup_date) def _onLoginStateChanged(self, logged_in: bool = False) -> None: - """Callback handler for changes in the login state.""" if logged_in: self.refreshBackups() def _onRestoringStateChanged(self, is_restoring: bool = False, error_message: str = None) -> None: - """Callback handler for changes in the restoring state.""" self._is_restoring_backup = is_restoring self.restoringStateChanged.emit() if error_message: Message(error_message, title = Settings.MESSAGE_TITLE, lifetime = 5).show() def _onCreatingStateChanged(self, is_creating: bool = False, error_message: str = None) -> None: - """Callback handler for changes in the creation state.""" self._is_creating_backup = is_creating self.creatingStateChanged.emit() if error_message: @@ -133,69 +119,41 @@ class DrivePluginExtension(QObject, Extension): @pyqtSlot(bool, name = "toggleAutoBackup") def toggleAutoBackup(self, enabled: bool) -> None: - """Enable or disable the auto-backup feature.""" self._preferences.setValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, enabled) self.preferencesChanged.emit() @pyqtProperty(bool, notify = preferencesChanged) def autoBackupEnabled(self) -> bool: - """Check if auto-backup is enabled or not.""" return bool(self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY)) @pyqtProperty(QObject, notify = backupsChanged) def backups(self) -> BackupListModel: - """ - Get a list of the backups. - :return: The backups as Qt List Model. - """ return self._backups_list_model @pyqtSlot(name = "refreshBackups") def refreshBackups(self) -> None: - """ - Forcefully refresh the backups list. - """ self._backups_list_model.loadBackups(self._drive_api_service.getBackups()) self.backupsChanged.emit() @pyqtProperty(bool, notify = restoringStateChanged) def isRestoringBackup(self) -> bool: - """ - Get the current restoring state. - :return: Boolean if we are restoring or not. - """ return self._is_restoring_backup @pyqtProperty(bool, notify = creatingStateChanged) def isCreatingBackup(self) -> bool: - """ - Get the current creating state. - :return: Boolean if we are creating or not. - """ return self._is_creating_backup @pyqtSlot(str, name = "restoreBackup") def restoreBackup(self, backup_id: str) -> None: - """ - Download and restore a backup by ID. - :param backup_id: The ID of the backup. - """ index = self._backups_list_model.find("backup_id", backup_id) backup = self._backups_list_model.getItem(index) self._drive_api_service.restoreBackup(backup) @pyqtSlot(name = "createBackup") def createBackup(self) -> None: - """ - Create a new backup. - """ self._drive_api_service.createBackup() @pyqtSlot(str, name = "deleteBackup") def deleteBackup(self, backup_id: str) -> None: - """ - Delete a backup by ID. - :param backup_id: The ID of the backup. - """ self._drive_api_service.deleteBackup(backup_id) self.refreshBackups() From 4d1e9d24f98d3c12e8f91f02d416ab1ddb8287b7 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 3 Jan 2019 16:58:51 +0100 Subject: [PATCH 21/51] Cleanup documentation Either fix the style to doxygen or remove it if it just repeated the functionnname. CURA-6005 --- plugins/CuraDrive/src/DriveApiService.py | 43 +++++++----------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index 3b6641cd74..39045d1317 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -34,7 +34,6 @@ class DriveApiService: self._cura_api = CuraApplication.getInstance().getCuraAPI() def getBackups(self) -> List[Dict[str, Any]]: - """Get all backups from the API.""" access_token = self._cura_api.account.accessToken if not access_token: Logger.log("w", "Could not get access token.") @@ -43,6 +42,7 @@ class DriveApiService: backup_list_request = requests.get(self.GET_BACKUPS_URL, headers = { "Authorization": "Bearer {}".format(access_token) }) + if backup_list_request.status_code > 299: Logger.log("w", "Could not get backups list from remote: %s", backup_list_request.text) Message(Settings.translatable_messages["get_backups_error"], title = Settings.MESSAGE_TITLE, @@ -51,7 +51,6 @@ class DriveApiService: return backup_list_request.json()["data"] def createBackup(self) -> None: - """Create a backup and upload it to CuraDrive cloud storage.""" self.onCreatingStateChanged.emit(is_creating = True) # Create the backup. @@ -74,10 +73,6 @@ class DriveApiService: upload_backup_job.start() def _onUploadFinished(self, job: "UploadBackupJob") -> None: - """ - Callback handler for the upload job. - :param job: The executed job. - """ if job.backup_upload_error_message != "": # If the job contains an error message we pass it along so the UI can display it. self.onCreatingStateChanged.emit(is_creating = False, error_message = job.backup_upload_error_message) @@ -85,10 +80,6 @@ class DriveApiService: self.onCreatingStateChanged.emit(is_creating = False) def restoreBackup(self, backup: Dict[str, Any]) -> None: - """ - Restore a previously exported backup from cloud storage. - :param backup: A dict containing an entry from the API list response. - """ self.onRestoringStateChanged.emit(is_restoring = True) download_url = backup.get("download_url") if not download_url: @@ -119,30 +110,22 @@ class DriveApiService: self.onRestoringStateChanged.emit(is_restoring = False) def _emitRestoreError(self, error_message: str = Settings.translatable_messages["backup_restore_error_message"]): - """Helper method for emitting a signal when restoring failed.""" self.onRestoringStateChanged.emit( is_restoring = False, error_message = error_message ) + # 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. @staticmethod def _verifyMd5Hash(file_path: str, known_hash: str) -> bool: - """ - 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. - """ 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: - """ - Delete a backup from the server by ID. - :param backup_id: The ID of the backup to delete. - :return: Success bool. - """ access_token = self._cura_api.account.accessToken if not access_token: Logger.log("w", "Could not get access token.") @@ -156,13 +139,12 @@ class DriveApiService: 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]: - """ - 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. - """ + access_token = self._cura_api.account.accessToken if not access_token: Logger.log("w", "Could not get access token.") @@ -176,8 +158,9 @@ class DriveApiService: }, headers = { "Authorization": "Bearer {}".format(access_token) }) - - if backup_upload_request.status_code > 299: + + # 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 From ed9a51791b9ed83c517dc5d1ba26b489905078ec Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 3 Jan 2019 17:05:58 +0100 Subject: [PATCH 22/51] Handle bunch of review comments CURA-6005 --- plugins/CuraDrive/src/DriveApiService.py | 23 ++++++++++++----------- plugins/CuraDrive/src/Settings.py | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index 39045d1317..f74f30bcda 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -17,6 +17,9 @@ from cura.CuraApplication import CuraApplication from .UploadBackupJob import UploadBackupJob 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. class DriveApiService: @@ -42,11 +45,10 @@ class DriveApiService: backup_list_request = requests.get(self.GET_BACKUPS_URL, headers = { "Authorization": "Bearer {}".format(access_token) }) - - if backup_list_request.status_code > 299: + + if backup_list_request.status_code >= 300: Logger.log("w", "Could not get backups list from remote: %s", backup_list_request.text) - Message(Settings.translatable_messages["get_backups_error"], title = Settings.MESSAGE_TITLE, - lifetime = 10).show() + Message(catalog.i18nc("@info:backup_status", "There was an error listing your backups."), title = Settings.MESSAGE_TITLE).show() return [] return backup_list_request.json()["data"] @@ -87,7 +89,7 @@ class DriveApiService: return self._emitRestoreError() download_package = requests.get(download_url, stream = True) - if download_package.status_code != 200: + 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() @@ -109,11 +111,10 @@ class DriveApiService: self._cura_api.backups.restoreBackup(read_backup.read(), backup.get("data")) self.onRestoringStateChanged.emit(is_restoring = False) - def _emitRestoreError(self, error_message: str = Settings.translatable_messages["backup_restore_error_message"]): - self.onRestoringStateChanged.emit( - is_restoring = False, - error_message = error_message - ) + def _emitRestoreError(self): + self.onRestoringStateChanged.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. @@ -134,7 +135,7 @@ class DriveApiService: delete_backup = requests.delete("{}/{}".format(self.DELETE_BACKUP_URL, backup_id), headers = { "Authorization": "Bearer {}".format(access_token) }) - if delete_backup.status_code > 299: + if delete_backup.status_code >= 300: Logger.log("w", "Could not delete backup: %s", delete_backup.text) return False return True diff --git a/plugins/CuraDrive/src/Settings.py b/plugins/CuraDrive/src/Settings.py index 04ace8af95..10d1ba4397 100644 --- a/plugins/CuraDrive/src/Settings.py +++ b/plugins/CuraDrive/src/Settings.py @@ -19,7 +19,7 @@ class Settings: I18N_CATALOG_ID = "cura" I18N_CATALOG = i18nCatalog(I18N_CATALOG_ID) - MESSAGE_TITLE = I18N_CATALOG.i18nc("@info:title", "Backups"), + MESSAGE_TITLE = I18N_CATALOG.i18nc("@info:title", "Backups") # Translatable messages for the entire plugin. translatable_messages = { From e36b3e37d1b680b89f2beb2b4af4bd2cdab233ec Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 3 Jan 2019 17:13:47 +0100 Subject: [PATCH 23/51] Remove indirection in the translation CURA-6005 --- plugins/CuraDrive/src/Settings.py | 22 ++-------------------- plugins/CuraDrive/src/UploadBackupJob.py | 14 +++++++------- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/plugins/CuraDrive/src/Settings.py b/plugins/CuraDrive/src/Settings.py index 10d1ba4397..fcb05b8c04 100644 --- a/plugins/CuraDrive/src/Settings.py +++ b/plugins/CuraDrive/src/Settings.py @@ -16,24 +16,6 @@ class Settings: AUTO_BACKUP_ENABLED_PREFERENCE_KEY = "cura_drive/auto_backup_enabled" AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date" - I18N_CATALOG_ID = "cura" - I18N_CATALOG = i18nCatalog(I18N_CATALOG_ID) + I18N_CATALOG = i18nCatalog("cura") - MESSAGE_TITLE = I18N_CATALOG.i18nc("@info:title", "Backups") - - # Translatable messages for the entire plugin. - translatable_messages = { - - # Menu items. - "extension_menu_entry": I18N_CATALOG.i18nc("@item:inmenu", "Manage backups"), - - # Notification messages. - "backup_failed": I18N_CATALOG.i18nc("@info:backup_status", "There was an error while creating your backup."), - "uploading_backup": I18N_CATALOG.i18nc("@info:backup_status", "Uploading your backup..."), - "uploading_backup_success": I18N_CATALOG.i18nc("@info:backup_status", "Your backup has finished uploading."), - "uploading_backup_error": I18N_CATALOG.i18nc("@info:backup_status", - "There was an error while uploading your backup."), - "get_backups_error": I18N_CATALOG.i18nc("@info:backup_status", "There was an error listing your backups."), - "backup_restore_error_message": I18N_CATALOG.i18nc("@info:backup_status", - "There was an error trying to restore your backup.") - } + MESSAGE_TITLE = I18N_CATALOG.i18nc("@info:title", "Backups") \ No newline at end of file diff --git a/plugins/CuraDrive/src/UploadBackupJob.py b/plugins/CuraDrive/src/UploadBackupJob.py index ae6cb13f2e..bdcc8dd8a6 100644 --- a/plugins/CuraDrive/src/UploadBackupJob.py +++ b/plugins/CuraDrive/src/UploadBackupJob.py @@ -8,14 +8,17 @@ from UM.Logger import Logger from UM.Message import Message from .Settings import Settings +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 @@ -24,18 +27,15 @@ class UploadBackupJob(Job): self.backup_upload_error_message = "" def run(self) -> None: - Message(Settings.translatable_messages["uploading_backup"], title = Settings.MESSAGE_TITLE, - lifetime = 10).show() + Message(catalog.i18nc("@info:backup_status", "Uploading your backup..."), title = self.MESSAGE_TITLE).show() backup_upload = requests.put(self._signed_upload_url, data = self._backup_zip) if backup_upload.status_code not in (200, 201): self.backup_upload_error_message = backup_upload.text Logger.log("w", "Could not upload backup file: %s", backup_upload.text) - Message(Settings.translatable_messages["uploading_backup_error"], title = Settings.MESSAGE_TITLE, - lifetime = 10).show() + 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(Settings.translatable_messages["uploading_backup_success"], title = Settings.MESSAGE_TITLE, - lifetime = 10).show() + Message(catalog.i18nc("@info:backup_status", "Your backup has finished uploading."), title = self.MESSAGE_TITLE).show() self.finished.emit(self) From 83fbb78c9ee4437d736696e5e74a9a747daabcad Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 3 Jan 2019 17:15:32 +0100 Subject: [PATCH 24/51] Backup uploading now shows a intermediate progress bar and hides when done CURA-6005 --- plugins/CuraDrive/src/UploadBackupJob.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/CuraDrive/src/UploadBackupJob.py b/plugins/CuraDrive/src/UploadBackupJob.py index bdcc8dd8a6..d9532a96f0 100644 --- a/plugins/CuraDrive/src/UploadBackupJob.py +++ b/plugins/CuraDrive/src/UploadBackupJob.py @@ -27,9 +27,12 @@ class UploadBackupJob(Job): self.backup_upload_error_message = "" def run(self) -> None: - Message(catalog.i18nc("@info:backup_status", "Uploading your backup..."), title = self.MESSAGE_TITLE).show() + 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 not in (200, 201): self.backup_upload_error_message = backup_upload.text Logger.log("w", "Could not upload backup file: %s", backup_upload.text) From 1578aaa301876cce04dfb6f7fc0c5c936fdad56a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 3 Jan 2019 17:17:40 +0100 Subject: [PATCH 25/51] Minor code cleanup CURA-6005 --- plugins/CuraDrive/src/UploadBackupJob.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/CuraDrive/src/UploadBackupJob.py b/plugins/CuraDrive/src/UploadBackupJob.py index d9532a96f0..2e76ed9b4b 100644 --- a/plugins/CuraDrive/src/UploadBackupJob.py +++ b/plugins/CuraDrive/src/UploadBackupJob.py @@ -7,7 +7,6 @@ from UM.Job import Job from UM.Logger import Logger from UM.Message import Message -from .Settings import Settings from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -15,10 +14,8 @@ 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. - """ + # 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 @@ -33,7 +30,7 @@ class UploadBackupJob(Job): backup_upload = requests.put(self._signed_upload_url, data = self._backup_zip) upload_message.hide() - if backup_upload.status_code not in (200, 201): + 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() From 49076a7103715703b9a7aeef591cbf2f74207c88 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 3 Jan 2019 17:42:58 +0100 Subject: [PATCH 26/51] Remove the unneeded BackupListModel CURA-6005 --- plugins/CuraDrive/src/DrivePluginExtension.py | 23 ++++++----- .../CuraDrive/src/models/BackupListModel.py | 40 ------------------- plugins/CuraDrive/src/models/__init__.py | 0 .../src/qml/components/BackupListItem.qml | 10 ++--- .../qml/components/BackupListItemDetails.qml | 10 ++--- 5 files changed, 22 insertions(+), 61 deletions(-) delete mode 100644 plugins/CuraDrive/src/models/BackupListModel.py delete mode 100644 plugins/CuraDrive/src/models/__init__.py diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index cd8274f83a..041c01a14d 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -3,18 +3,17 @@ import os from datetime import datetime -from typing import Optional +from typing import Optional, List from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal -from UM.Application import Application from UM.Extension import Extension +from UM.Logger import Logger from UM.Message import Message from cura.CuraApplication import CuraApplication from .Settings import Settings from .DriveApiService import DriveApiService -from .models.BackupListModel import BackupListModel from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -45,7 +44,7 @@ class DrivePluginExtension(QObject, Extension): # Local data caching for the UI. self._drive_window = None # type: Optional[QObject] - self._backups_list_model = BackupListModel() + self._backups = [] self._is_restoring_backup = False self._is_creating_backup = False @@ -126,13 +125,13 @@ class DrivePluginExtension(QObject, Extension): def autoBackupEnabled(self) -> bool: return bool(self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY)) - @pyqtProperty(QObject, notify = backupsChanged) - def backups(self) -> BackupListModel: - return self._backups_list_model + @pyqtProperty("QVariantList", notify = backupsChanged) + def backups(self) -> List: + return self._backups @pyqtSlot(name = "refreshBackups") def refreshBackups(self) -> None: - self._backups_list_model.loadBackups(self._drive_api_service.getBackups()) + self._backups = self._drive_api_service.getBackups() self.backupsChanged.emit() @pyqtProperty(bool, notify = restoringStateChanged) @@ -145,9 +144,11 @@ class DrivePluginExtension(QObject, Extension): @pyqtSlot(str, name = "restoreBackup") def restoreBackup(self, backup_id: str) -> None: - index = self._backups_list_model.find("backup_id", backup_id) - backup = self._backups_list_model.getItem(index) - self._drive_api_service.restoreBackup(backup) + for backup in self._backups: + if backup.get("backup_id") == backup_id: + self._drive_api_service.restoreBackup(backup) + return + Logger.log("w", "Unable to find backup with the ID %s", backup_id) @pyqtSlot(name = "createBackup") def createBackup(self) -> None: diff --git a/plugins/CuraDrive/src/models/BackupListModel.py b/plugins/CuraDrive/src/models/BackupListModel.py deleted file mode 100644 index 06b256b22c..0000000000 --- a/plugins/CuraDrive/src/models/BackupListModel.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (c) 2018 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from typing import Any, List, Dict - -from UM.Qt.ListModel import ListModel - -from PyQt5.QtCore import Qt - - -class BackupListModel(ListModel): - """ - The BackupListModel transforms the backups data that came from the server so it can be served to the Qt UI. - """ - - def __init__(self, parent = None) -> None: - super().__init__(parent) - self.addRoleName(Qt.UserRole + 1, "backup_id") - self.addRoleName(Qt.UserRole + 2, "download_url") - self.addRoleName(Qt.UserRole + 3, "generated_time") - self.addRoleName(Qt.UserRole + 4, "md5_hash") - self.addRoleName(Qt.UserRole + 5, "data") - - def loadBackups(self, data: List[Dict[str, Any]]) -> None: - """ - Populate the model with server data. - :param data: - """ - items = [] - for backup in data: - # We do this loop because we only want to append these specific fields. - # Without this, ListModel will break. - items.append({ - "backup_id": backup["backup_id"], - "download_url": backup["download_url"], - "generated_time": backup["generated_time"], - "md5_hash": backup["md5_hash"], - "data": backup["metadata"] - }) - self.setItems(items) diff --git a/plugins/CuraDrive/src/models/__init__.py b/plugins/CuraDrive/src/models/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/plugins/CuraDrive/src/qml/components/BackupListItem.qml b/plugins/CuraDrive/src/qml/components/BackupListItem.qml index ba4f1a32a4..0cd897fada 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItem.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItem.qml @@ -44,7 +44,7 @@ Item Label { - text: new Date(model["generated_time"]).toLocaleString(UM.Preferences.getValue("general/language")) + text: new Date(modelData.generated_time).toLocaleString(UM.Preferences.getValue("general/language")) color: UM.Theme.getColor("text") elide: Text.ElideRight Layout.minimumWidth: 100 * screenScaleFactor @@ -55,7 +55,7 @@ Item Label { - text: model["data"]["description"] + text: modelData.metadata.description color: UM.Theme.getColor("text") elide: Text.ElideRight Layout.minimumWidth: 100 * screenScaleFactor @@ -85,7 +85,7 @@ Item BackupListItemDetails { id: backupDetails - backupDetailsData: model + backupDetailsData: modelData width: parent.width visible: parent.showDetails anchors.top: dataRow.bottom @@ -97,7 +97,7 @@ Item title: catalog.i18nc("@dialog:title", "Delete Backup") text: catalog.i18nc("@dialog:info", "Are you sure you want to delete this backup? This cannot be undone.") standardButtons: StandardButton.Yes | StandardButton.No - onYes: CuraDrive.deleteBackup(model["backup_id"]) + onYes: CuraDrive.deleteBackup(modelData.backup_id) } MessageDialog @@ -106,6 +106,6 @@ Item title: catalog.i18nc("@dialog:title", "Restore Backup") text: catalog.i18nc("@dialog:info", "You will need to restart Cura before your backup is restored. Do you want to close Cura now?") standardButtons: StandardButton.Yes | StandardButton.No - onYes: CuraDrive.restoreBackup(model["backup_id"]) + onYes: CuraDrive.restoreBackup(modelData.backup_id) } } diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml index 38a2557c47..e84f1e0982 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml @@ -19,7 +19,7 @@ ColumnLayout { iconSource: "../images/cura.svg" label: catalog.i18nc("@backuplist:label", "Cura Version") - value: backupDetailsData["data"]["cura_release"] + value: backupDetailsData.metadata.cura_release } // Machine count. @@ -27,7 +27,7 @@ ColumnLayout { iconSource: "../images/printer.svg" label: catalog.i18nc("@backuplist:label", "Machines") - value: backupDetailsData["data"]["machine_count"] + value: backupDetailsData.metadata.machine_count } // Meterial count. @@ -35,7 +35,7 @@ ColumnLayout { iconSource: "../images/material.svg" label: catalog.i18nc("@backuplist:label", "Materials") - value: backupDetailsData["data"]["material_count"] + value: backupDetailsData.metadata.material_count } // Meterial count. @@ -43,7 +43,7 @@ ColumnLayout { iconSource: "../images/profile.svg" label: catalog.i18nc("@backuplist:label", "Profiles") - value: backupDetailsData["data"]["profile_count"] + value: backupDetailsData.metadata.profile_count } // Meterial count. @@ -51,7 +51,7 @@ ColumnLayout { iconSource: "../images/plugin.svg" label: catalog.i18nc("@backuplist:label", "Plugins") - value: backupDetailsData["data"]["plugin_count"] + value: backupDetailsData.metadata.plugin_count } // Spacer. From 0edc3f2680d03aa158a6218c30580290e37a6664 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 10:17:06 +0100 Subject: [PATCH 27/51] Make some minor improvements to the qml This implements some of the best practices that weren't used yet for the QML CURA-6005 --- .../qml/components/BackupListItemDetails.qml | 2 +- .../components/BackupListItemDetailsRow.qml | 16 ++--- plugins/CuraDrive/src/qml/components/Icon.qml | 58 ------------------- plugins/CuraDrive/src/qml/main.qml | 2 +- .../CuraDrive/src/qml/pages/BackupsPage.qml | 2 +- .../CuraDrive/src/qml/pages/WelcomePage.qml | 5 +- 6 files changed, 16 insertions(+), 69 deletions(-) delete mode 100644 plugins/CuraDrive/src/qml/components/Icon.qml diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml index e84f1e0982..d07bb50b11 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml @@ -11,7 +11,7 @@ ColumnLayout { id: backupDetails width: parent.width - spacing: 10 * screenScaleFactor + spacing: UM.Theme.getSize("default_margin").width property var backupDetailsData // Cura version diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml index d1c8dd33d2..550bdaefab 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml @@ -13,9 +13,9 @@ RowLayout width: parent.width height: 40 * screenScaleFactor - property var iconSource - property var label - property var value + property alias iconSource: icon.source + property alias label: detailName.text + property alias value: detailValue.text // Spacing. Item @@ -23,16 +23,18 @@ RowLayout width: 40 * screenScaleFactor } - Icon + UM.RecolorImage { + id: icon width: 18 * screenScaleFactor - iconSource: detailsRow.iconSource + height: width + source: "" color: UM.Theme.getColor("text") } Label { - text: detailsRow.label + id: detailName color: UM.Theme.getColor("text") elide: Text.ElideRight Layout.minimumWidth: 50 * screenScaleFactor @@ -43,7 +45,7 @@ RowLayout Label { - text: detailsRow.value + id: detailValue color: UM.Theme.getColor("text") elide: Text.ElideRight Layout.minimumWidth: 50 * screenScaleFactor diff --git a/plugins/CuraDrive/src/qml/components/Icon.qml b/plugins/CuraDrive/src/qml/components/Icon.qml deleted file mode 100644 index 8d559bc2b4..0000000000 --- a/plugins/CuraDrive/src/qml/components/Icon.qml +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.7 -import QtQuick.Controls 2.1 -import QtGraphicalEffects 1.0 - -Item -{ - id: icon - width: parent.height - height: width - property var color: "transparent" - property var iconSource - property bool animated: false - - Image - { - id: iconImage - width: parent.height - height: width - smooth: true - source: icon.iconSource - sourceSize.width: width - sourceSize.height: height - antialiasing: true - visible: !icon.animated - } - - AnimatedImage - { - id: animatedIconImage - width: parent.height - height: width - smooth: true - antialiasing: true - source: "../images/loading.gif" - visible: icon.animated - } - - ColorOverlay - { - anchors.fill: iconImage - source: iconImage - color: icon.color - antialiasing: true - visible: !icon.animated - } - - ColorOverlay - { - anchors.fill: animatedIconImage - source: animatedIconImage - color: icon.color - antialiasing: true - visible: icon.animated - } -} diff --git a/plugins/CuraDrive/src/qml/main.qml b/plugins/CuraDrive/src/qml/main.qml index e8a49a49e5..48bf3b6ea4 100644 --- a/plugins/CuraDrive/src/qml/main.qml +++ b/plugins/CuraDrive/src/qml/main.qml @@ -27,7 +27,7 @@ Window UM.I18nCatalog { id: catalog - name: "cura_drive" + name: "cura" } WelcomePage diff --git a/plugins/CuraDrive/src/qml/pages/BackupsPage.qml b/plugins/CuraDrive/src/qml/pages/BackupsPage.qml index 0a7b00d2cb..3b905a4a39 100644 --- a/plugins/CuraDrive/src/qml/pages/BackupsPage.qml +++ b/plugins/CuraDrive/src/qml/pages/BackupsPage.qml @@ -18,7 +18,7 @@ Item ColumnLayout { - spacing: UM.Theme.getSize("default_margin").height * 2 + spacing: UM.Theme.getSize("wide_margin").height width: parent.width anchors.fill: parent diff --git a/plugins/CuraDrive/src/qml/pages/WelcomePage.qml b/plugins/CuraDrive/src/qml/pages/WelcomePage.qml index 19eecedf28..0b207bc170 100644 --- a/plugins/CuraDrive/src/qml/pages/WelcomePage.qml +++ b/plugins/CuraDrive/src/qml/pages/WelcomePage.qml @@ -10,12 +10,14 @@ import Cura 1.1 as Cura import "../components" + Column { id: welcomePage spacing: UM.Theme.getSize("wide_margin").height width: parent.width - topPadding: 150 * screenScaleFactor + height: childrenRect.height + anchors.centerIn: parent Image { @@ -51,3 +53,4 @@ Column fixedWidthMode: true } } + From 6492b51d7cbbefa0123f0c86bf9b4a984fd127b6 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 10:25:22 +0100 Subject: [PATCH 28/51] Remove duplicated icons CURA-6005 --- .../src/qml/components/BackupListItemDetails.qml | 12 ++++++------ plugins/CuraDrive/src/qml/images/material.svg | 7 ------- plugins/CuraDrive/src/qml/images/plugin.svg | 7 ------- plugins/CuraDrive/src/qml/images/profile.svg | 3 --- 4 files changed, 6 insertions(+), 23 deletions(-) delete mode 100644 plugins/CuraDrive/src/qml/images/material.svg delete mode 100644 plugins/CuraDrive/src/qml/images/plugin.svg delete mode 100644 plugins/CuraDrive/src/qml/images/profile.svg diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml index d07bb50b11..cf365501ec 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml @@ -30,26 +30,26 @@ ColumnLayout value: backupDetailsData.metadata.machine_count } - // Meterial count. + // Material count BackupListItemDetailsRow { - iconSource: "../images/material.svg" + iconSource: UM.Theme.getIcon("category_material") label: catalog.i18nc("@backuplist:label", "Materials") value: backupDetailsData.metadata.material_count } - // Meterial count. + // Profile count. BackupListItemDetailsRow { - iconSource: "../images/profile.svg" + iconSource: UM.Theme.getIcon("profile") label: catalog.i18nc("@backuplist:label", "Profiles") value: backupDetailsData.metadata.profile_count } - // Meterial count. + // Plugin count. BackupListItemDetailsRow { - iconSource: "../images/plugin.svg" + iconSource: UM.Theme.getIcon("plugin") label: catalog.i18nc("@backuplist:label", "Plugins") value: backupDetailsData.metadata.plugin_count } diff --git a/plugins/CuraDrive/src/qml/images/material.svg b/plugins/CuraDrive/src/qml/images/material.svg deleted file mode 100644 index eac724e471..0000000000 --- a/plugins/CuraDrive/src/qml/images/material.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/plugin.svg b/plugins/CuraDrive/src/qml/images/plugin.svg deleted file mode 100644 index 674eb99a54..0000000000 --- a/plugins/CuraDrive/src/qml/images/plugin.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/profile.svg b/plugins/CuraDrive/src/qml/images/profile.svg deleted file mode 100644 index ec2130f3d6..0000000000 --- a/plugins/CuraDrive/src/qml/images/profile.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file From 551f07552789505f4c456bcb4d35d6be4572ab15 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 10:27:41 +0100 Subject: [PATCH 29/51] Removed unneeded destinction between get/put/delete URL They are all the same, so no real need to keep this seperate. CURA-6005 --- plugins/CuraDrive/src/DriveApiService.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index f74f30bcda..d9185cc1d2 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -23,9 +23,7 @@ catalog = i18nCatalog("cura") ## The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling. class DriveApiService: - GET_BACKUPS_URL = "{}/backups".format(Settings.DRIVE_API_URL) - PUT_BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL) - DELETE_BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL) + BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL) # Emit signal when restoring backup started or finished. onRestoringStateChanged = Signal() @@ -42,7 +40,7 @@ class DriveApiService: Logger.log("w", "Could not get access token.") return [] - backup_list_request = requests.get(self.GET_BACKUPS_URL, headers = { + backup_list_request = requests.get(self.BACKUP_URL, headers = { "Authorization": "Bearer {}".format(access_token) }) @@ -132,7 +130,7 @@ class DriveApiService: Logger.log("w", "Could not get access token.") return False - delete_backup = requests.delete("{}/{}".format(self.DELETE_BACKUP_URL, backup_id), headers = { + delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = { "Authorization": "Bearer {}".format(access_token) }) if delete_backup.status_code >= 300: @@ -151,7 +149,7 @@ class DriveApiService: Logger.log("w", "Could not get access token.") return None - backup_upload_request = requests.put(self.PUT_BACKUP_URL, json = { + backup_upload_request = requests.put(self.BACKUP_URL, json = { "data": { "backup_size": backup_size, "metadata": backup_metadata From 31156d623017f7a6a00a4cba1a574b4a86230037 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 10:28:54 +0100 Subject: [PATCH 30/51] Add missing signalemitter decorater CURA-6005 --- plugins/CuraDrive/src/DriveApiService.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index d9185cc1d2..d9116eccb2 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -11,7 +11,7 @@ import requests from UM.Logger import Logger from UM.Message import Message -from UM.Signal import Signal +from UM.Signal import Signal, signalemitter from cura.CuraApplication import CuraApplication from .UploadBackupJob import UploadBackupJob @@ -22,6 +22,7 @@ catalog = i18nCatalog("cura") ## The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling. +@signalemitter class DriveApiService: BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL) From 8ebd4282fd661582e144531f75ca8ee833841c50 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 10:39:39 +0100 Subject: [PATCH 31/51] Make the backupList use a scrollview instead of manually re-doing it CURA-6005 --- .../src/qml/components/BackupList.qml | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/plugins/CuraDrive/src/qml/components/BackupList.qml b/plugins/CuraDrive/src/qml/components/BackupList.qml index af7e72b6e6..10ca15afe5 100644 --- a/plugins/CuraDrive/src/qml/components/BackupList.qml +++ b/plugins/CuraDrive/src/qml/components/BackupList.qml @@ -2,32 +2,35 @@ // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.7 -import QtQuick.Controls 2.1 +import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import UM 1.1 as UM -ListView +ScrollView { - id: backupList + property alias model: backupList.model width: parent.width - clip: true - delegate: Item + ListView { + id: backupList width: parent.width - height: childrenRect.height - - BackupListItem - { - id: backupListItem - width: parent.width - UM.Theme.getSize("default_margin").width // Add a margin, otherwise the scrollbar is be on top of the right most component - } - - Divider + delegate: Item { width: parent.width - anchors.top: backupListItem.bottom + height: childrenRect.height + + BackupListItem + { + id: backupListItem + width: parent.width + } + + Divider + { + width: parent.width + anchors.top: backupListItem.bottom + } } } - ScrollBar.vertical: RightSideScrollBar {} } From 87eb8634512f8f97a191d5074b2f3e07e700487f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 10:43:40 +0100 Subject: [PATCH 32/51] Remove the final traces of the translation indirection CURA-6005 --- plugins/CuraDrive/src/DriveApiService.py | 2 +- plugins/CuraDrive/src/DrivePluginExtension.py | 4 ++-- plugins/CuraDrive/src/Settings.py | 10 ++-------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index d9116eccb2..7ca009018b 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -47,7 +47,7 @@ class DriveApiService: 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 = Settings.MESSAGE_TITLE).show() + Message(catalog.i18nc("@info:backup_status", "There was an error listing your backups."), title = catalog.i18nc("@info:title", "Backup")).show() return [] return backup_list_request.json()["data"] diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index 041c01a14d..edd89745ea 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -103,13 +103,13 @@ class DrivePluginExtension(QObject, Extension): self._is_restoring_backup = is_restoring self.restoringStateChanged.emit() if error_message: - Message(error_message, title = Settings.MESSAGE_TITLE, lifetime = 5).show() + Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show() def _onCreatingStateChanged(self, is_creating: bool = False, error_message: str = None) -> None: self._is_creating_backup = is_creating self.creatingStateChanged.emit() if error_message: - Message(error_message, title = Settings.MESSAGE_TITLE, lifetime = 5).show() + Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show() else: self._storeBackupDate() if not is_creating: diff --git a/plugins/CuraDrive/src/Settings.py b/plugins/CuraDrive/src/Settings.py index fcb05b8c04..c5383555b2 100644 --- a/plugins/CuraDrive/src/Settings.py +++ b/plugins/CuraDrive/src/Settings.py @@ -7,15 +7,9 @@ from cura import UltimakerCloudAuthentication class Settings: - """ - Keeps the application settings. - """ + # Keeps the plugin settings. DRIVE_API_VERSION = 1 DRIVE_API_URL = "{}/cura-drive/v{}".format(UltimakerCloudAuthentication.CuraCloudAPIRoot, str(DRIVE_API_VERSION)) AUTO_BACKUP_ENABLED_PREFERENCE_KEY = "cura_drive/auto_backup_enabled" - AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date" - - I18N_CATALOG = i18nCatalog("cura") - - MESSAGE_TITLE = I18N_CATALOG.i18nc("@info:title", "Backups") \ No newline at end of file + AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date" \ No newline at end of file From 63c2a901bb54a9f50cbdec259d06ae969192419e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 10:45:24 +0100 Subject: [PATCH 33/51] Prevent double emits / recalculations when not needed CURA-6005 --- plugins/CuraDrive/src/DrivePluginExtension.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index edd89745ea..d1ea34f4a5 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -112,14 +112,13 @@ class DrivePluginExtension(QObject, Extension): Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show() else: self._storeBackupDate() - if not is_creating: + if not is_creating and not error_message: # We've finished creating a new backup, to the list has to be updated. self.refreshBackups() @pyqtSlot(bool, name = "toggleAutoBackup") def toggleAutoBackup(self, enabled: bool) -> None: self._preferences.setValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, enabled) - self.preferencesChanged.emit() @pyqtProperty(bool, notify = preferencesChanged) def autoBackupEnabled(self) -> bool: From 23315260ae1870f393f720393e8b3d5af6df0c47 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 10:49:41 +0100 Subject: [PATCH 34/51] Fix some of the documentation CURA-6005 --- plugins/CuraDrive/src/DrivePluginExtension.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index d1ea34f4a5..ce9a6651d5 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -18,11 +18,8 @@ from .DriveApiService import DriveApiService from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") - +# The DivePluginExtension provides functionality to backup and restore your Cura configuration to Ultimaker's cloud. class DrivePluginExtension(QObject, Extension): - """ - The DivePluginExtension provides functionality to backup and restore your Cura configuration to Ultimaker's cloud. - """ # Signal emitted when the list of backups changed. backupsChanged = pyqtSignal() @@ -38,7 +35,7 @@ class DrivePluginExtension(QObject, Extension): DATE_FORMAT = "%d/%m/%Y %H:%M:%S" - def __init__(self): + def __init__(self) -> None: QObject.__init__(self, None) Extension.__init__(self) @@ -69,7 +66,6 @@ class DrivePluginExtension(QObject, Extension): CuraApplication.getInstance().engineCreatedSignal.connect(self._autoBackup) def showDriveWindow(self) -> None: - """Show the Drive UI popup window.""" if not self._drive_window: path = os.path.join(os.path.dirname(__file__), "qml", "main.qml") self._drive_window = CuraApplication.getInstance().createQmlComponent(path, {"CuraDrive": self}) From 3b498d3a34cbfd1a1abd3ae909cec8383ec5f8b1 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 10:51:07 +0100 Subject: [PATCH 35/51] Change name of the signals so they are consistent with rest of the code CURA-6005 --- plugins/CuraDrive/src/DriveApiService.py | 22 +++++++++---------- plugins/CuraDrive/src/DrivePluginExtension.py | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index 7ca009018b..3bb416fce6 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -27,10 +27,10 @@ class DriveApiService: BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL) # Emit signal when restoring backup started or finished. - onRestoringStateChanged = Signal() + restoringStateChanged = Signal() # Emit signal when creating backup started or finished. - onCreatingStateChanged = Signal() + creatingStateChanged = Signal() def __init__(self) -> None: self._cura_api = CuraApplication.getInstance().getCuraAPI() @@ -52,12 +52,12 @@ class DriveApiService: return backup_list_request.json()["data"] def createBackup(self) -> None: - self.onCreatingStateChanged.emit(is_creating = True) + 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.onCreatingStateChanged.emit(is_creating = False, error_message = "Could not create backup.") + self.creatingStateChanged.emit(is_creating = False, error_message ="Could not create backup.") return # Create an upload entry for the backup. @@ -65,7 +65,7 @@ class DriveApiService: 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.onCreatingStateChanged.emit(is_creating = False, error_message = "Could not upload backup.") + self.creatingStateChanged.emit(is_creating = False, error_message ="Could not upload backup.") return # Upload the backup to storage. @@ -76,12 +76,12 @@ class DriveApiService: def _onUploadFinished(self, job: "UploadBackupJob") -> 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.onCreatingStateChanged.emit(is_creating = False, error_message = job.backup_upload_error_message) + self.creatingStateChanged.emit(is_creating = False, error_message = job.backup_upload_error_message) else: - self.onCreatingStateChanged.emit(is_creating = False) + self.creatingStateChanged.emit(is_creating = False) def restoreBackup(self, backup: Dict[str, Any]) -> None: - self.onRestoringStateChanged.emit(is_restoring = True) + self.restoringStateChanged.emit(is_restoring = True) download_url = backup.get("download_url") if not download_url: # If there is no download URL, we can't restore the backup. @@ -108,11 +108,11 @@ class DriveApiService: # 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("data")) - self.onRestoringStateChanged.emit(is_restoring = False) + self.restoringStateChanged.emit(is_restoring = False) def _emitRestoreError(self): - self.onRestoringStateChanged.emit(is_restoring = False, - error_message = catalog.i18nc("@info:backup_status", + 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. diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index ce9a6651d5..8944b9a980 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -51,8 +51,8 @@ class DrivePluginExtension(QObject, Extension): # Attach signals. CuraApplication.getInstance().getCuraAPI().account.loginStateChanged.connect(self._onLoginStateChanged) - self._drive_api_service.onRestoringStateChanged.connect(self._onRestoringStateChanged) - self._drive_api_service.onCreatingStateChanged.connect(self._onCreatingStateChanged) + self._drive_api_service.restoringStateChanged.connect(self._onRestoringStateChanged) + self._drive_api_service.creatingStateChanged.connect(self._onCreatingStateChanged) # Register preferences. self._preferences.addPreference(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, False) From 0ec0b68360d7029349702f0e7099e9c8e28087bb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 10:54:23 +0100 Subject: [PATCH 36/51] Remove unneeded component CURA-6005 --- .../src/qml/components/RightSideScrollBar.qml | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml diff --git a/plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml b/plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml deleted file mode 100644 index 5ef8558ee7..0000000000 --- a/plugins/CuraDrive/src/qml/components/RightSideScrollBar.qml +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.7 -import QtQuick.Controls 2.1 -import QtQuick.Layouts 1.3 - -ScrollBar -{ - active: true - size: parent.height - anchors.top: parent.top - anchors.right: parent.right - anchors.bottom: parent.bottom -} From 74087ade05117ee2c048de387de023cfdd69c1a1 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 10:58:51 +0100 Subject: [PATCH 37/51] Remove a number of orphaned files CURA-6005 --- .../CuraDrive/src/qml/images/avatar_default.png | Bin 3115 -> 0 bytes plugins/CuraDrive/src/qml/images/background.svg | 12 ------------ plugins/CuraDrive/src/qml/images/cura_logo.jpg | Bin 19308 -> 0 bytes plugins/CuraDrive/src/qml/images/cura_logo.png | Bin 13258 -> 0 bytes plugins/CuraDrive/src/qml/images/folder.svg | 7 ------- plugins/CuraDrive/src/qml/images/home.svg | 3 --- .../src/qml/images/inverted_circle.png | Bin 1608 -> 0 bytes plugins/CuraDrive/src/qml/images/restore.svg | 7 ------- 8 files changed, 29 deletions(-) delete mode 100644 plugins/CuraDrive/src/qml/images/avatar_default.png delete mode 100644 plugins/CuraDrive/src/qml/images/background.svg delete mode 100644 plugins/CuraDrive/src/qml/images/cura_logo.jpg delete mode 100644 plugins/CuraDrive/src/qml/images/cura_logo.png delete mode 100644 plugins/CuraDrive/src/qml/images/folder.svg delete mode 100644 plugins/CuraDrive/src/qml/images/home.svg delete mode 100644 plugins/CuraDrive/src/qml/images/inverted_circle.png delete mode 100644 plugins/CuraDrive/src/qml/images/restore.svg diff --git a/plugins/CuraDrive/src/qml/images/avatar_default.png b/plugins/CuraDrive/src/qml/images/avatar_default.png deleted file mode 100644 index 0c306680f721dba0de73fb7be90fe5c548608692..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3115 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 - - - Polygon 18 - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/cura_logo.jpg b/plugins/CuraDrive/src/qml/images/cura_logo.jpg deleted file mode 100644 index 621c03f035cddb3c9337099c25bccf19a5866968..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19308 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 V{_(E9+wT8(SKsZ~f4r-A{|^$iZYBT# diff --git a/plugins/CuraDrive/src/qml/images/cura_logo.png b/plugins/CuraDrive/src/qml/images/cura_logo.png deleted file mode 100644 index f846f2a0f0934f4843c3279ed741c149c77b2e1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13258 zcmeHu;4V*)BSRu^XaTvbLN?uz4uJ)2XzHpY!LRVSFdoD6lJwuy+WyZdCve~ygc*u(2ZXX z6n8BJ=~tEGlzXpU@kS`gN`3M{In2g&`K-Ng?48+YOUK6%Q!&j{8UO%f(B`2OjGe6E z=pFl$ws^S=Htqi&Eu+nt`f5zkAyuM|<9+lUC7ObBbhAJ)l>)Q49oq{+g+>xugU({Y z!PhW}n#xpMueQi{{k(Xgq^qIfb|t>U@SfRMr>zd5Z{lC^?1wlPJ#bg}3RdVQbU~*? z`TxiNdl#7g_x2?FIG|)>n5~LZ=;-U{r$9xWcyi_~xxLz49$qf=d`tG6X zIStGm9v<#dC-s0$b#qRMgsHFQp^FeRDg%|8>pO3^vnue#Z%ltH62Zo)abYu)lLUvz zhB9bh6s{bH^OfNC;bPHuwsUnckKTeN_+-^P3q;Rl_BXG`4v(a2lIvSvy%OYFLvt*Z39TCgr_a$F=Mp8M>*tj%kWC~v)94VFN5 z1qXZl?GEiRi;f`o`qcv{-e76%eFdNVsQd3-SZTWP)QI7xn+|JN!Q5xxAATLlA*Y)u z8pe!KMjd{a`|_;R{4@b4QtZ`tcX#VkMd;L<4<)IL{^KurzKG;6p8r3E0*#`BRc1%Z4{vHItk9179tO)8Rr)A_% z^@S+;h%bJ(TWOfDQ?c0pN|yDfn~1yLxnevG5wggjcb=tILHvFOzr@qF%`oJMOSJ+{ zvx|e^`kBeW*=VvqpXlg0-=GhY0MM;vt$z~ATSwB$WVr;veV2t$H*iB= z9Qz*R+wd#7o~OW+AvB*R9cy`8kkDbTmzQzWzP^*cskOBg%~EfKP~j8Ris_&aPFLcN zUi#uzboWne?!(MqzJEh8^?ZVidSPC0n3@71p>*8%Z)6$;fFKy%0QQAvykQl%ipl86D zp6LP5w;D0G`6p$=CEh&yyO5|8W$0;Ch)sve$3xGmuBW@dNk;N!XYs*xus)kGap#&jjLnRa>E$AcC6{{L0hB2p z(d5UzFVqZCgKNN3ck+4~>(E_#s08-L_qzM?y77I))HmP8By}+g-%woX6(1QP(8wUT z8_$T)hmPi*wF8sEf`U=ZECL~5A8}a?!?V`I{7*d4ZwX7Lx}uJP?o@<9_<=V#dT;p< zkU)+;E_S^HTK+lpxLcUa<4?$|o6Zp!vVcu&0D#Dwt;O`Ed(S5i;&3`TIyU^WPfJ>o zMJ{QDzv6}BI!lIRtO*usuLGIrp1YIl0XL^Co4C=jrpf69aHA(>N7tQyTpu5(GS~d5%UqzuqpMu?ZR; zn5YT+op(z-ohE~l$Y}oqN`(%5@MOS)F7%$_xUvh1wzo$U0txB7mObYxX2K4(z=BQ| zLm$e;sDrk?iu{re_7*`Ura)P?pJy5L-*KN1(;Z=9a~CixFsrkfDi}Z}`u3eqNJx

{tidI^DPfL*th*F*+_ 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 diff --git a/plugins/CuraDrive/src/qml/images/folder.svg b/plugins/CuraDrive/src/qml/images/folder.svg deleted file mode 100644 index f66f83a888..0000000000 --- a/plugins/CuraDrive/src/qml/images/folder.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/home.svg b/plugins/CuraDrive/src/qml/images/home.svg deleted file mode 100644 index 9d0e4d802c..0000000000 --- a/plugins/CuraDrive/src/qml/images/home.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/inverted_circle.png b/plugins/CuraDrive/src/qml/images/inverted_circle.png deleted file mode 100644 index 3612b37d4d38cd7b1218be42c943932a79f7b465..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1608 zcmV-O2DkZ%P)Px*0!c(cRCodHojY&bMihm$2^^$|ogzu+GJuKziK{dQ0@$6KbS?t}`UTGaabp-t z;i~kgR zm9(f#Bk`Hh#bQJToQuwp^t1Gf^xI;wNV0G!$A#wim2^+K zFMT83l8Q%Z1I|R?x%5mr7Vv*8h{%xl)y`e%JL#eHu~Z~V7{F5fT6!wI6!f=6GOrqy z86Rr&Tj`PXKw@pN3pPPU@weJJl)kXbZnu``Th&MUY7%<}t&q$U>GR!il>0P&t@@nZ zh};GoD-m{nlMMPUkwdn~WxyZm@quK!J<0Kd99kqasqEGcb@~(JT5IK!cP27c-S#u#kG0(8mB)x!EuI zv3W3&cHSKAY5+T(*>3Y==1IR!KzbqHGm%;v-v;2 z4RYK2u#!6cSnF!Qxky!b|3W&lo2<^=jwe1)mBu@+orz~NMqnp!_)z@gNL*nofa?r5*u znHuo8zp#~HypGs_SMis8IxPW_f3>Esk@uq6KaHk+GChGN|X7 z)YpLLr7x7tnD=OAT`}g0@KppSG~}9j4+8?Q4&1{|_)ITw&Af*J0a!~gU+;vEtZt4l z$ZzybRIHEaXxImETabq>ffaLB1BD-WvxU#5{|?|8IhXvfv%LWvraosAbfRrilDiR& zbC`5<74ws&0h7*T+MRa;3j5~MnScA#mXalmQ=Fbpry4LcAnp|E$84L0IpEm`5cgIC|7%EK z$vhZv->_kCYDHkhoYg?#mnIr67UI3WG%@T0Fu^a*H8KI#04KsGh^8rI?O{XU9qW}3 z%fEhuh5>w$-g<)uCETeZ?va-OjA6Zs+8V&9-=r77WEzU`k9FqtoiMNaZK|2TGh6X( zy-gJp{6_6eCcqZJ^@!I1-t$rg84M8D&ZP=4!HabyHUXx9jpMR4fX}cN_+2xJOn_y@ zRD))0Gk{Nd!KMTTh-0sUO~Iyv1bvwGECjN5$udmvqJ0AvAMqPpv=0-!Y-LXqU{d?E`(IhHzx!sYx(+Mf - - - - - - \ No newline at end of file From add5f17ae75bd23734142875e348f4aa2574b774 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 11:06:43 +0100 Subject: [PATCH 38/51] And removed even more unneeded / duplicate / orphaned images CURA-6005 --- .../src/qml/components/BackupListFooter.qml | 2 +- .../src/qml/components/BackupListItemDetails.qml | 6 +++--- plugins/CuraDrive/src/qml/images/backup.svg | 3 --- plugins/CuraDrive/src/qml/images/cura.svg | 7 ------- .../CuraDrive/src/qml/images/preview_banner.png | Bin 8324 -> 0 bytes plugins/CuraDrive/src/qml/images/printer.svg | 14 -------------- 6 files changed, 4 insertions(+), 28 deletions(-) delete mode 100644 plugins/CuraDrive/src/qml/images/backup.svg delete mode 100644 plugins/CuraDrive/src/qml/images/cura.svg delete mode 100644 plugins/CuraDrive/src/qml/images/preview_banner.png delete mode 100644 plugins/CuraDrive/src/qml/images/printer.svg diff --git a/plugins/CuraDrive/src/qml/components/BackupListFooter.qml b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml index a0bc212597..56706b9990 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListFooter.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListFooter.qml @@ -29,7 +29,7 @@ RowLayout { id: createBackupButton text: catalog.i18nc("@button", "Backup Now") - iconSource: "../images/backup.svg" + iconSource: UM.Theme.getIcon("plus") enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup && !backupListFooter.showInfoButton onClicked: CuraDrive.createBackup() busy: CuraDrive.isCreatingBackup diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml index cf365501ec..2f2dd48e13 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml @@ -17,7 +17,7 @@ ColumnLayout // Cura version BackupListItemDetailsRow { - iconSource: "../images/cura.svg" + iconSource: UM.Theme.getIcon("application") label: catalog.i18nc("@backuplist:label", "Cura Version") value: backupDetailsData.metadata.cura_release } @@ -25,7 +25,7 @@ ColumnLayout // Machine count. BackupListItemDetailsRow { - iconSource: "../images/printer.svg" + iconSource: UM.Theme.getIcon("printer_single") label: catalog.i18nc("@backuplist:label", "Machines") value: backupDetailsData.metadata.machine_count } @@ -41,7 +41,7 @@ ColumnLayout // Profile count. BackupListItemDetailsRow { - iconSource: UM.Theme.getIcon("profile") + iconSource: UM.Theme.getIcon("settings") label: catalog.i18nc("@backuplist:label", "Profiles") value: backupDetailsData.metadata.profile_count } diff --git a/plugins/CuraDrive/src/qml/images/backup.svg b/plugins/CuraDrive/src/qml/images/backup.svg deleted file mode 100644 index 51f6be4cba..0000000000 --- a/plugins/CuraDrive/src/qml/images/backup.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/cura.svg b/plugins/CuraDrive/src/qml/images/cura.svg deleted file mode 100644 index 6b1b6c0c79..0000000000 --- a/plugins/CuraDrive/src/qml/images/cura.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/plugins/CuraDrive/src/qml/images/preview_banner.png b/plugins/CuraDrive/src/qml/images/preview_banner.png deleted file mode 100644 index 414019531beed2e503e05cc394fc7e6de246830a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8324 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 E2O#6GiU0rr diff --git a/plugins/CuraDrive/src/qml/images/printer.svg b/plugins/CuraDrive/src/qml/images/printer.svg deleted file mode 100644 index f7dc83987d..0000000000 --- a/plugins/CuraDrive/src/qml/images/printer.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - icn_singlePrinter - Created with Sketch. - - - - - - - - - \ No newline at end of file From 98153769ff64c306bcf7f6997c10875a2dffbe20 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 11:23:34 +0100 Subject: [PATCH 39/51] Use wide_margin instead of multiplying default margin CURA-6005 --- plugins/CuraDrive/src/qml/pages/BackupsPage.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CuraDrive/src/qml/pages/BackupsPage.qml b/plugins/CuraDrive/src/qml/pages/BackupsPage.qml index 3b905a4a39..0ba0cae09b 100644 --- a/plugins/CuraDrive/src/qml/pages/BackupsPage.qml +++ b/plugins/CuraDrive/src/qml/pages/BackupsPage.qml @@ -14,7 +14,7 @@ Item { id: backupsPage anchors.fill: parent - anchors.margins: UM.Theme.getSize("default_margin").width * 3 + anchors.margins: UM.Theme.getSize("wide_margin").width ColumnLayout { From b4911478e343dfcb0dde658014b20181790a40da Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 11:35:34 +0100 Subject: [PATCH 40/51] Made the plugin depend more on the theme CURA-6005 --- plugins/CuraDrive/src/qml/components/BackupListItem.qml | 2 ++ .../src/qml/components/BackupListItemDetails.qml | 2 +- .../src/qml/components/BackupListItemDetailsRow.qml | 8 ++------ 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/CuraDrive/src/qml/components/BackupListItem.qml b/plugins/CuraDrive/src/qml/components/BackupListItem.qml index 0cd897fada..5cdb500b4e 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItem.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItem.qml @@ -50,6 +50,7 @@ Item Layout.minimumWidth: 100 * screenScaleFactor Layout.maximumWidth: 500 * screenScaleFactor Layout.fillWidth: true + font: UM.Theme.getFont("default") renderType: Text.NativeRendering } @@ -61,6 +62,7 @@ Item Layout.minimumWidth: 100 * screenScaleFactor Layout.maximumWidth: 500 * screenScaleFactor Layout.fillWidth: true + font: UM.Theme.getFont("default") renderType: Text.NativeRendering } diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml index 2f2dd48e13..4da15c6f16 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetails.qml @@ -58,6 +58,6 @@ ColumnLayout Item { width: parent.width - height: 10 * screenScaleFactor + height: UM.Theme.getSize("default_margin").height } } diff --git a/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml b/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml index 550bdaefab..9e4612fcf8 100644 --- a/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml +++ b/plugins/CuraDrive/src/qml/components/BackupListItemDetailsRow.qml @@ -17,12 +17,6 @@ RowLayout property alias label: detailName.text property alias value: detailValue.text - // Spacing. - Item - { - width: 40 * screenScaleFactor - } - UM.RecolorImage { id: icon @@ -40,6 +34,7 @@ RowLayout Layout.minimumWidth: 50 * screenScaleFactor Layout.maximumWidth: 100 * screenScaleFactor Layout.fillWidth: true + font: UM.Theme.getFont("default") renderType: Text.NativeRendering } @@ -51,6 +46,7 @@ RowLayout Layout.minimumWidth: 50 * screenScaleFactor Layout.maximumWidth: 100 * screenScaleFactor Layout.fillWidth: true + font: UM.Theme.getFont("default") renderType: Text.NativeRendering } } From c10b8b5c3f5f97a3c08be797614bd6e03662422c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 11:37:52 +0100 Subject: [PATCH 41/51] Fix crash when attempting to restore backup CURA-6005 --- plugins/CuraDrive/src/DriveApiService.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index 3bb416fce6..79f49bf1a3 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -107,7 +107,7 @@ class DriveApiService: # 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("data")) + self._cura_api.backups.restoreBackup(read_backup.read(), backup.get("metadata", {})) self.restoringStateChanged.emit(is_restoring = False) def _emitRestoreError(self): From 3df9b369f7aaa4aaddee974cfd2449cecd69064d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 11:40:09 +0100 Subject: [PATCH 42/51] Removed the divider as a seperate component Since it's only used in one place, there is no real point in making it a seperate file CURA-6005 --- plugins/CuraDrive/src/qml/components/BackupList.qml | 7 ++++--- plugins/CuraDrive/src/qml/components/Divider.qml | 13 ------------- 2 files changed, 4 insertions(+), 16 deletions(-) delete mode 100644 plugins/CuraDrive/src/qml/components/Divider.qml diff --git a/plugins/CuraDrive/src/qml/components/BackupList.qml b/plugins/CuraDrive/src/qml/components/BackupList.qml index 10ca15afe5..afa9538486 100644 --- a/plugins/CuraDrive/src/qml/components/BackupList.qml +++ b/plugins/CuraDrive/src/qml/components/BackupList.qml @@ -26,10 +26,11 @@ ScrollView width: parent.width } - Divider + Rectangle { - width: parent.width - anchors.top: backupListItem.bottom + id: divider + color: UM.Theme.getColor("lining") + height: UM.Theme.getSize("default_lining").height } } } diff --git a/plugins/CuraDrive/src/qml/components/Divider.qml b/plugins/CuraDrive/src/qml/components/Divider.qml deleted file mode 100644 index 202794fe23..0000000000 --- a/plugins/CuraDrive/src/qml/components/Divider.qml +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2018 Ultimaker B.V. -// Cura is released under the terms of the LGPLv3 or higher. - -import QtQuick 2.7 - -import UM 1.3 as UM - -Rectangle -{ - id: divider - color: UM.Theme.getColor("lining") - height: UM.Theme.getSize("default_lining").height -} From bca070d567937f5743b3707f25d84a095e1e31d9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 11:41:21 +0100 Subject: [PATCH 43/51] Fixed typing CURA-6005 --- cura/API/Backups.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/API/Backups.py b/cura/API/Backups.py index 8e5cd7b83a..ef74e74be0 100644 --- a/cura/API/Backups.py +++ b/cura/API/Backups.py @@ -1,6 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Tuple, Optional, TYPE_CHECKING +from typing import Tuple, Optional, TYPE_CHECKING, Dict, Any from cura.Backups.BackupsManager import BackupsManager @@ -24,12 +24,12 @@ class Backups: ## Create a new back-up using the BackupsManager. # \return Tuple containing a ZIP file with the back-up data and a dict # with metadata about the back-up. - def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]: + def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, Any]]]: return self.manager.createBackup() ## Restore a back-up using the BackupsManager. # \param zip_file A ZIP file containing the actual back-up data. # \param meta_data Some metadata needed for restoring a back-up, like the # Cura version number. - def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None: + def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, Any]) -> None: return self.manager.restoreBackup(zip_file, meta_data) From b363be4afb861bb128357433d765bdc69104d746 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 11:48:06 +0100 Subject: [PATCH 44/51] Fix typing --- cura/UltimakerCloudAuthentication.py | 2 +- plugins/CuraDrive/src/DrivePluginExtension.py | 4 ++-- plugins/Toolbox/src/Toolbox.py | 3 --- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/cura/UltimakerCloudAuthentication.py b/cura/UltimakerCloudAuthentication.py index 7ebdfd054b..ac752231b9 100644 --- a/cura/UltimakerCloudAuthentication.py +++ b/cura/UltimakerCloudAuthentication.py @@ -5,7 +5,7 @@ # Constants used for the Cloud API # --------- DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str -DEFAULT_CLOUD_API_VERSION = "1" # type: str +DEFAULT_CLOUD_API_VERSION = 1 # type: int DEFAULT_CLOUD_ACCOUNT_API_ROOT = "https://account.ultimaker.com" # type: str try: diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index 8944b9a980..baa8ce092e 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -3,7 +3,7 @@ import os from datetime import datetime -from typing import Optional, List +from typing import Optional, List, Dict, Any from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal @@ -41,7 +41,7 @@ class DrivePluginExtension(QObject, Extension): # Local data caching for the UI. self._drive_window = None # type: Optional[QObject] - self._backups = [] + self._backups = [] # type: List[Dict[str, Any]] self._is_restoring_backup = False self._is_creating_backup = False diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index e7e849f1d2..192471a357 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -31,9 +31,6 @@ i18n_catalog = i18nCatalog("cura") ## The Toolbox class is responsible of communicating with the server through the API class Toolbox(QObject, Extension): - DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str - DEFAULT_CLOUD_API_VERSION = 1 # type: int - def __init__(self, application: CuraApplication) -> None: super().__init__() From b4b7e1fc2138718a92a3470254c957d99a2d2b9d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 4 Jan 2019 11:50:02 +0100 Subject: [PATCH 45/51] Codestyle & typing --- plugins/CuraDrive/__init__.py | 2 ++ plugins/CuraDrive/src/DriveApiService.py | 3 +-- plugins/CuraDrive/src/DrivePluginExtension.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/CuraDrive/__init__.py b/plugins/CuraDrive/__init__.py index dd7ffeaac3..eeb6b78689 100644 --- a/plugins/CuraDrive/__init__.py +++ b/plugins/CuraDrive/__init__.py @@ -3,8 +3,10 @@ from .src.DrivePluginExtension import DrivePluginExtension + def getMetaData(): return {} + def register(app): return {"extension": DrivePluginExtension()} diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index 79f49bf1a3..23e70a978c 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -110,7 +110,7 @@ class DriveApiService: self._cura_api.backups.restoreBackup(read_backup.read(), backup.get("metadata", {})) self.restoringStateChanged.emit(is_restoring = False) - def _emitRestoreError(self): + 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.")) @@ -144,7 +144,6 @@ class DriveApiService: # \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.") diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index baa8ce092e..fdf7fc1609 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -121,7 +121,7 @@ class DrivePluginExtension(QObject, Extension): return bool(self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY)) @pyqtProperty("QVariantList", notify = backupsChanged) - def backups(self) -> List: + def backups(self) -> List[Dict[str, Any]]: return self._backups @pyqtSlot(name = "refreshBackups") From 53cb2ce1891066c0437effb7a961a1c982f320f2 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 8 Jan 2019 13:29:45 +0100 Subject: [PATCH 46/51] Add comments for http status >=300 check CURA-6005 --- plugins/CuraDrive/src/DriveApiService.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/CuraDrive/src/DriveApiService.py b/plugins/CuraDrive/src/DriveApiService.py index 23e70a978c..7c1f8faa83 100644 --- a/plugins/CuraDrive/src/DriveApiService.py +++ b/plugins/CuraDrive/src/DriveApiService.py @@ -45,6 +45,8 @@ class DriveApiService: "Authorization": "Bearer {}".format(access_token) }) + # 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() From a8fe5ced89c92bc7a8362fe280b0c1b5e62cc14e Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 8 Jan 2019 13:33:46 +0100 Subject: [PATCH 47/51] Remove unused import CURA-6005 --- plugins/CuraDrive/src/Settings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/CuraDrive/src/Settings.py b/plugins/CuraDrive/src/Settings.py index c5383555b2..abe64e0acd 100644 --- a/plugins/CuraDrive/src/Settings.py +++ b/plugins/CuraDrive/src/Settings.py @@ -1,8 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from UM import i18nCatalog - from cura import UltimakerCloudAuthentication @@ -12,4 +10,4 @@ class Settings: DRIVE_API_URL = "{}/cura-drive/v{}".format(UltimakerCloudAuthentication.CuraCloudAPIRoot, str(DRIVE_API_VERSION)) AUTO_BACKUP_ENABLED_PREFERENCE_KEY = "cura_drive/auto_backup_enabled" - AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date" \ No newline at end of file + AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date" From aee4034e3e5d01f43ba34324efb38dc676a6c7c9 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 8 Jan 2019 15:49:34 +0100 Subject: [PATCH 48/51] No caching preferences CURA-6005 --- plugins/CuraDrive/src/DrivePluginExtension.py | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index fdf7fc1609..1f66706ce6 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -18,6 +18,7 @@ from .DriveApiService import DriveApiService from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") + # The DivePluginExtension provides functionality to backup and restore your Cura configuration to Ultimaker's cloud. class DrivePluginExtension(QObject, Extension): @@ -32,7 +33,7 @@ class DrivePluginExtension(QObject, Extension): # Signal emitted when preferences changed (like auto-backup). preferencesChanged = pyqtSignal() - + DATE_FORMAT = "%d/%m/%Y %H:%M:%S" def __init__(self) -> None: @@ -46,7 +47,7 @@ class DrivePluginExtension(QObject, Extension): self._is_creating_backup = False # Initialize services. - self._preferences = CuraApplication.getInstance().getPreferences() + preferences = CuraApplication.getInstance().getPreferences() self._drive_api_service = DriveApiService() # Attach signals. @@ -55,10 +56,10 @@ class DrivePluginExtension(QObject, Extension): self._drive_api_service.creatingStateChanged.connect(self._onCreatingStateChanged) # Register preferences. - self._preferences.addPreference(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, False) - self._preferences.addPreference(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, datetime.now() - .strftime(self.DATE_FORMAT)) - + preferences.addPreference(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, False) + preferences.addPreference(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, + datetime.now().strftime(self.DATE_FORMAT)) + # Register the menu item self.addMenuItem(catalog.i18nc("@item:inmenu", "Manage backups"), self.showDriveWindow) @@ -74,9 +75,10 @@ class DrivePluginExtension(QObject, Extension): self._drive_window.show() def _autoBackup(self) -> None: - if self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._isLastBackupTooLongAgo(): + preferences = CuraApplication.getInstance().getPreferences() + if preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._isLastBackupTooLongAgo(): self.createBackup() - + def _isLastBackupTooLongAgo(self) -> bool: current_date = datetime.now() last_backup_date = self._getLastBackupDate() @@ -84,12 +86,14 @@ class DrivePluginExtension(QObject, Extension): return date_diff.days > 1 def _getLastBackupDate(self) -> "datetime": - last_backup_date = self._preferences.getValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY) + preferences = CuraApplication.getInstance().getPreferences() + last_backup_date = preferences.getValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY) return datetime.strptime(last_backup_date, self.DATE_FORMAT) def _storeBackupDate(self) -> None: backup_date = datetime.now().strftime(self.DATE_FORMAT) - self._preferences.setValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, backup_date) + preferences = CuraApplication.getInstance().getPreferences() + preferences.setValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, backup_date) def _onLoginStateChanged(self, logged_in: bool = False) -> None: if logged_in: @@ -114,11 +118,13 @@ class DrivePluginExtension(QObject, Extension): @pyqtSlot(bool, name = "toggleAutoBackup") def toggleAutoBackup(self, enabled: bool) -> None: - self._preferences.setValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, enabled) + preferences = CuraApplication.getInstance().getPreferences() + preferences.setValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, enabled) @pyqtProperty(bool, notify = preferencesChanged) def autoBackupEnabled(self) -> bool: - return bool(self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY)) + preferences = CuraApplication.getInstance().getPreferences() + return bool(preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY)) @pyqtProperty("QVariantList", notify = backupsChanged) def backups(self) -> List[Dict[str, Any]]: From 520b34ab8901df47a55c90fe98bf827cef9d2499 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 9 Jan 2019 08:41:13 +0100 Subject: [PATCH 49/51] Use getPluginPath() CURA-6005 --- plugins/CuraDrive/src/DrivePluginExtension.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/CuraDrive/src/DrivePluginExtension.py b/plugins/CuraDrive/src/DrivePluginExtension.py index 1f66706ce6..060f1496f1 100644 --- a/plugins/CuraDrive/src/DrivePluginExtension.py +++ b/plugins/CuraDrive/src/DrivePluginExtension.py @@ -68,7 +68,8 @@ class DrivePluginExtension(QObject, Extension): def showDriveWindow(self) -> None: if not self._drive_window: - path = os.path.join(os.path.dirname(__file__), "qml", "main.qml") + plugin_dir_path = CuraApplication.getInstance().getPluginRegistry().getPluginPath("CuraDrive") + path = os.path.join(plugin_dir_path, "src", "qml", "main.qml") self._drive_window = CuraApplication.getInstance().createQmlComponent(path, {"CuraDrive": self}) self.refreshBackups() if self._drive_window: From 580a69f11e864f1d5328143613398abbec29ec39 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 9 Jan 2019 09:27:31 +0100 Subject: [PATCH 50/51] Add CuraCloudAccountAPIRoot to CuraVersion.py.in CURA-6005 --- cura/CuraVersion.py.in | 1 + 1 file changed, 1 insertion(+) diff --git a/cura/CuraVersion.py.in b/cura/CuraVersion.py.in index 7c6304231d..d0f07ebb8c 100644 --- a/cura/CuraVersion.py.in +++ b/cura/CuraVersion.py.in @@ -8,3 +8,4 @@ CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False CuraSDKVersion = "@CURA_SDK_VERSION@" CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@" CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@" +CuraCloudAccountAPIRoot = "CURA_CLOUD_ACCOUNT_API_ROOT" From a042de128b64b91421625507e4410496cf050b1a Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 10 Jan 2019 14:16:20 +0100 Subject: [PATCH 51/51] Tag CuraCloudAPIVersion-string-value as expression instead of literal. [CURA-6005] --- cura/CuraVersion.py.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CuraVersion.py.in b/cura/CuraVersion.py.in index d0f07ebb8c..770a0efd7b 100644 --- a/cura/CuraVersion.py.in +++ b/cura/CuraVersion.py.in @@ -8,4 +8,4 @@ CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False CuraSDKVersion = "@CURA_SDK_VERSION@" CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@" CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@" -CuraCloudAccountAPIRoot = "CURA_CLOUD_ACCOUNT_API_ROOT" +CuraCloudAccountAPIRoot = "@CURA_CLOUD_ACCOUNT_API_ROOT@"