vehicles.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. """
  2. Vehicles service for handling vehicle-related business logic.
  3. """
  4. import logging
  5. from typing import List, Optional
  6. from fastapi import Request
  7. from metadata_manager.firmware_server.index import latest_features_txt_url
  8. from metadata_manager.versions_manager.providers import OFFICIAL_REMOTE_NAME
  9. from web.schemas import (
  10. VehicleBase,
  11. RemoteInfo,
  12. VersionOut,
  13. BoardOut,
  14. StandardArtifactOut,
  15. FeatureOut,
  16. CategoryBase,
  17. FeatureDefault,
  18. )
  19. logger = logging.getLogger(__name__)
  20. class VehiclesService:
  21. """Service for managing vehicles, versions, boards, and features."""
  22. def __init__(self, vehicle_manager=None,
  23. versions_manager=None,
  24. ap_src_metadata_fetcher=None,
  25. manifest_json=None,
  26. features_txt_client=None,
  27. repo=None):
  28. self.vehicles_manager = vehicle_manager
  29. self.versions_manager = versions_manager
  30. self.ap_src_metadata_fetcher = ap_src_metadata_fetcher
  31. self.manifest_json = manifest_json
  32. self.features_txt_client = features_txt_client
  33. self.repo = repo
  34. def get_all_vehicles(self) -> List[VehicleBase]:
  35. """Get list of all available vehicles."""
  36. logger.info('Fetching all vehicles')
  37. vehicles = self.vehicles_manager.get_all_vehicles()
  38. # Sort by name for consistent ordering
  39. sorted_vehicles = sorted(vehicles, key=lambda v: v.name)
  40. logger.info(f'Found vehicles: {[v.name for v in sorted_vehicles]}')
  41. return [
  42. VehicleBase(id=vehicle.id, name=vehicle.name)
  43. for vehicle in sorted_vehicles
  44. ]
  45. def get_vehicle(self, vehicle_id: str) -> Optional[VehicleBase]:
  46. """Get a specific vehicle by ID."""
  47. vehicle = self.vehicles_manager.get_vehicle_by_id(vehicle_id)
  48. if vehicle:
  49. return VehicleBase(id=vehicle.id, name=vehicle.name)
  50. return None
  51. def get_versions(
  52. self,
  53. vehicle_id: str,
  54. type_filter: Optional[str] = None
  55. ) -> List[VersionOut]:
  56. """Get all versions available for a specific vehicle."""
  57. versions = []
  58. for version_info in self.versions_manager.get_versions_for_vehicle(
  59. vehicle_id=vehicle_id
  60. ):
  61. # Apply type filter if provided
  62. if type_filter and version_info.release_type != type_filter:
  63. continue
  64. if version_info.release_type == "latest":
  65. title = f"Latest ({version_info.remote_info.name})"
  66. else:
  67. rel_type = version_info.release_type
  68. ver_num = version_info.version_number
  69. remote = version_info.remote_info.name
  70. title = f"{rel_type} {ver_num} ({remote})"
  71. versions.append(VersionOut(
  72. id=version_info.version_id,
  73. name=title,
  74. type=version_info.release_type,
  75. remote=RemoteInfo(
  76. name=version_info.remote_info.name,
  77. url=version_info.remote_info.url,
  78. ),
  79. commit_ref=version_info.commit_ref,
  80. vehicle_id=vehicle_id,
  81. ))
  82. # Sort by name
  83. return sorted(versions, key=lambda x: x.name)
  84. def get_version(
  85. self,
  86. vehicle_id: str,
  87. version_id: str
  88. ) -> Optional[VersionOut]:
  89. """Get details of a specific version for a vehicle."""
  90. versions = self.get_versions(vehicle_id)
  91. for version in versions:
  92. if version.id == version_id:
  93. return version
  94. return None
  95. def get_boards(
  96. self,
  97. vehicle_id: str,
  98. version_id: str
  99. ) -> List[BoardOut]:
  100. """Get all boards available for a specific vehicle version."""
  101. # Get version info
  102. version_info = self.versions_manager.get_version_info(
  103. vehicle_id=vehicle_id,
  104. version_id=version_id
  105. )
  106. if not version_info:
  107. return []
  108. logger.info(
  109. f'Board list requested for {vehicle_id} '
  110. f'{version_info.remote_info.name} {version_info.commit_ref}'
  111. )
  112. # Get boards list
  113. with self.repo.get_checkout_lock():
  114. boards = self.ap_src_metadata_fetcher.get_boards(
  115. remote=version_info.remote_info.name,
  116. commit_ref=version_info.commit_ref,
  117. vehicle_id=vehicle_id,
  118. )
  119. return [
  120. BoardOut(
  121. id=board,
  122. name=board,
  123. vehicle_id=vehicle_id,
  124. version_id=version_id
  125. )
  126. for board in boards
  127. ]
  128. def get_board(
  129. self,
  130. vehicle_id: str,
  131. version_id: str,
  132. board_id: str
  133. ) -> Optional[BoardOut]:
  134. """Get details of a specific board for a vehicle version."""
  135. boards = self.get_boards(vehicle_id, version_id)
  136. for board in boards:
  137. if board.id == board_id:
  138. return board
  139. return None
  140. def get_board_standard_artifacts(
  141. self,
  142. vehicle_id: str,
  143. version_id: str,
  144. board_id: str,
  145. ) -> Optional[List[StandardArtifactOut]]:
  146. """Get standard build artifacts for an official manifest-backed version."""
  147. version_info = self.versions_manager.get_version_info(
  148. vehicle_id=vehicle_id,
  149. version_id=version_id,
  150. )
  151. if (
  152. version_info is None
  153. or version_info.remote_info.name != OFFICIAL_REMOTE_NAME
  154. ):
  155. return None
  156. with self.repo.get_checkout_lock():
  157. boards = self.ap_src_metadata_fetcher.get_boards(
  158. remote=version_info.remote_info.name,
  159. commit_ref=version_info.commit_ref,
  160. vehicle_id=vehicle_id,
  161. )
  162. if board_id not in boards:
  163. return None
  164. logger.info(
  165. "Standard artifacts requested for %s version %s board %s",
  166. vehicle_id,
  167. version_id,
  168. board_id,
  169. )
  170. artifacts = self.manifest_json.get_board_artifacts(
  171. vehicle_id=vehicle_id,
  172. release_type=version_info.release_type,
  173. version_number=version_info.version_number,
  174. board_id=board_id,
  175. )
  176. if not artifacts:
  177. return None
  178. return [
  179. StandardArtifactOut(
  180. name=artifact.name,
  181. url=artifact.url,
  182. format=artifact.format,
  183. size=artifact.size,
  184. )
  185. for artifact in artifacts
  186. ]
  187. def get_features(
  188. self,
  189. vehicle_id: str,
  190. version_id: str,
  191. board_id: str,
  192. category_id: Optional[str] = None
  193. ) -> List[FeatureOut]:
  194. """
  195. Get all features with defaults for a specific
  196. vehicle version/board.
  197. """
  198. # Get version info
  199. version_info = self.versions_manager.get_version_info(
  200. vehicle_id=vehicle_id,
  201. version_id=version_id
  202. )
  203. if not version_info:
  204. return []
  205. logger.info(
  206. f'Features requested for {vehicle_id} '
  207. f'{version_info.remote_info.name} {version_info.commit_ref}'
  208. )
  209. # Get build options from source
  210. with self.repo.get_checkout_lock():
  211. options = self.ap_src_metadata_fetcher.get_build_options_at_commit(
  212. remote=version_info.remote_info.name,
  213. commit_ref=version_info.commit_ref
  214. )
  215. # Try to fetch board-specific defaults from firmware-server
  216. if version_info.remote_info.name == OFFICIAL_REMOTE_NAME:
  217. features_url = self.manifest_json.get_features_txt_url(
  218. vehicle_id=vehicle_id,
  219. release_type=version_info.release_type,
  220. version_number=version_info.version_number,
  221. board_id=board_id,
  222. )
  223. else:
  224. features_url = latest_features_txt_url(vehicle_id, board_id)
  225. board_defaults = (
  226. self.features_txt_client.get_defaults(features_url)
  227. if features_url
  228. else None
  229. )
  230. # Build feature list
  231. features = []
  232. for option in options:
  233. # Apply category filter if provided
  234. if category_id and option.category != category_id:
  235. continue
  236. # Determine default state and source
  237. if board_defaults and option.define in board_defaults:
  238. # Override with firmware server data
  239. default_enabled = (board_defaults[option.define] != 0)
  240. default_source = 'firmware-server'
  241. else:
  242. # Use build-options-py fallback
  243. default_enabled = (option.default != 0)
  244. default_source = 'build-options-py'
  245. # Parse dependencies (comma-separated labels)
  246. dependencies = []
  247. if option.dependency:
  248. dependencies = [
  249. label.strip()
  250. for label in option.dependency.split(',')
  251. ]
  252. features.append(FeatureOut(
  253. id=option.label,
  254. name=option.label,
  255. category=CategoryBase(
  256. id=option.category,
  257. name=option.category,
  258. description=None
  259. ),
  260. description=option.description,
  261. vehicle_id=vehicle_id,
  262. version_id=version_id,
  263. board_id=board_id,
  264. default=FeatureDefault(
  265. enabled=default_enabled,
  266. source=default_source
  267. ),
  268. dependencies=dependencies
  269. ))
  270. # Sort by name
  271. return sorted(features, key=lambda x: x.category.name)
  272. def get_feature(
  273. self,
  274. vehicle_id: str,
  275. version_id: str,
  276. board_id: str,
  277. feature_id: str
  278. ) -> Optional[FeatureOut]:
  279. """Get details of a specific feature for a vehicle version/board."""
  280. features = self.get_features(vehicle_id, version_id, board_id)
  281. for feature in features:
  282. if feature.id == feature_id:
  283. return feature
  284. return None
  285. def get_vehicles_service(request: Request) -> VehiclesService:
  286. """
  287. Get VehiclesService instance with dependencies from app state.
  288. Args:
  289. request: FastAPI Request object
  290. Returns:
  291. VehiclesService instance initialized with app state dependencies
  292. """
  293. return VehiclesService(
  294. vehicle_manager=request.app.state.vehicles_manager,
  295. versions_manager=request.app.state.versions_manager,
  296. ap_src_metadata_fetcher=request.app.state.ap_src_metadata_fetcher,
  297. manifest_json=request.app.state.manifest_json,
  298. features_txt_client=request.app.state.features_txt_client,
  299. repo=request.app.state.repo,
  300. )