PluginBrowser.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. # Copyright (c) 2017 Ultimaker B.V.
  2. # PluginBrowser is released under the terms of the AGPLv3 or higher.
  3. from UM.Extension import Extension
  4. from UM.i18n import i18nCatalog
  5. from UM.Logger import Logger
  6. from UM.Qt.ListModel import ListModel
  7. from UM.PluginRegistry import PluginRegistry
  8. from UM.Application import Application
  9. from UM.Version import Version
  10. from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
  11. from PyQt5.QtCore import QUrl, QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
  12. from PyQt5.QtQml import QQmlComponent, QQmlContext
  13. import json
  14. import os
  15. import tempfile
  16. i18n_catalog = i18nCatalog("cura")
  17. class PluginBrowser(QObject, Extension):
  18. def __init__(self, parent = None):
  19. super().__init__(parent)
  20. self.addMenuItem(i18n_catalog.i18n("Browse plugins"), self.browsePlugins)
  21. self._api_version = 1
  22. self._api_url = "http://software.ultimaker.com/cura/v%s/" % self._api_version
  23. self._plugin_list_request = None
  24. self._download_plugin_request = None
  25. self._download_plugin_reply = None
  26. self._network_manager = None
  27. self._plugins_metadata = []
  28. self._plugins_model = None
  29. self._qml_component = None
  30. self._qml_context = None
  31. self._dialog = None
  32. self._download_progress = 0
  33. self._is_downloading = False
  34. pluginsMetadataChanged = pyqtSignal()
  35. onDownloadProgressChanged = pyqtSignal()
  36. onIsDownloadingChanged = pyqtSignal()
  37. @pyqtProperty(bool, notify = onIsDownloadingChanged)
  38. def isDownloading(self):
  39. return self._is_downloading
  40. def browsePlugins(self):
  41. self._createNetworkManager()
  42. self.requestPluginList()
  43. if not self._dialog:
  44. self._createDialog()
  45. self._dialog.show()
  46. def requestPluginList(self):
  47. url = QUrl(self._api_url + "plugins")
  48. self._plugin_list_request = QNetworkRequest(url)
  49. self._network_manager.get(self._plugin_list_request)
  50. def _createDialog(self):
  51. Logger.log("d", "PluginBrowser")
  52. path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "PluginBrowser.qml"))
  53. self._qml_component = QQmlComponent(Application.getInstance()._engine, path)
  54. # We need access to engine (although technically we can't)
  55. self._qml_context = QQmlContext(Application.getInstance()._engine.rootContext())
  56. self._qml_context.setContextProperty("manager", self)
  57. self._dialog = self._qml_component.create(self._qml_context)
  58. if self._dialog is None:
  59. Logger.log("e", "QQmlComponent status %s", self._qml_component.status())
  60. Logger.log("e", "QQmlComponent errorString %s", self._qml_component.errorString())
  61. def setIsDownloading(self, is_downloading):
  62. if self._is_downloading != is_downloading:
  63. self._is_downloading = is_downloading
  64. self.onIsDownloadingChanged.emit()
  65. def _onDownloadPluginProgress(self, bytes_sent, bytes_total):
  66. if bytes_total > 0:
  67. new_progress = bytes_sent / bytes_total * 100
  68. if new_progress > self._download_progress:
  69. self._download_progress = new_progress
  70. self.onDownloadProgressChanged.emit()
  71. self._download_progress = new_progress
  72. if new_progress == 100.0:
  73. self.setIsDownloading(False)
  74. self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress)
  75. self._temp_plugin_file = tempfile.NamedTemporaryFile(suffix = ".curaplugin")
  76. self._temp_plugin_file.write(self._download_plugin_reply.readAll())
  77. result = PluginRegistry.getInstance().installPlugin("file://" + self._temp_plugin_file.name)
  78. self._temp_plugin_file.close() # Plugin was installed, delete temp file
  79. @pyqtProperty(int, notify = onDownloadProgressChanged)
  80. def downloadProgress(self):
  81. return self._download_progress
  82. @pyqtSlot(str)
  83. def downloadAndInstallPlugin(self, url):
  84. Logger.log("i", "Attempting to download & install plugin from %s", url)
  85. url = QUrl(url)
  86. self._download_plugin_request = QNetworkRequest(url)
  87. self._download_plugin_reply = self._network_manager.get(self._download_plugin_request)
  88. self._download_progress = 0
  89. self.setIsDownloading(True)
  90. self.onDownloadProgressChanged.emit()
  91. self._download_plugin_reply.downloadProgress.connect(self._onDownloadPluginProgress)
  92. @pyqtProperty(QObject, notify=pluginsMetadataChanged)
  93. def pluginsModel(self):
  94. if self._plugins_model is None:
  95. self._plugins_model = ListModel()
  96. self._plugins_model.addRoleName(Qt.UserRole + 1, "name")
  97. self._plugins_model.addRoleName(Qt.UserRole + 2, "version")
  98. self._plugins_model.addRoleName(Qt.UserRole + 3, "short_description")
  99. self._plugins_model.addRoleName(Qt.UserRole + 4, "author")
  100. self._plugins_model.addRoleName(Qt.UserRole + 5, "already_installed")
  101. self._plugins_model.addRoleName(Qt.UserRole + 6, "file_location")
  102. else:
  103. self._plugins_model.clear()
  104. items = []
  105. for metadata in self._plugins_metadata:
  106. items.append({
  107. "name": metadata["label"],
  108. "version": metadata["version"],
  109. "short_description": metadata["short_description"],
  110. "author": metadata["author"],
  111. "already_installed": self._checkAlreadyInstalled(metadata["id"], metadata["version"]),
  112. "file_location": metadata["file_location"]
  113. })
  114. self._plugins_model.setItems(items)
  115. return self._plugins_model
  116. def _checkAlreadyInstalled(self, id, version):
  117. plugin_registry = PluginRegistry.getInstance()
  118. metadata = plugin_registry.getMetaData(id)
  119. if metadata != {}:
  120. current_version = Version(metadata["plugin"]["version"])
  121. new_version = Version(version)
  122. if new_version > current_version:
  123. return False
  124. return True
  125. def _onRequestFinished(self, reply):
  126. reply_url = reply.url().toString()
  127. if reply.operation() == QNetworkAccessManager.GetOperation:
  128. if reply_url == self._api_url + "plugins":
  129. try:
  130. json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
  131. self._plugins_metadata = json_data
  132. self.pluginsMetadataChanged.emit()
  133. except json.decoder.JSONDecodeError:
  134. Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
  135. return
  136. else:
  137. # Ignore any operation that is not a get operation
  138. pass
  139. def _createNetworkManager(self):
  140. if self._network_manager:
  141. self._network_manager.finished.disconnect(self._onRequestFinished)
  142. self._network_manager = QNetworkAccessManager()
  143. self._network_manager.finished.connect(self._onRequestFinished)