Rename Observatory to VMService (#5349)

diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
new file mode 100644
index 0000000..569a35e
--- /dev/null
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -0,0 +1,355 @@
+// Copyright 2016 The Chromium 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 'dart:async';
+import 'dart:convert' show BASE64;
+import 'dart:io';
+
+import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
+import 'package:web_socket_channel/io.dart';
+
+import 'globals.dart';
+
+// TODO(johnmccutchan): Rename this class to ServiceProtocol or VmService.
+class VMService {
+  VMService._(this.peer, this.port, this.httpAddress) {
+    peer.registerMethod('streamNotify', (rpc.Parameters event) {
+      _handleStreamNotify(event.asMap);
+    });
+
+    onIsolateEvent.listen((Event event) {
+      if (event.kind == 'IsolateStart') {
+        _addIsolate(event.isolate);
+      } else if (event.kind == 'IsolateExit') {
+        String removedId = event.isolate.id;
+        isolates.removeWhere((IsolateRef ref) => ref.id == removedId);
+      }
+    });
+  }
+
+  static Future<VMService> connect(int port) async {
+    Uri uri = new Uri(scheme: 'ws', host: '127.0.0.1', port: port, path: 'ws');
+    WebSocket ws = await WebSocket.connect(uri.toString());
+    rpc.Peer peer = new rpc.Peer(new IOWebSocketChannel(ws));
+    peer.listen();
+    Uri httpAddress = new Uri(scheme: 'http', host: '127.0.0.1', port: port);
+    return new VMService._(peer, port, httpAddress);
+  }
+  final Uri httpAddress;
+  final rpc.Peer peer;
+  final int port;
+
+  List<IsolateRef> isolates = <IsolateRef>[];
+  Completer<IsolateRef> _waitFirstIsolateCompleter;
+
+  Map<String, StreamController<Event>> _eventControllers = <String, StreamController<Event>>{};
+
+  Set<String> _listeningFor = new Set<String>();
+
+  bool get isClosed => peer.isClosed;
+  Future<Null> get done => peer.done;
+
+  String get firstIsolateId => isolates.isEmpty ? null : isolates.first.id;
+
+  // Events
+
+  Stream<Event> get onExtensionEvent => onEvent('Extension');
+  // IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, ServiceExtensionAdded
+  Stream<Event> get onIsolateEvent => onEvent('Isolate');
+  Stream<Event> get onTimelineEvent => onEvent('Timeline');
+
+  // Listen for a specific event name.
+  Stream<Event> onEvent(String streamId) {
+    streamListen(streamId);
+    return _getEventController(streamId).stream;
+  }
+
+  StreamController<Event> _getEventController(String eventName) {
+    StreamController<Event> controller = _eventControllers[eventName];
+    if (controller == null) {
+      controller = new StreamController<Event>.broadcast();
+      _eventControllers[eventName] = controller;
+    }
+    return controller;
+  }
+
+  void _handleStreamNotify(Map<String, dynamic> data) {
+    Event event = new Event(data['event']);
+    _getEventController(data['streamId']).add(event);
+  }
+
+  Future<Null> populateIsolateInfo() async {
+    // Calling this has the side effect of populating the isolate information.
+    await waitFirstIsolate;
+  }
+
+  Future<IsolateRef> get waitFirstIsolate async {
+    if (isolates.isNotEmpty)
+      return isolates.first;
+
+    _waitFirstIsolateCompleter ??= new Completer<IsolateRef>();
+
+    getVM().then((VM vm) {
+      for (IsolateRef isolate in vm.isolates)
+        _addIsolate(isolate);
+    });
+
+    return _waitFirstIsolateCompleter.future;
+  }
+
+  // Requests
+
+  Future<Response> sendRequest(String method, [Map<String, dynamic> args]) {
+    return peer.sendRequest(method, args).then((dynamic result) => new Response(result));
+  }
+
+  Future<Null> streamListen(String streamId) async {
+    if (!_listeningFor.contains(streamId)) {
+      _listeningFor.add(streamId);
+      sendRequest('streamListen', <String, dynamic>{ 'streamId': streamId });
+    }
+  }
+
+  Future<VM> getVM() {
+    return peer.sendRequest('getVM').then((dynamic result) {
+      return new VM(result);
+    });
+  }
+
+  Future<Map<String, dynamic>> reloadSources(String isolateId) async {
+    try {
+      Response response =
+          await sendRequest('_reloadSources',
+                            <String, dynamic>{ 'isolateId': isolateId });
+      return response.response;
+    } catch (e) {
+      return new Future<Map<String, dynamic>>.error(e.data['details']);
+    }
+  }
+
+  Future<List<Map<String, String>>> getViewList() async {
+    Map<String, dynamic> response = await peer.sendRequest('_flutter.listViews');
+    List<Map<String, String>> views = response['views'];
+    return views;
+  }
+
+  Future<String> getFirstViewId() async {
+    List<Map<String, String>> views = await getViewList();
+    return views[0]['id'];
+  }
+
+  Future<Null> runInView(String viewId,
+                         String main,
+                         String packages,
+                         String assetsDirectory) async {
+    await peer.sendRequest('_flutter.runInView',
+                           <String, dynamic> {
+                             'viewId': viewId,
+                             'mainScript': main,
+                             'packagesFile': packages,
+                             'assetDirectory': assetsDirectory
+                           });
+    return null;
+  }
+
+  Future<Response> clearVMTimeline() => sendRequest('_clearVMTimeline');
+
+  Future<Response> setVMTimelineFlags(List<String> recordedStreams) {
+    assert(recordedStreams != null);
+
+    return sendRequest('_setVMTimelineFlags', <String, dynamic> {
+      'recordedStreams': recordedStreams
+    });
+  }
+
+  Future<Response> getVMTimeline() => sendRequest('_getVMTimeline');
+
+  // DevFS / VM virtual file system methods
+
+  /// Create a new file system.
+  Future<CreateDevFSResponse> createDevFS(String fsName) async {
+    Response response = await sendRequest('_createDevFS', <String, dynamic> { 'fsName': fsName });
+    return new CreateDevFSResponse(response.response);
+  }
+
+  /// List the available file systems.
+  Future<List<String>> listDevFS() {
+    return sendRequest('_listDevFS').then((Response response) {
+      return response.response['fsNames'];
+    });
+  }
+
+  // Write one file into a file system.
+  Future<Response> writeDevFSFile(String fsName, {
+    String path,
+    List<int> fileContents
+  }) {
+    assert(path != null);
+    assert(fileContents != null);
+
+    return sendRequest('_writeDevFSFile', <String, dynamic> {
+      'fsName': fsName,
+      'path': path,
+      'fileContents': BASE64.encode(fileContents)
+    });
+  }
+
+  // Read one file from a file system.
+  Future<List<int>> readDevFSFile(String fsName, String path) {
+    return sendRequest('_readDevFSFile', <String, dynamic> {
+      'fsName': fsName,
+      'path': path
+    }).then((Response response) {
+      return BASE64.decode(response.response['fileContents']);
+    });
+  }
+
+  /// The complete list of a file system.
+  Future<List<String>> listDevFSFiles(String fsName) {
+    return sendRequest('_listDevFSFiles', <String, dynamic> {
+      'fsName': fsName
+    }).then((Response response) {
+      return response.response['files'];
+    });
+  }
+
+  /// Delete an existing file system.
+  Future<Response> deleteDevFS(String fsName) {
+    return sendRequest('_deleteDevFS', <String, dynamic> { 'fsName': fsName });
+  }
+
+  // Flutter extension methods.
+
+  Future<Response> flutterDebugDumpApp(String isolateId) {
+    return peer.sendRequest('ext.flutter.debugDumpApp', <String, dynamic>{
+      'isolateId': isolateId
+    }).then((dynamic result) => new Response(result));
+  }
+
+  Future<Response> flutterDebugDumpRenderTree(String isolateId) {
+    return peer.sendRequest('ext.flutter.debugDumpRenderTree', <String, dynamic>{
+      'isolateId': isolateId
+    }).then((dynamic result) => new Response(result));
+  }
+
+  // Loader page extension methods.
+
+  Future<Response> flutterLoaderShowMessage(String isolateId, String message) {
+    return peer.sendRequest('ext.flutter.loaderShowMessage', <String, dynamic>{
+      'isolateId': isolateId,
+      'value': message
+    }).then(
+      (dynamic result) => new Response(result),
+      onError: (dynamic exception) { printTrace('ext.flutter.loaderShowMessage: $exception'); }
+    );
+  }
+
+  Future<Response> flutterLoaderSetProgress(String isolateId, double progress) {
+    return peer.sendRequest('ext.flutter.loaderSetProgress', <String, dynamic>{
+      'isolateId': isolateId,
+      'loaderSetProgress': progress
+    }).then(
+      (dynamic result) => new Response(result),
+      onError: (dynamic exception) { printTrace('ext.flutter.loaderSetProgress: $exception'); }
+    );
+  }
+
+  Future<Response> flutterLoaderSetProgressMax(String isolateId, double max) {
+    return peer.sendRequest('ext.flutter.loaderSetProgressMax', <String, dynamic>{
+      'isolateId': isolateId,
+      'loaderSetProgressMax': max
+    }).then(
+      (dynamic result) => new Response(result),
+      onError: (dynamic exception) { printTrace('ext.flutter.loaderSetProgressMax: $exception'); }
+    );
+  }
+
+  /// Causes the application to pick up any changed code.
+  Future<Response> flutterReassemble(String isolateId) {
+    return peer.sendRequest('ext.flutter.reassemble', <String, dynamic>{
+      'isolateId': isolateId
+    }).then((dynamic result) => new Response(result));
+  }
+
+  Future<Response> flutterEvictAsset(String isolateId, String assetPath) {
+    return peer.sendRequest('ext.flutter.evict', <String, dynamic>{
+      'isolateId': isolateId,
+      'value': assetPath
+    }).then((dynamic result) => new Response(result));
+  }
+
+  Future<Response> flutterExit(String isolateId) {
+    return peer
+      .sendRequest('ext.flutter.exit', <String, dynamic>{ 'isolateId': isolateId })
+      .then((dynamic result) => new Response(result))
+      .timeout(new Duration(seconds: 2), onTimeout: () => null);
+  }
+
+  void _addIsolate(IsolateRef isolate) {
+    if (!isolates.contains(isolate)) {
+      isolates.add(isolate);
+
+      if (_waitFirstIsolateCompleter != null) {
+        _waitFirstIsolateCompleter.complete(isolate);
+        _waitFirstIsolateCompleter = null;
+      }
+    }
+  }
+}
+
+class Response {
+  Response(this.response);
+
+  final Map<String, dynamic> response;
+
+  String get type => response['type'];
+
+  dynamic operator[](String key) => response[key];
+
+  @override
+  String toString() => response.toString();
+}
+
+class CreateDevFSResponse extends Response {
+  CreateDevFSResponse(Map<String, dynamic> response) : super(response);
+
+  String get name => response['name'];
+  String get uri => response['uri'];
+}
+
+class VM extends Response {
+  VM(Map<String, dynamic> response) : super(response);
+
+  List<IsolateRef> get isolates => response['isolates'].map((dynamic ref) => new IsolateRef(ref)).toList();
+}
+
+class Event extends Response {
+  Event(Map<String, dynamic> response) : super(response);
+
+  String get kind => response['kind'];
+  IsolateRef get isolate => new IsolateRef.from(response['isolate']);
+
+  /// Only valid for [kind] == `Extension`.
+  String get extensionKind => response['extensionKind'];
+}
+
+class IsolateRef extends Response {
+  IsolateRef(Map<String, dynamic> response) : super(response);
+  factory IsolateRef.from(dynamic ref) => ref == null ? null : new IsolateRef(ref);
+
+  String get id => response['id'];
+
+  @override
+  bool operator ==(dynamic other) {
+    if (identical(this, other))
+      return true;
+    if (other is! IsolateRef)
+      return false;
+    final IsolateRef typedOther = other;
+    return id == typedOther.id;
+  }
+
+  @override
+  int get hashCode => id.hashCode;
+}