recoverPendingInstall method

Future<void> recoverPendingInstall()

Recovers a pending native install marker from the app-owned store.

Implementation

Future<void> recoverPendingInstall() async {
  final UpdateInstallRecoveryMarker? marker;
  try {
    marker = await recoveryStore.readPendingInstall(channel: channel);
  } on Object catch (error) {
    _diagnosticsRecorder.record(
      stage: UpdateDiagnosticStage.install,
      level: UpdateDiagnosticLevel.warning,
      message: "Recovery marker read failed.",
      error: error,
    );
    notifyListeners();
    return;
  }

  if (marker == null) {
    return;
  }

  _diagnosticsRecorder
    ..clear()
    ..record(
      stage: UpdateDiagnosticStage.install,
      level: UpdateDiagnosticLevel.warning,
      message: "Pending install marker found for "
          "${marker.updateVersion ?? "unknown update"}.",
    );
  _diagnosticsRecorder.record(
    stage: UpdateDiagnosticStage.install,
    level: UpdateDiagnosticLevel.info,
    message: "Recovery started for pending install.",
  );

  final transactionId = marker.transactionId;
  if (transactionId != null && transactionId.isNotEmpty) {
    final NativeInstallTransactionStatus? nativeStatus;
    try {
      final recovery = DesktopUpdaterPlatform.instance.nativeInstallRecovery;
      if (_isWindows) {
        if (recovery is! AtomicAfterExitNativeInstallRecovery) {
          throw StateError(
            "Windows native recovery must be atomic after caller exit.",
          );
        }
        nativeStatus = await recovery
            .resolvePendingInstallTransactionAfterExit(transactionId);
      } else {
        if (recovery is! QueryAndRecoverNativeInstallRecovery) {
          throw StateError(
            "This platform must expose query/recover native recovery.",
          );
        }
        var status = await recovery.queryInstallTransaction(transactionId);
        if (status?.requiresRecovery ?? false) {
          const maximumRecoveryAttempts = 6;
          var attempts = 0;
          do {
            status = await recovery
                .recoverPendingInstallTransaction(transactionId);
            attempts += 1;
          } while ((status?.awaitsCallerExit ?? false) &&
              attempts < maximumRecoveryAttempts);
        }
        nativeStatus = status;
      }
    } on Object catch (error) {
      _failRecoveredInstall(
        marker,
        StateError("Could not verify native install transaction status."),
        message: "Could not verify native install transaction status.",
        appVersion: marker.appVersion,
        error: error,
      );
      return;
    }

    // A custom released platform implementation has no native recovery
    // lookup surface, so preserve its existing version-only behavior.
    if (nativeStatus?.awaitsCallerExit ?? false) {
      _state = const UpdateInstalling();
      notifyListeners();
      return;
    }
    if (nativeStatus != null && !nativeStatus.isTerminalSuccess) {
      if (nativeStatus.isTerminalFailure) {
        final cleaned = await _cleanupRecoveredOwnedStage(marker);
        if (cleaned) {
          await _clearPendingRecoveryMarker();
        }
      }
      _failRecoveredInstall(
        marker,
        StateError("Native helper did not confirm a completed install."),
        message: "Native helper did not confirm a completed install.",
        appVersion: marker.appVersion,
      );
      return;
    }
  }

  final DesktopVersionInfo? currentVersion;
  try {
    currentVersion = await currentVersionInfo();
  } on Object catch (error) {
    _failRecoveredInstall(
      marker,
      StateError("Could not verify completed install after relaunch."),
      message: "Could not verify completed install after relaunch.",
      appVersion: marker.appVersion,
      error: error,
    );
    return;
  }

  if (currentVersion == null) {
    _failRecoveredInstall(
      marker,
      StateError("Could not verify completed install after relaunch."),
      message: "Could not verify completed install after relaunch.",
      appVersion: marker.appVersion,
    );
    return;
  }

  final currentAppVersion = _formatVersionInfo(currentVersion);
  _currentAppVersion = currentAppVersion ?? marker.appVersion;
  if (_matchesRecoveredTarget(currentVersion, marker)) {
    if (!await _cleanupRecoveredOwnedStage(marker)) {
      _failRecoveredInstall(
        marker,
        StateError("Completed install stage cleanup failed."),
        message: "Completed install stage cleanup failed.",
        appVersion: _currentAppVersion,
      );
      return;
    }
    await _clearPendingRecoveryMarker();
    _state = const UpdateIdle();
    notifyListeners();
    return;
  }

  _failRecoveredInstall(
    marker,
    StateError("Pending install did not complete after relaunch."),
    message: "Pending install did not complete after relaunch.",
    appVersion: _currentAppVersion,
  );
}