| // Copyright (c) 2014, 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'; |
| |
| import 'package:analysis_server/src/channel.dart'; |
| import 'package:analysis_server/src/constants.dart'; |
| import 'package:analysis_server/src/protocol.dart'; |
| |
| /** |
| * [AnalysisManager] is used to launch and manage an analysis server |
| * 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 |
| static const int PORT = 3333; |
| |
| /** |
| * The analysis server process being managed |
| * or `null` if managing an analysis server that was already running. |
| */ |
| Process process; |
| |
| /** |
| * The channel used to communicate with the analysis server. |
| */ |
| ClientCommunicationChannel channel; |
| |
| /** |
| * Launch analysis server in a separate process |
| * and return a future with a manager for that analysis server. |
| */ |
| 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 serverUrl) { |
| return new AnalysisManager()._openConnection(serverUrl); |
| } |
| |
| /** |
| * Launch an analysis server and open a connection to that server. |
| */ |
| Future<AnalysisManager> _launchServer(String pathToServer) { |
| // TODO dynamically allocate port and/or allow client to specify port |
| List<String> serverArgs = [pathToServer, '--port', PORT.toString()]; |
| return Process.start(Platform.executable, serverArgs) |
| .catchError((error) { |
| exitCode = 1; |
| throw 'Failed to launch analysis server: $error'; |
| }) |
| .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) { |
| exitCode = 1; |
| process.kill(); |
| throw 'Expected port from analysis server'; |
| }) |
| .then((String line) { |
| String port = line.substring(pattern.length).trim(); |
| String url = 'ws://${InternetAddress.LOOPBACK_IP_V4.address}:$port/'; |
| return _openConnection(url); |
| }); |
| } |
| |
| /** |
| * Open a connection to the analysis server using the given URL. |
| */ |
| Future<AnalysisManager> _openConnection(String serverUrl) { |
| Function 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.channel = new WebSocketClientChannel(socket); |
| return this; |
| }); |
| } catch (error) { |
| onError(error); |
| } |
| } |
| |
| /** |
| * Stop the analysis server. |
| * |
| * 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. |
| */ |
| Future<bool> stop() { |
| if (process == null) { |
| return channel.close().then((_) => false); |
| } |
| return channel |
| .sendRequest(new Request('0', SERVER_SHUTDOWN)) |
| .timeout(new Duration(seconds: 2), onTimeout: () { |
| print('Expected shutdown response'); |
| }) |
| .then((Response response) { |
| return channel.close().then((_) => process.exitCode); |
| }) |
| .timeout(new Duration(seconds: 2), onTimeout: () { |
| print('Expected server to shutdown'); |
| process.kill(); |
| }) |
| .then((int result) { |
| if (result != null && result != 0) { |
| exitCode = result; |
| } |
| return true; |
| }); |
| } |
| } |