DrivePluginExtension.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import os
  4. from datetime import datetime
  5. from typing import Any, cast, Dict, List, Optional
  6. from PyQt6.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
  7. from UM.Extension import Extension
  8. from UM.Logger import Logger
  9. from UM.Message import Message
  10. from cura.CuraApplication import CuraApplication
  11. from .Settings import Settings
  12. from .DriveApiService import DriveApiService
  13. from UM.i18n import i18nCatalog
  14. catalog = i18nCatalog("cura")
  15. # The DivePluginExtension provides functionality to backup and restore your Cura configuration to Ultimaker's cloud.
  16. class DrivePluginExtension(QObject, Extension):
  17. # Signal emitted when the list of backups changed.
  18. backupsChanged = pyqtSignal()
  19. # Signal emitted when restoring has started. Needed to prevent parallel restoring.
  20. restoringStateChanged = pyqtSignal()
  21. # Signal emitted when creating has started. Needed to prevent parallel creation of backups.
  22. creatingStateChanged = pyqtSignal()
  23. # Signal emitted when preferences changed (like auto-backup).
  24. preferencesChanged = pyqtSignal()
  25. # Signal emitted when the id of the backup-to-be-restored is changed
  26. backupIdBeingRestoredChanged = pyqtSignal(arguments = ["backup_id_being_restored"])
  27. DATE_FORMAT = "%d/%m/%Y %H:%M:%S"
  28. def __init__(self) -> None:
  29. QObject.__init__(self, None)
  30. Extension.__init__(self)
  31. # Local data caching for the UI.
  32. self._drive_window = None # type: Optional[QObject]
  33. self._backups = [] # type: List[Dict[str, Any]]
  34. self._is_restoring_backup = False
  35. self._is_creating_backup = False
  36. self._backup_id_being_restored = ""
  37. # Initialize services.
  38. preferences = CuraApplication.getInstance().getPreferences()
  39. self._drive_api_service = DriveApiService()
  40. # Attach signals.
  41. CuraApplication.getInstance().getCuraAPI().account.loginStateChanged.connect(self._onLoginStateChanged)
  42. CuraApplication.getInstance().applicationShuttingDown.connect(self._onApplicationShuttingDown)
  43. self._drive_api_service.restoringStateChanged.connect(self._onRestoringStateChanged)
  44. self._drive_api_service.creatingStateChanged.connect(self._onCreatingStateChanged)
  45. # Register preferences.
  46. preferences.addPreference(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, False)
  47. preferences.addPreference(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY,
  48. datetime.now().strftime(self.DATE_FORMAT))
  49. # Register the menu item
  50. self.addMenuItem(catalog.i18nc("@item:inmenu", "Manage backups"), self.showDriveWindow)
  51. # Make auto-backup on boot if required.
  52. CuraApplication.getInstance().engineCreatedSignal.connect(self._autoBackup)
  53. def showDriveWindow(self) -> None:
  54. if not self._drive_window:
  55. plugin_dir_path = cast(str, CuraApplication.getInstance().getPluginRegistry().getPluginPath(self.getPluginId())) # We know this plug-in exists because that's us, so this always returns str.
  56. path = os.path.join(plugin_dir_path, "src", "qml", "main.qml")
  57. self._drive_window = CuraApplication.getInstance().createQmlComponent(path, {"CuraDrive": self})
  58. self.refreshBackups()
  59. if self._drive_window:
  60. self._drive_window.show()
  61. def _onApplicationShuttingDown(self):
  62. if self._drive_window:
  63. self._drive_window.hide()
  64. def _autoBackup(self) -> None:
  65. preferences = CuraApplication.getInstance().getPreferences()
  66. if preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._isLastBackupTooLongAgo():
  67. self.createBackup()
  68. def _isLastBackupTooLongAgo(self) -> bool:
  69. current_date = datetime.now()
  70. last_backup_date = self._getLastBackupDate()
  71. date_diff = current_date - last_backup_date
  72. return date_diff.days > 1
  73. def _getLastBackupDate(self) -> "datetime":
  74. preferences = CuraApplication.getInstance().getPreferences()
  75. last_backup_date = preferences.getValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY)
  76. return datetime.strptime(last_backup_date, self.DATE_FORMAT)
  77. def _storeBackupDate(self) -> None:
  78. backup_date = datetime.now().strftime(self.DATE_FORMAT)
  79. preferences = CuraApplication.getInstance().getPreferences()
  80. preferences.setValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, backup_date)
  81. def _onLoginStateChanged(self, logged_in: bool = False) -> None:
  82. if logged_in:
  83. self.refreshBackups()
  84. def _onRestoringStateChanged(self, is_restoring: bool = False, error_message: Optional[str] = None) -> None:
  85. self._is_restoring_backup = is_restoring
  86. self.restoringStateChanged.emit()
  87. if error_message:
  88. self.backupIdBeingRestored = ""
  89. Message(error_message,
  90. title = catalog.i18nc("@info:title", "Backup"),
  91. message_type = Message.MessageType.ERROR).show()
  92. def _onCreatingStateChanged(self, is_creating: bool = False, error_message: str = None) -> None:
  93. self._is_creating_backup = is_creating
  94. self.creatingStateChanged.emit()
  95. if error_message:
  96. Message(error_message,
  97. title = catalog.i18nc("@info:title", "Backup"),
  98. message_type = Message.MessageType.ERROR).show()
  99. else:
  100. self._storeBackupDate()
  101. if not is_creating and not error_message:
  102. # We've finished creating a new backup, to the list has to be updated.
  103. self.refreshBackups()
  104. @pyqtSlot(bool, name = "toggleAutoBackup")
  105. def toggleAutoBackup(self, enabled: bool) -> None:
  106. preferences = CuraApplication.getInstance().getPreferences()
  107. preferences.setValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, enabled)
  108. @pyqtProperty(bool, notify = preferencesChanged)
  109. def autoBackupEnabled(self) -> bool:
  110. preferences = CuraApplication.getInstance().getPreferences()
  111. return bool(preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY))
  112. @pyqtProperty("QVariantList", notify = backupsChanged)
  113. def backups(self) -> List[Dict[str, Any]]:
  114. return self._backups
  115. @pyqtSlot(name = "refreshBackups")
  116. def refreshBackups(self) -> None:
  117. self._drive_api_service.getBackups(self._backupsChangedCallback)
  118. def _backupsChangedCallback(self, backups: List[Dict[str, Any]]) -> None:
  119. self._backups = backups
  120. self.backupsChanged.emit()
  121. @pyqtProperty(bool, notify = restoringStateChanged)
  122. def isRestoringBackup(self) -> bool:
  123. return self._is_restoring_backup
  124. @pyqtProperty(bool, notify = creatingStateChanged)
  125. def isCreatingBackup(self) -> bool:
  126. return self._is_creating_backup
  127. @pyqtSlot(str, name = "restoreBackup")
  128. def restoreBackup(self, backup_id: str) -> None:
  129. for backup in self._backups:
  130. if backup.get("backup_id") == backup_id:
  131. self._drive_api_service.restoreBackup(backup)
  132. self.setBackupIdBeingRestored(backup_id)
  133. return
  134. Logger.log("w", "Unable to find backup with the ID %s", backup_id)
  135. @pyqtSlot(name = "createBackup")
  136. def createBackup(self) -> None:
  137. self._drive_api_service.createBackup()
  138. @pyqtSlot(str, name = "deleteBackup")
  139. def deleteBackup(self, backup_id: str) -> None:
  140. self._drive_api_service.deleteBackup(backup_id, self._backupDeletedCallback)
  141. def _backupDeletedCallback(self, success: bool):
  142. if success:
  143. self.refreshBackups()
  144. def setBackupIdBeingRestored(self, backup_id_being_restored: str) -> None:
  145. if backup_id_being_restored != self._backup_id_being_restored:
  146. self._backup_id_being_restored = backup_id_being_restored
  147. self.backupIdBeingRestoredChanged.emit()
  148. @pyqtProperty(str, fset = setBackupIdBeingRestored, notify = backupIdBeingRestoredChanged)
  149. def backupIdBeingRestored(self) -> str:
  150. return self._backup_id_being_restored