LocalPackageList.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. # Copyright (c) 2021 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import Any, Dict, List, Optional, TYPE_CHECKING
  4. from operator import attrgetter
  5. from PyQt5.QtCore import pyqtSlot, QObject
  6. if TYPE_CHECKING:
  7. from PyQt5.QtCore import QObject
  8. from PyQt5.QtNetwork import QNetworkReply
  9. from UM.i18n import i18nCatalog
  10. from UM.TaskManagement.HttpRequestManager import HttpRequestManager
  11. from UM.Logger import Logger
  12. from .PackageList import PackageList
  13. from .PackageModel import PackageModel
  14. from .Constants import PACKAGE_UPDATES_URL
  15. catalog = i18nCatalog("cura")
  16. class LocalPackageList(PackageList):
  17. PACKAGE_CATEGORIES = {
  18. "installed":
  19. {
  20. "plugin": catalog.i18nc("@label", "Installed Plugins"),
  21. "material": catalog.i18nc("@label", "Installed Materials")
  22. },
  23. "bundled":
  24. {
  25. "plugin": catalog.i18nc("@label", "Bundled Plugins"),
  26. "material": catalog.i18nc("@label", "Bundled Materials")
  27. }
  28. } # The section headers to be used for the different package categories
  29. def __init__(self, parent: Optional["QObject"] = None) -> None:
  30. super().__init__(parent)
  31. self._has_footer = False
  32. self._ongoing_requests["check_updates"] = None
  33. self._package_manager.packagesWithUpdateChanged.connect(self._sortSectionsOnUpdate)
  34. self._package_manager.packageUninstalled.connect(self._removePackageModel)
  35. def _sortSectionsOnUpdate(self) -> None:
  36. SECTION_ORDER = dict(zip([i for k, v in self.PACKAGE_CATEGORIES.items() for i in self.PACKAGE_CATEGORIES[k].values()], ["a", "b", "c", "d"]))
  37. self.sort(lambda model: f"{SECTION_ORDER[model.sectionTitle]}_{model._can_update}_{model.displayName}".lower(), key = "package")
  38. def _removePackageModel(self, package_id):
  39. if package_id not in self._package_manager.local_packages_ids:
  40. index = self.find("package", package_id)
  41. if index < 0:
  42. Logger.error(f"Could not find card in Listview corresponding with {package_id}")
  43. self.updatePackages()
  44. return
  45. self.removeItem(index)
  46. @pyqtSlot()
  47. def updatePackages(self) -> None:
  48. """Update the list with local packages, these are materials or plugin, either bundled or user installed. The list
  49. will also contain **to be removed** or **to be installed** packages since the user might still want to interact
  50. with these.
  51. """
  52. self.setErrorMessage("") # Clear any previous errors.
  53. self.setIsLoading(True)
  54. # Obtain and sort the local packages
  55. self.setItems([{"package": p} for p in [self._makePackageModel(p) for p in self._package_manager.local_packages]])
  56. self._sortSectionsOnUpdate()
  57. self.checkForUpdates(self._package_manager.local_packages)
  58. self.setIsLoading(False)
  59. self.setHasMore(False) # All packages should have been loaded at this time
  60. def _makePackageModel(self, package_info: Dict[str, Any]) -> PackageModel:
  61. """ Create a PackageModel from the package_info and determine its section_title"""
  62. package_id = package_info["package_id"]
  63. bundled_or_installed = "bundled" if self._package_manager.isBundledPackage(package_id) else "installed"
  64. package_type = package_info["package_type"]
  65. section_title = self.PACKAGE_CATEGORIES[bundled_or_installed][package_type]
  66. package = PackageModel(package_info, section_title = section_title, parent = self)
  67. self._connectManageButtonSignals(package)
  68. return package
  69. def checkForUpdates(self, packages: List[Dict[str, Any]]) -> None:
  70. installed_packages = "installed_packages=".join([f"{package['package_id']}:{package['package_version']}&" for package in packages])
  71. request_url = f"{PACKAGE_UPDATES_URL}?installed_packages={installed_packages[:-1]}"
  72. self._ongoing_requests["check_updates"] = HttpRequestManager.getInstance().get(
  73. request_url,
  74. scope = self._scope,
  75. callback = self._parseResponse
  76. )
  77. def _parseResponse(self, reply: "QNetworkReply") -> None:
  78. """
  79. Parse the response from the package list API request which can update.
  80. :param reply: A reply containing information about a number of packages.
  81. """
  82. response_data = HttpRequestManager.readJSON(reply)
  83. if "data" not in response_data:
  84. Logger.error(
  85. f"Could not interpret the server's response. Missing 'data' from response data. Keys in response: {response_data.keys()}")
  86. return
  87. if len(response_data["data"]) == 0:
  88. return
  89. packages = response_data["data"]
  90. self._package_manager.setPackagesWithUpdate(dict(zip([p['package_id'] for p in packages], [p for p in packages])))
  91. self._ongoing_requests["check_updates"] = None