diff --git a/src/infuse_iot/tools/cloud.py b/src/infuse_iot/tools/cloud.py index 78e83c3..c3ea22e 100644 --- a/src/infuse_iot/tools/cloud.py +++ b/src/infuse_iot/tools/cloud.py @@ -7,6 +7,7 @@ import base64 import glob +import pathlib import sys from typing import Any from uuid import UUID @@ -537,11 +538,28 @@ def _info_one(self, client: Client): tablefmt="simple", ) ) + + other_apps = get_applications_by_organisation_id.sync(client=client, id=UUID(self.args.org)) + assert isinstance(other_apps, list) + diff_info = [] for diff in diffs: + source_app_id = self.args.app from_release = get_release_by_organisation_id_and_application_id_and_release_id.sync( - client=client, id=UUID(self.args.org), application_id=self.args.app, release_id=diff.from_release_id + client=client, id=UUID(self.args.org), application_id=source_app_id, release_id=diff.from_release_id ) + if not isinstance(from_release, models.ApplicationRelease): + # Try the other applications in the organisation + for other_app in other_apps: + from_release = get_release_by_organisation_id_and_application_id_and_release_id.sync( + client=client, + id=UUID(self.args.org), + application_id=other_app.id, + release_id=diff.from_release_id, + ) + if isinstance(from_release, models.ApplicationRelease): + source_app_id = other_app.id + break if not isinstance(from_release, models.ApplicationRelease): print(f"Failed to query information about source release {diff.from_release_id}") continue @@ -549,11 +567,13 @@ def _info_one(self, client: Client): from_version_str = ( f"{from_version.major}.{from_version.minor}.{from_version.revision}+{from_version.build_num:08x}" ) - diff_info.append([from_version_str, diff.file.coap_path, diff.file.len_, diff.file.crc]) + diff_info.append( + [f"0x{source_app_id:08x}", from_version_str, diff.file.coap_path, diff.file.len_, diff.file.crc] + ) if len(diff_info) > 0: print("~~~ Diffs ~~~") - print(tabulate(diff_info, headers=["From Version", "Path", "Length", "CRC"])) + print(tabulate(diff_info, headers=["From App", "From Version", "Path", "Length", "CRC"])) def _info_all(self, client: Client): releases = get_releases_by_organisation_id_and_application_id.sync( @@ -642,7 +662,7 @@ def upload(self, client: Client): self._board_name = boards[idx].name else: board = get_board_by_id.sync(client=client, id=self._board) - if not isinstance(org, models.Board): + if not isinstance(board, models.Board): sys.exit(f"Failed to query board for ID {self._board}") self._board_name = board.name @@ -677,24 +697,26 @@ def upload(self, client: Client): if len(ota_files) != 1: sys.exit(f"Unexpected OTA file search result {ota_files}") - releases = get_releases_by_organisation_id_and_application_id.sync( - client=client, - id=self._org, - application_id=app_id, - ) - if not isinstance(releases, list): - sys.exit(f"Unexpected release query result {releases}") - - cloud_release: None | models.ApplicationRelease = None - cloud_releases_by_version: dict[Version, models.ApplicationRelease] = {} - for r in releases: - v = Version(r.version.major, r.version.minor, r.version.revision, r.version.build_num) - cloud_releases_by_version[v] = r - if v == version: - print(f"Found release for application '0x{app_id:08x} {str(version)}' ({r.id})") - cloud_release = r - - if cloud_release is None: + def get_all_releases(org: UUID, app_id: int) -> dict[Version, models.ApplicationRelease]: + releases = get_releases_by_organisation_id_and_application_id.sync( + client=client, + id=org, + application_id=app_id, + ) + if not isinstance(releases, list): + sys.exit(f"Unexpected release query result {releases}") + + by_version: dict[Version, models.ApplicationRelease] = {} + for r in releases: + v = Version(r.version.major, r.version.minor, r.version.revision, r.version.build_num) + by_version[v] = r + return by_version + + cloud_releases_by_version = get_all_releases(self._org, app_id) + cloud_release = cloud_releases_by_version.get(version) + if cloud_release is not None: + print(f"Found release for application '0x{app_id:08x} {str(version)}' ({cloud_release.id})") + else: dialog = ( f"Create release for application '0x{app_id:08x} {str(version)}'" + f" in organisation '{self._org_name}' for board '{self._board_name}'?" @@ -730,37 +752,64 @@ def upload(self, client: Client): print(f"Release created with ID '{rsp.id}'") cloud_release = rsp + def upload_diffs_from_application( + org: UUID, + application: models.Application, + releases_from_version: dict[Version, models.ApplicationRelease], + diff_folder: pathlib.Path, + ): + for path in diff_folder.iterdir(): + if path.is_dir(): + try: + other_app_id = int(path.stem, 16) + except ValueError: + print(f"{path.stem} does not appear to be an application ID") + continue + other_application = get_application_by_organisation_id_and_application_id.sync( + client=client, id=org, application_id=other_app_id + ) + if not isinstance(other_application, models.Application): + print(f"Could not retrieve application with ID {path.stem}") + continue + other_application_releases = get_all_releases(org, other_application.id) + upload_diffs_from_application(org, other_application, other_application_releases, path) + continue + elif path.suffix != ".bin": + continue + try: + diff_from_version = Version.from_string(path.stem) + except ValueError: + print(f"Couldn't parse diff files version '{path.stem}'") + continue + from_version = releases_from_version.get(diff_from_version) + if from_version is None: + print(f"Version {diff_from_version} doesn't exist on cloud for application 0x{application.id:08x}") + continue + + with open(path, "rb") as f: + diff_file = File(f, str(path), None) + + create_body = models.CreateReleaseDiffBody(file=diff_file, from_release_id=from_version.id) + diff_rsp = create_release_diff.sync( + client=client, + id=org, + application_id=app_id, + release_id=cloud_release.id, + body=create_body, + ) + prefix = f"{str(diff_from_version)} -> {str(version)}" + if isinstance(diff_rsp, models.Error): + print(f"{prefix}: <{diff_rsp.code}> {diff_rsp.message}") + elif isinstance(diff_rsp, models.ApplicationReleaseDiff): + print(f"{prefix}: Diff created with ID '{diff_rsp.id}'") + else: + print(f"{prefix}: No response") + # Upload any diffs diff_folder = release.dir / "diffs" if not diff_folder.exists(): return - for path in diff_folder.iterdir(): - if not path.is_file() or path.suffix != ".bin": - continue - try: - diff_from_version = Version.from_string(path.stem) - except ValueError: - print(f"Couldn't parse diff files version '{path.stem}'") - continue - from_version = cloud_releases_by_version.get(diff_from_version) - if from_version is None: - print(f"Version {diff_from_version} doesn't exist on cloud") - continue - - with open(path, "rb") as f: - diff_file = File(f, str(path), None) - - create_body = models.CreateReleaseDiffBody(file=diff_file, from_release_id=from_version.id) - diff_rsp = create_release_diff.sync( - client=client, id=self._org, application_id=app_id, release_id=cloud_release.id, body=create_body - ) - prefix = f"{str(diff_from_version)} -> {str(version)}" - if isinstance(diff_rsp, models.Error): - print(f"{prefix}: <{diff_rsp.code}> {diff_rsp.message}") - elif isinstance(diff_rsp, models.ApplicationReleaseDiff): - print(f"{prefix}: Diff created with ID '{diff_rsp.id}'") - else: - print(f"{prefix}: No response") + upload_diffs_from_application(self._org, application, cloud_releases_by_version, diff_folder) class SubCommand(InfuseCommand):