| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'package:flutter_tools_core/flutter_tools_core.dart'; | |
| import 'package:flutter_tools_extension/flutter_tools_extension.dart'; | |
| import '../base/logger.dart'; | |
| import '../doctor_validator.dart'; | |
| import 'extension_discovery.dart'; | |
| /// A host [DoctorValidator] adapter that delegates diagnostic checks to a single tool extension. | |
| class ExtensionDoctorValidator extends DoctorValidator { | |
| /// Creates an [ExtensionDoctorValidator] for the given diagnostic [extension]. | |
| ExtensionDoctorValidator({required this.extension, required this._logger}) | |
| : super(extension.title); | |
| /// The active extension service executing diagnostic checks. | |
| final DiagnosticsExtension extension; | |
| final Logger _logger; | |
| @override | |
| String get title => extension.title; | |
| @override | |
| Future<ValidationResult> validateImpl() async { | |
| _logger.printTrace( | |
| 'ExtensionDoctorValidator validating diagnostics for extension "${extension.title}".', | |
| ); | |
| final List<ValidationResult> results = await extension.runDiagnostics(); | |
| _logger.printTrace( | |
| 'ExtensionDoctorValidator received ${results.length} validation result(s) from "${extension.title}".', | |
| ); | |
| final allMessages = <ValidationMessage>[]; | |
| ValidationType aggregateType = ValidationType.success; | |
| String? statusInfo; | |
| for (final result in results) { | |
| statusInfo ??= result.statusInfo; | |
| allMessages.addAll(result.messages); | |
| switch (result.type) { | |
| case ValidationType.crash: | |
| aggregateType = ValidationType.crash; | |
| case ValidationType.missing: | |
| if (aggregateType != ValidationType.crash) { | |
| aggregateType = ValidationType.missing; | |
| } | |
| case ValidationType.partial: | |
| if (aggregateType != ValidationType.crash && aggregateType != ValidationType.missing) { | |
| aggregateType = ValidationType.partial; | |
| } | |
| case ValidationType.notAvailable: | |
| if (aggregateType != ValidationType.crash && | |
| aggregateType != ValidationType.missing && | |
| aggregateType != ValidationType.partial) { | |
| aggregateType = ValidationType.notAvailable; | |
| } | |
| case ValidationType.success: | |
| break; | |
| } | |
| } | |
| return ValidationResult(aggregateType, allMessages, statusInfo: statusInfo); | |
| } | |
| } | |
| /// A host-side [DiagnosticsExtension] client adapter that delegates RPC queries to an [ExtensionConnection]. | |
| class DiagnosticsExtensionClient extends DiagnosticsExtension { | |
| /// Creates a [DiagnosticsExtensionClient] wrapping the host [connection]. | |
| DiagnosticsExtensionClient( | |
| this.connection, { | |
| required this._logger, | |
| this._defaultTitle = 'Tool Extension Diagnostics', | |
| }); | |
| /// The active extension isolate connection. | |
| final ExtensionConnection connection; | |
| final String _defaultTitle; | |
| final Logger _logger; | |
| String? _titleCache; | |
| @override | |
| String get title => _titleCache ?? _defaultTitle; | |
| /// Fetches the validator title from the remote extension isolate. | |
| Future<String> fetchTitle() async { | |
| if (_titleCache != null) { | |
| return _titleCache!; | |
| } | |
| _logger.printTrace( | |
| 'DiagnosticsExtensionClient fetching title via RPC ("${DiagnosticsExtension.getTitleMethod}")...', | |
| ); | |
| try { | |
| final Object? response = await connection.sendRequest(DiagnosticsExtension.getTitleMethod); | |
| if (response is String) { | |
| _titleCache = response; | |
| } else { | |
| _logger.printTrace('DiagnosticsExtensionClient received invalid title response: $response'); | |
| } | |
| } on Object catch (err, stack) { | |
| _logger.printTrace('DiagnosticsExtensionClient failed to fetch title: $err\n$stack'); | |
| } | |
| return _titleCache ?? _defaultTitle; | |
| } | |
| @override | |
| Future<List<ValidationResult>> runDiagnostics() async { | |
| _logger.printTrace( | |
| 'DiagnosticsExtensionClient running diagnostics via RPC ("${DiagnosticsExtension.runDiagnosticsMethod}")...', | |
| ); | |
| try { | |
| final Object? response = await connection.sendRequest( | |
| DiagnosticsExtension.runDiagnosticsMethod, | |
| ); | |
| if (response is List<Object?>) { | |
| final results = <ValidationResult>[]; | |
| for (final Object? element in response) { | |
| if (element is Map) { | |
| results.add(ValidationResult.fromJson(element.cast<String, Object?>())); | |
| } | |
| } | |
| _logger.printTrace( | |
| 'DiagnosticsExtensionClient received ${results.length} result(s) via RPC.', | |
| ); | |
| return results; | |
| } | |
| _logger.printTrace( | |
| 'DiagnosticsExtensionClient received invalid diagnostics response: $response', | |
| ); | |
| } on Object catch (err, stack) { | |
| _logger.printTrace('DiagnosticsExtensionClient failed to run diagnostics: $err\n$stack'); | |
| } | |
| return const <ValidationResult>[]; | |
| } | |
| } |