| Index: pkg/analysis_server/lib/src/analysis_manager.dart
|
| diff --git a/pkg/analysis_server/lib/src/analysis_manager.dart b/pkg/analysis_server/lib/src/analysis_manager.dart
|
| index f46d1c90f06371b3ec76d16063384ed370d02437..cb001e4c41740c31903371b39ad697f97b979946 100644
|
| --- a/pkg/analysis_server/lib/src/analysis_manager.dart
|
| +++ b/pkg/analysis_server/lib/src/analysis_manager.dart
|
| @@ -5,10 +5,13 @@
|
| import 'dart:async';
|
| import 'dart:convert';
|
| import 'dart:io';
|
| +import 'package:analysis_server/src/channel.dart';
|
| +import 'package:analysis_server/src/domain_server.dart';
|
| +import 'package:analysis_server/src/protocol.dart';
|
|
|
| /**
|
| * [AnalysisManager] is used to launch and manage an analysis server
|
| - * running in a separate process using the static [start] method.
|
| + * running in a separate process using either the [start] or [connect] methods.
|
| */
|
| class AnalysisManager {
|
| // TODO dynamically allocate port and/or allow client to specify port
|
| @@ -18,75 +21,120 @@ class AnalysisManager {
|
| * The analysis server process being managed
|
| * or `null` if managing an analysis server that was already running.
|
| */
|
| - final Process process;
|
| + Process process;
|
|
|
| /**
|
| - * The websocket used to communicate with the analysis server.
|
| + * The socket used to communicate with the analysis server.
|
| */
|
| - final WebSocket socket;
|
| + WebSocket socket;
|
|
|
| /**
|
| - * Launch analysis server in a separate process and return a
|
| - * [Future<AnalysisManager>] for managing that analysis server.
|
| + * Launch analysis server in a separate process
|
| + * and return a future with a manager for that analysis server.
|
| */
|
| - static Future<AnalysisManager> start(String pathToServer) {
|
| - // TODO dynamically allocate port and/or allow client to specify port
|
| - return Process.start(Platform.executable, [pathToServer, "--port",
|
| - PORT.toString()]).then(_attach).catchError((error) {
|
| - print("Failed to launch analysis server: $error");
|
| - exitCode = 1;
|
| - throw error;
|
| - });
|
| + static Future<AnalysisManager> start(String serverPath) {
|
| + return new AnalysisManager()._launchServer(serverPath);
|
| }
|
|
|
| /**
|
| * Open a connection to a running analysis server
|
| + * and return a future with a manager for that analysis server.
|
| */
|
| - static Future<AnalysisManager> connect(String url) {
|
| - return WebSocket.connect(url)
|
| - .then((WebSocket socket) => new AnalysisManager(null, socket));
|
| + static Future<AnalysisManager> connect(String serverUrl) {
|
| + return new AnalysisManager()._openConnection(serverUrl);
|
| }
|
|
|
| /**
|
| - * Attach this process to the newly launched analysis server process,
|
| - * and open a connection to the analysis server.
|
| + * Launch an analysis server and open a connection to that server.
|
| */
|
| - static Future<AnalysisManager> _attach(Process process) {
|
| - var url = 'ws://${InternetAddress.LOOPBACK_IP_V4.address}:$PORT/';
|
| - process.stderr.pipe(stderr);
|
| - Stream out = process.stdout.transform(UTF8.decoder).asBroadcastStream();
|
| - out.listen((line) {
|
| - print(line);
|
| - });
|
| - return out
|
| - .any((String line) => line.startsWith("Listening on port"))
|
| - .then((bool listening) {
|
| - if (!listening) {
|
| - throw "Expected analysis server to listen on a port";
|
| - }
|
| + Future<AnalysisManager> _launchServer(String pathToServer) {
|
| + // TODO dynamically allocate port and/or allow client to specify port
|
| + var serverArgs = [pathToServer, '--port', PORT.toString()];
|
| + return Process.start(Platform.executable, serverArgs)
|
| + .catchError((error) {
|
| + exitCode = 1;
|
| + throw 'Failed to launch analysis server: $error';
|
| })
|
| - .then((_) => WebSocket.connect(url))
|
| - .then((WebSocket socket) => new AnalysisManager(process, socket))
|
| + .then(_listenForPort);
|
| + }
|
| +
|
| + /**
|
| + * Listen for a port from the given analysis server process.
|
| + */
|
| + Future<AnalysisManager> _listenForPort(Process process) {
|
| + this.process = process;
|
| +
|
| + // Echo stdout and stderr
|
| + Stream out = process.stdout.transform(UTF8.decoder).asBroadcastStream();
|
| + out.listen((line) => print(line));
|
| + process.stderr.pipe(stderr);
|
| +
|
| + // Listen for port from server
|
| + const String pattern = 'Listening on port ';
|
| + return out.firstWhere((String line) => line.startsWith(pattern))
|
| + .timeout(new Duration(seconds: 10))
|
| .catchError((error) {
|
| - process.kill();
|
| - print("Failed to connect to analysis server: $error");
|
| exitCode = 1;
|
| - throw error;
|
| + process.kill();
|
| + throw 'Expected port from analysis server';
|
| + })
|
| + .then((String line) {
|
| + String port = line.substring(pattern.length).trim();
|
| + var url = 'ws://${InternetAddress.LOOPBACK_IP_V4.address}:$port/';
|
| + return _openConnection(url);
|
| });
|
| }
|
|
|
| /**
|
| - * Create a new instance that manages the specified analysis server process.
|
| + * Open a connection to the analysis server using the given URL.
|
| */
|
| - AnalysisManager(this.process, this.socket);
|
| + Future<AnalysisManager> _openConnection(String serverUrl) {
|
| + var onError = (error) {
|
| + exitCode = 1;
|
| + if (process != null) {
|
| + process.kill();
|
| + }
|
| + throw 'Failed to connect to analysis server at $serverUrl\n $error';
|
| + };
|
| + try {
|
| + return WebSocket.connect(serverUrl)
|
| + .catchError(onError)
|
| + .then((WebSocket socket) {
|
| + this.socket = socket;
|
| + return this;
|
| + });
|
| + } catch (error) {
|
| + onError(error);
|
| + }
|
| + }
|
|
|
| /**
|
| * Stop the analysis server.
|
| *
|
| - * Returns `true` if the signal is successfully sent and process is killed.
|
| + * Returns `true` if the signal is successfully sent and process terminates.
|
| * Otherwise there was no attached process or the signal could not be sent,
|
| * usually meaning that the process is already dead.
|
| */
|
| - // TODO request analysis server stop
|
| - bool stop() => process != null ? process.kill() : false;
|
| + Future<bool> stop() {
|
| + if (process == null) {
|
| + return new Future.value(false);
|
| + }
|
| + var shutdownRequest = {
|
| + Request.ID : '0',
|
| + Request.METHOD : ServerDomainHandler.SHUTDOWN_METHOD };
|
| + socket.add(JSON.encoder.convert(shutdownRequest));
|
| + return process.exitCode
|
| + .timeout(new Duration(seconds: 10))
|
| + .catchError((error) {
|
| + process.kill();
|
| + throw 'Expected server to shutdown';
|
| + })
|
| + .then((result) {
|
| + if (result != 0) {
|
| + exitCode = result;
|
| + }
|
| + return true;
|
| + });
|
| + }
|
| +
|
| }
|
|
|