manager.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import logging
  2. from pathlib import Path
  3. import ap_git
  4. from utils import TaskRunner
  5. from metadata_manager.versions_manager.models import RemoteInfo, VersionInfo
  6. from metadata_manager.versions_manager.providers import (
  7. VersionsProvider,
  8. build_default_providers,
  9. )
  10. from metadata_manager.vehicles_manager import VehiclesManager as vehm
  11. REMOTES_SCHEMA_PATH = Path(__file__).resolve().parent.parent / "remotes.schema.json"
  12. def _release_type_dedup_priority(release_type: str) -> int:
  13. """Lower value wins when multiple releases share the same version_id."""
  14. match release_type:
  15. case "stable":
  16. return 0
  17. case "beta":
  18. return 1
  19. case "latest":
  20. return 2
  21. case "tag":
  22. return 3
  23. case _:
  24. return 99
  25. class VersionsManager:
  26. """
  27. Version manager merges version metadata from different pluggable providers.
  28. """
  29. __singleton = None
  30. def __init__(
  31. self,
  32. ap_repo: ap_git.GitRepo,
  33. remotes_json_path: str,
  34. manifest_json=None,
  35. providers: list[VersionsProvider] | None = None,
  36. ):
  37. if vehm.get_singleton() is None:
  38. raise RuntimeError("VehiclesManager should be initialised first")
  39. if VersionsManager.__singleton:
  40. raise RuntimeError("VersionsManager must be a singleton.")
  41. self.logger = logging.getLogger(__name__)
  42. if providers is None:
  43. if manifest_json is None:
  44. raise ValueError("manifest_json is required when providers is omitted")
  45. providers = build_default_providers(
  46. manifest_json=manifest_json,
  47. remotes_json_path=remotes_json_path,
  48. schema_path=str(REMOTES_SCHEMA_PATH),
  49. )
  50. self._providers = providers
  51. self.repo = ap_repo
  52. self._remotes_json_path = remotes_json_path
  53. self.__task__runner = TaskRunner(tasks=((self.refresh_all, 1200),))
  54. VersionsManager.__singleton = self
  55. def start(self) -> None:
  56. self.logger.info("Starting VersionsManager background refresh jobs.")
  57. self.__task__runner.start()
  58. def stop(self) -> None:
  59. self.logger.info("Stopping VersionsManager background refresh jobs.")
  60. self.__task__runner.stop()
  61. def refresh_all(self) -> None:
  62. for provider in self._providers:
  63. try:
  64. provider.refresh()
  65. except Exception as exc:
  66. self.logger.error(
  67. "Provider %s refresh failed: %s", provider.name, exc
  68. )
  69. self._sync_remotes_with_ap_repo()
  70. def get_all_remotes_info(self) -> list[RemoteInfo]:
  71. remotes = {}
  72. for provider in self._providers:
  73. for remote in provider.get_remotes():
  74. remotes[remote.name] = remote
  75. return list(remotes.values())
  76. def get_remote_info(self, remote_name: str) -> RemoteInfo | None:
  77. return next(
  78. (remote for remote in self.get_all_remotes_info()
  79. if remote.name == remote_name),
  80. None,
  81. )
  82. def get_versions_for_vehicle(self, vehicle_id: str) -> list[VersionInfo]:
  83. if vehicle_id is None:
  84. raise ValueError("Vehicle ID is a required parameter.")
  85. vehicle = vehm.get_singleton().get_vehicle_by_id(vehicle_id)
  86. if vehicle is None:
  87. raise ValueError(f"Invalid vehicle ID '{vehicle_id}'.")
  88. by_version_id: dict[str, VersionInfo] = {}
  89. for provider in self._providers:
  90. for version in provider.get_versions(vehicle_id):
  91. existing = by_version_id.get(version.version_id)
  92. if existing is None:
  93. by_version_id[version.version_id] = version
  94. continue
  95. if _release_type_dedup_priority(version.release_type) < (
  96. _release_type_dedup_priority(existing.release_type)
  97. ):
  98. by_version_id[version.version_id] = version
  99. return list(by_version_id.values())
  100. def is_version_listed(self, vehicle_id: str, version_id: str) -> bool:
  101. if vehicle_id is None:
  102. raise ValueError("vehicle_id is a required parameter.")
  103. if version_id is None:
  104. raise ValueError("version_id is a required parameter.")
  105. return version_id in [
  106. version.version_id
  107. for version in self.get_versions_for_vehicle(vehicle_id=vehicle_id)
  108. ]
  109. def get_version_info(self, vehicle_id: str, version_id: str) -> VersionInfo | None:
  110. return next(
  111. (
  112. version
  113. for version in self.get_versions_for_vehicle(vehicle_id=vehicle_id)
  114. if version.version_id == version_id
  115. ),
  116. None,
  117. )
  118. def _sync_remotes_with_ap_repo(self) -> None:
  119. remotes = tuple(
  120. (remote.name, remote.url)
  121. for remote in self.get_all_remotes_info()
  122. )
  123. if remotes:
  124. self.repo.remote_add_bulk(remotes=remotes, force=True)
  125. @staticmethod
  126. def get_singleton():
  127. return VersionsManager.__singleton