blob: 484397baf5fc61fa4fcfc10557f09d4d6aecdcad [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
/// Type of callbacks used to process notification.
typedef void NotificationProcessor(String event, Map<String, Object> params);
/// Instances of the class [AnalysisServerClient] manage a connection to an
/// [AnalysisServer] process, and facilitate communication to and from the
/// client/user.
class AnalysisServerClient {
/// AnalysisServer process object, or null if the server has been shut down.
final Process _process;
/// Commands that have been sent to the server but not yet acknowledged,
/// and the [Completer] objects which should be completed when
/// acknowledgement is received.
final Map<String, Completer> _pendingCommands = <String, Completer>{};
/// Number which should be used to compute the 'id' to send to the next
/// command sent to the server.
int _nextId = 0;
AnalysisServerClient(this._process);
/// Return a future that will complete when all commands that have been
/// sent to the server so far have been flushed to the OS buffer.
Future<Null> flushCommands() {
return _process.stdin.flush();
}
/// Force kill the server. Returns exit code future.
Future<int> kill() {
_process.kill();
return _process.exitCode;
}
void listenToOutput({NotificationProcessor notificationProcessor}) {
_process.stdout
.transform((new Utf8Codec()).decoder)
.transform(new LineSplitter())
.listen((String line) {
String trimmedLine = line.trim();
if (trimmedLine.startsWith('Observatory listening on ')) {
return;
}
final result = json.decoder.convert(trimmedLine) as Map;
if (result.containsKey('id')) {
final id = result['id'] as String;
final completer = _pendingCommands.remove(id);
if (result.containsKey('error')) {
completer.completeError(new ServerErrorMessage(result['error']));
} else {
completer.complete(result['result']);
}
} else if (notificationProcessor != null && result.containsKey('event')) {
// Message is a notification. It should have an event and possibly
// params.
notificationProcessor(result['event'], result['params']);
}
});
}
/// Sends a command to the server. An 'id' will be automatically assigned.
/// The returned [Future] will be completed when the server acknowledges
/// the command with a response. If the server acknowledges the command
/// with a normal (non-error) response, the future will be completed
/// with the 'result' field from the response. If the server acknowledges
/// the command with an error response, the future will be completed with an
/// error.
Future send(String method, Map<String, dynamic> params) {
String id = '${_nextId++}';
Map<String, dynamic> command = <String, dynamic>{
'id': id,
'method': method
};
if (params != null) {
command['params'] = params;
}
Completer completer = new Completer();
_pendingCommands[id] = completer;
String commandAsJson = json.encode(command);
_process.stdin.add(utf8.encoder.convert('$commandAsJson\n'));
return completer.future;
}
}
class ServerErrorMessage {
final Map errorJson;
ServerErrorMessage(this.errorJson);
String get code => errorJson['code'].toString();
String get message => errorJson['message'];
String get stackTrace => errorJson['stackTrace'];
}