CloudPackageChecker.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import json
  2. from typing import Optional
  3. from PyQt5.QtCore import QObject
  4. from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
  5. from UM import i18nCatalog
  6. from UM.Logger import Logger
  7. from UM.Message import Message
  8. from UM.Signal import Signal
  9. from plugins.Toolbox.src.UltimakerCloudScope import UltimakerCloudScope
  10. from cura.CuraApplication import CuraApplication
  11. from plugins.Toolbox.src.CloudApiModel import CloudApiModel
  12. from plugins.Toolbox.src.CloudSync.SubscribedPackagesModel import SubscribedPackagesModel
  13. class CloudPackageChecker(QObject):
  14. def __init__(self, application: CuraApplication) -> None:
  15. super().__init__()
  16. self.discrepancies = Signal() # Emits SubscribedPackagesModel
  17. self._application = application # type: CuraApplication
  18. self._scope = UltimakerCloudScope(application)
  19. self._model = SubscribedPackagesModel()
  20. self._application.initializationFinished.connect(self._onAppInitialized)
  21. self._i18n_catalog = i18nCatalog("cura")
  22. # This is a plugin, so most of the components required are not ready when
  23. # this is initialized. Therefore, we wait until the application is ready.
  24. def _onAppInitialized(self) -> None:
  25. self._package_manager = self._application.getPackageManager()
  26. # initial check
  27. self._fetchUserSubscribedPackages()
  28. # check again whenever the login state changes
  29. self._application.getCuraAPI().account.loginStateChanged.connect(self._fetchUserSubscribedPackages)
  30. def _fetchUserSubscribedPackages(self) -> None:
  31. if self._application.getCuraAPI().account.isLoggedIn:
  32. self._getUserPackages()
  33. def _handleCompatibilityData(self, json_data) -> None:
  34. user_subscribed_packages = [plugin["package_id"] for plugin in json_data]
  35. user_installed_packages = self._package_manager.getUserInstalledPackages()
  36. user_dismissed_packages = self._package_manager.getDismissedPackages()
  37. if user_dismissed_packages:
  38. user_installed_packages += user_dismissed_packages
  39. # We check if there are packages installed in Cloud Marketplace but not in Cura marketplace
  40. package_discrepancy = list(set(user_subscribed_packages).difference(user_installed_packages))
  41. self._model.setMetadata(json_data)
  42. self._model.addDiscrepancies(package_discrepancy)
  43. self._model.initialize()
  44. if package_discrepancy:
  45. self._handlePackageDiscrepancies()
  46. def _handlePackageDiscrepancies(self) -> None:
  47. Logger.log("d", "Discrepancy found between Cloud subscribed packages and Cura installed packages")
  48. sync_message = Message(self._i18n_catalog.i18nc(
  49. "@info:generic",
  50. "\nDo you want to sync material and software packages with your account?"),
  51. lifetime=0,
  52. title=self._i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", ))
  53. sync_message.addAction("sync",
  54. name=self._i18n_catalog.i18nc("@action:button", "Sync"),
  55. icon="",
  56. description="Sync your Cloud subscribed packages to your local environment.",
  57. button_align=Message.ActionButtonAlignment.ALIGN_RIGHT)
  58. sync_message.actionTriggered.connect(self._onSyncButtonClicked)
  59. sync_message.show()
  60. def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None:
  61. sync_message.hide()
  62. self.discrepancies.emit(self._model)
  63. def _getUserPackages(self) -> None:
  64. Logger.log("d", "Requesting subscribed packages metadata from server.")
  65. url = CloudApiModel.api_url_user_packages
  66. self._application.getHttpRequestManager().get(url,
  67. callback = self._onUserPackagesRequestFinished,
  68. error_callback = self._onUserPackagesRequestFinished,
  69. scope = self._scope)
  70. def _onUserPackagesRequestFinished(self,
  71. reply: "QNetworkReply",
  72. error: Optional["QNetworkReply.NetworkError"] = None) -> None:
  73. if error is not None or reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
  74. Logger.log("w",
  75. "Requesting user packages failed, response code %s while trying to connect to %s",
  76. reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
  77. return
  78. try:
  79. json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
  80. # Check for errors:
  81. if "errors" in json_data:
  82. for error in json_data["errors"]:
  83. Logger.log("e", "%s", error["title"])
  84. return
  85. self._handleCompatibilityData(json_data["data"])
  86. except json.decoder.JSONDecodeError:
  87. Logger.log("w", "Received invalid JSON for user packages")