OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:async'; | 5 import 'dart:async'; |
6 import 'dart:convert'; | 6 import 'dart:convert'; |
7 import 'dart:io'; | 7 import 'dart:io'; |
| 8 import 'package:analysis_server/src/channel.dart'; |
| 9 import 'package:analysis_server/src/domain_server.dart'; |
| 10 import 'package:analysis_server/src/protocol.dart'; |
8 | 11 |
9 /** | 12 /** |
10 * [AnalysisManager] is used to launch and manage an analysis server | 13 * [AnalysisManager] is used to launch and manage an analysis server |
11 * running in a separate process using the static [start] method. | 14 * running in a separate process using either the [start] or [connect] methods. |
12 */ | 15 */ |
13 class AnalysisManager { | 16 class AnalysisManager { |
14 // TODO dynamically allocate port and/or allow client to specify port | 17 // TODO dynamically allocate port and/or allow client to specify port |
15 static const int PORT = 3333; | 18 static const int PORT = 3333; |
16 | 19 |
17 /** | 20 /** |
18 * The analysis server process being managed | 21 * The analysis server process being managed |
19 * or `null` if managing an analysis server that was already running. | 22 * or `null` if managing an analysis server that was already running. |
20 */ | 23 */ |
21 final Process process; | 24 Process process; |
22 | 25 |
23 /** | 26 /** |
24 * The websocket used to communicate with the analysis server. | 27 * The socket used to communicate with the analysis server. |
25 */ | 28 */ |
26 final WebSocket socket; | 29 WebSocket socket; |
27 | 30 |
28 /** | 31 /** |
29 * Launch analysis server in a separate process and return a | 32 * Launch analysis server in a separate process |
30 * [Future<AnalysisManager>] for managing that analysis server. | 33 * and return a future with a manager for that analysis server. |
31 */ | 34 */ |
32 static Future<AnalysisManager> start(String pathToServer) { | 35 static Future<AnalysisManager> start(String serverPath) { |
| 36 return new AnalysisManager()._launchServer(serverPath); |
| 37 } |
| 38 |
| 39 /** |
| 40 * Open a connection to a running analysis server |
| 41 * and return a future with a manager for that analysis server. |
| 42 */ |
| 43 static Future<AnalysisManager> connect(String serverUrl) { |
| 44 return new AnalysisManager()._openConnection(serverUrl); |
| 45 } |
| 46 |
| 47 /** |
| 48 * Launch an analysis server and open a connection to that server. |
| 49 */ |
| 50 Future<AnalysisManager> _launchServer(String pathToServer) { |
33 // TODO dynamically allocate port and/or allow client to specify port | 51 // TODO dynamically allocate port and/or allow client to specify port |
34 return Process.start(Platform.executable, [pathToServer, "--port", | 52 var serverArgs = [pathToServer, '--port', PORT.toString()]; |
35 PORT.toString()]).then(_attach).catchError((error) { | 53 return Process.start(Platform.executable, serverArgs) |
36 print("Failed to launch analysis server: $error"); | 54 .catchError((error) { |
37 exitCode = 1; | 55 exitCode = 1; |
38 throw error; | 56 throw 'Failed to launch analysis server: $error'; |
| 57 }) |
| 58 .then(_listenForPort); |
| 59 } |
| 60 |
| 61 /** |
| 62 * Listen for a port from the given analysis server process. |
| 63 */ |
| 64 Future<AnalysisManager> _listenForPort(Process process) { |
| 65 this.process = process; |
| 66 |
| 67 // Echo stdout and stderr |
| 68 Stream out = process.stdout.transform(UTF8.decoder).asBroadcastStream(); |
| 69 out.listen((line) => print(line)); |
| 70 process.stderr.pipe(stderr); |
| 71 |
| 72 // Listen for port from server |
| 73 const String pattern = 'Listening on port '; |
| 74 return out.firstWhere((String line) => line.startsWith(pattern)) |
| 75 .timeout(new Duration(seconds: 10)) |
| 76 .catchError((error) { |
| 77 exitCode = 1; |
| 78 process.kill(); |
| 79 throw 'Expected port from analysis server'; |
| 80 }) |
| 81 .then((String line) { |
| 82 String port = line.substring(pattern.length).trim(); |
| 83 var url = 'ws://${InternetAddress.LOOPBACK_IP_V4.address}:$port/'; |
| 84 return _openConnection(url); |
39 }); | 85 }); |
40 } | 86 } |
41 | 87 |
42 /** | 88 /** |
43 * Open a connection to a running analysis server | 89 * Open a connection to the analysis server using the given URL. |
44 */ | 90 */ |
45 static Future<AnalysisManager> connect(String url) { | 91 Future<AnalysisManager> _openConnection(String serverUrl) { |
46 return WebSocket.connect(url) | 92 var onError = (error) { |
47 .then((WebSocket socket) => new AnalysisManager(null, socket)); | 93 exitCode = 1; |
| 94 if (process != null) { |
| 95 process.kill(); |
| 96 } |
| 97 throw 'Failed to connect to analysis server at $serverUrl\n $error'; |
| 98 }; |
| 99 try { |
| 100 return WebSocket.connect(serverUrl) |
| 101 .catchError(onError) |
| 102 .then((WebSocket socket) { |
| 103 this.socket = socket; |
| 104 return this; |
| 105 }); |
| 106 } catch (error) { |
| 107 onError(error); |
| 108 } |
48 } | 109 } |
49 | 110 |
50 /** | 111 /** |
51 * Attach this process to the newly launched analysis server process, | |
52 * and open a connection to the analysis server. | |
53 */ | |
54 static Future<AnalysisManager> _attach(Process process) { | |
55 var url = 'ws://${InternetAddress.LOOPBACK_IP_V4.address}:$PORT/'; | |
56 process.stderr.pipe(stderr); | |
57 Stream out = process.stdout.transform(UTF8.decoder).asBroadcastStream(); | |
58 out.listen((line) { | |
59 print(line); | |
60 }); | |
61 return out | |
62 .any((String line) => line.startsWith("Listening on port")) | |
63 .then((bool listening) { | |
64 if (!listening) { | |
65 throw "Expected analysis server to listen on a port"; | |
66 } | |
67 }) | |
68 .then((_) => WebSocket.connect(url)) | |
69 .then((WebSocket socket) => new AnalysisManager(process, socket)) | |
70 .catchError((error) { | |
71 process.kill(); | |
72 print("Failed to connect to analysis server: $error"); | |
73 exitCode = 1; | |
74 throw error; | |
75 }); | |
76 } | |
77 | |
78 /** | |
79 * Create a new instance that manages the specified analysis server process. | |
80 */ | |
81 AnalysisManager(this.process, this.socket); | |
82 | |
83 /** | |
84 * Stop the analysis server. | 112 * Stop the analysis server. |
85 * | 113 * |
86 * Returns `true` if the signal is successfully sent and process is killed. | 114 * Returns `true` if the signal is successfully sent and process terminates. |
87 * Otherwise there was no attached process or the signal could not be sent, | 115 * Otherwise there was no attached process or the signal could not be sent, |
88 * usually meaning that the process is already dead. | 116 * usually meaning that the process is already dead. |
89 */ | 117 */ |
90 // TODO request analysis server stop | 118 Future<bool> stop() { |
91 bool stop() => process != null ? process.kill() : false; | 119 if (process == null) { |
| 120 return new Future.value(false); |
| 121 } |
| 122 var shutdownRequest = { |
| 123 Request.ID : '0', |
| 124 Request.METHOD : ServerDomainHandler.SHUTDOWN_METHOD }; |
| 125 socket.add(JSON.encoder.convert(shutdownRequest)); |
| 126 return process.exitCode |
| 127 .timeout(new Duration(seconds: 10)) |
| 128 .catchError((error) { |
| 129 process.kill(); |
| 130 throw 'Expected server to shutdown'; |
| 131 }) |
| 132 .then((result) { |
| 133 if (result != 0) { |
| 134 exitCode = result; |
| 135 } |
| 136 return true; |
| 137 }); |
| 138 } |
| 139 |
92 } | 140 } |
OLD | NEW |