| Index: sdk/lib/io/file_system_entity.dart
 | 
| diff --git a/sdk/lib/io/file_system_entity.dart b/sdk/lib/io/file_system_entity.dart
 | 
| index b764f04be7ca151d28701ae498155411f4b86f5f..a00cb03229638eba8ab8c6330e0ec29cb9ee6692 100644
 | 
| --- a/sdk/lib/io/file_system_entity.dart
 | 
| +++ b/sdk/lib/io/file_system_entity.dart
 | 
| @@ -317,6 +317,34 @@ abstract class FileSystemEntity {
 | 
|    FileStat statSync();
 | 
|  
 | 
|  
 | 
| +
 | 
| +  /**
 | 
| +   * Start watch the [FileSystemEntity] for changes.
 | 
| +   *
 | 
| +   * The implementation uses platform-depending event-based APIs for receiving
 | 
| +   * file-system notifixations, thus behvaiour depends on the platform.
 | 
| +   *
 | 
| +   *   * `Windows`: Uses `ReadDirectoryChangesW`. The implementation supports
 | 
| +   *     only watching dirctories but supports recursive watching.
 | 
| +   *   * `Linux`: Uses `inotify`. The implementation supports watching both
 | 
| +   *     files and dirctories, but doesn't support recursive watching.
 | 
| +   *   * `Mac OS`: Uses `FSEvents`. The implementation supports watching both
 | 
| +   *     files and dirctories, and also recursive watching. Note that FSEvents
 | 
| +   *     always use recursion internally, so when disabled, some events are
 | 
| +   *     ignored.
 | 
| +   *
 | 
| +   * The system will start listen for events once the returned [Stream] is
 | 
| +   * being listened to, not when the call to [watch] is issued. Note that the
 | 
| +   * returned [Stream] is endless. To stop the [Stream], simply cancel the
 | 
| +   * subscription.
 | 
| +   */
 | 
| +  Stream<FileSystemEvent> watch({int events: FileSystemEvent.ALL,
 | 
| +                                 bool recursive: false})
 | 
| +     => new _FileSystemWatcher(_trimTrailingPathSeparators(path),
 | 
| +                               events,
 | 
| +                               recursive).stream;
 | 
| +
 | 
| +
 | 
|    /**
 | 
|     * Finds the type of file system object that a path points to. Returns
 | 
|     * a [:Future<FileSystemEntityType>:] that completes with the result.
 | 
| @@ -390,11 +418,214 @@ abstract class FileSystemEntity {
 | 
|        (_getTypeSync(path, true) == FileSystemEntityType.DIRECTORY._type);
 | 
|  
 | 
|  
 | 
| -  static _throwIfError(Object result, String msg) {
 | 
| +  static _throwIfError(Object result, String msg, [String path]) {
 | 
|      if (result is OSError) {
 | 
| -      throw new FileException(msg, result);
 | 
| +      throw new FileException(msg, result, path);
 | 
|      } else if (result is ArgumentError) {
 | 
|        throw result;
 | 
|      }
 | 
|    }
 | 
| +
 | 
| +  static String _trimTrailingPathSeparators(String path) {
 | 
| +    // Don't handle argument errors here.
 | 
| +    if (path is! String) return path;
 | 
| +    if (Platform.operatingSystem == 'windows') {
 | 
| +      while (path.length > 1 &&
 | 
| +             (path.endsWith(Platform.pathSeparator) ||
 | 
| +              path.endsWith('/'))) {
 | 
| +        path = path.substring(0, path.length - 1);
 | 
| +      }
 | 
| +    } else {
 | 
| +      while (path.length > 1 && path.endsWith(Platform.pathSeparator)) {
 | 
| +        path = path.substring(0, path.length - 1);
 | 
| +      }
 | 
| +    }
 | 
| +    return path;
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +
 | 
| +/**
 | 
| + * Base event class emitted by FileSystemWatcher.
 | 
| + */
 | 
| +class FileSystemEvent {
 | 
| +  static const int CREATE = 1 << 0;
 | 
| +  static const int MODIFY = 1 << 1;
 | 
| +  static const int DELETE = 1 << 2;
 | 
| +  static const int MOVE = 1 << 3;
 | 
| +  static const int ALL = CREATE | MODIFY | DELETE | MOVE;
 | 
| +
 | 
| +  static const int _MODIFY_ATTRIBUTES = 1 << 4;
 | 
| +
 | 
| +  /**
 | 
| +   * The type of event. See [FileSystemEvent] for a list of events.
 | 
| +   */
 | 
| +  final int type;
 | 
| +
 | 
| +  /**
 | 
| +   * The path that triggered the event. Depending on the platform and the
 | 
| +   * FileSystemEntity, the path may be relative.
 | 
| +   */
 | 
| +  final String path;
 | 
| +
 | 
| +  FileSystemEvent._(this.type, this.path);
 | 
| +}
 | 
| +
 | 
| +
 | 
| +/**
 | 
| + * File system event for newly created file system objects.
 | 
| + */
 | 
| +class FileSystemCreateEvent extends FileSystemEvent {
 | 
| +  FileSystemCreateEvent._(path)
 | 
| +      : super._(FileSystemEvent.CREATE, path);
 | 
| +
 | 
| +  String toString() => "FileSystemCreateEvent('$path')";
 | 
| +}
 | 
| +
 | 
| +
 | 
| +/**
 | 
| + * File system event for modifications of file system objects.
 | 
| + */
 | 
| +class FileSystemModifyEvent extends FileSystemEvent {
 | 
| +  /**
 | 
| +   * If the content was changed and not only the attributes, [contentChanged]
 | 
| +   * is `true`.
 | 
| +   */
 | 
| +  final bool contentChanged;
 | 
| +
 | 
| +  FileSystemModifyEvent._(path, this.contentChanged)
 | 
| +      : super._(FileSystemEvent.MODIFY, path);
 | 
| +
 | 
| +  String toString() =>
 | 
| +      "FileSystemModifyEvent('$path', contentChanged=$contentChanged)";
 | 
| +}
 | 
| +
 | 
| +
 | 
| +/**
 | 
| + * File system event for deletion of file system objects.
 | 
| + */
 | 
| +class FileSystemDeleteEvent extends FileSystemEvent {
 | 
| +  FileSystemDeleteEvent._(path)
 | 
| +      : super._(FileSystemEvent.DELETE, path);
 | 
| +
 | 
| +  String toString() => "FileSystemDeleteEvent('$path')";
 | 
| +}
 | 
| +
 | 
| +
 | 
| +/**
 | 
| + * File system event for moving of file system objects.
 | 
| + */
 | 
| +class FileSystemMoveEvent extends FileSystemEvent {
 | 
| +  /**
 | 
| +   * If the underlaying implementation is able to identify the destination of
 | 
| +   * the moved file, [destination] will be set. Otherwise, it will be `null`.
 | 
| +   */
 | 
| +  final String destination;
 | 
| +
 | 
| +  FileSystemMoveEvent._(path, this.destination)
 | 
| +      : super._(FileSystemEvent.MOVE, path);
 | 
| +
 | 
| +  String toString() {
 | 
| +    var buffer = new StringBuffer();
 | 
| +    buffer.write("FileSystemMoveEvent('$path'");
 | 
| +    if (destination != null) buffer.write(", '$destination'");
 | 
| +    buffer.write(')');
 | 
| +    return buffer.toString();
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +
 | 
| +class _FileSystemWatcher extends NativeFieldWrapperClass1 {
 | 
| +  final String _path;
 | 
| +  final int _events;
 | 
| +  final bool _recursive;
 | 
| +
 | 
| +  StreamController _controller;
 | 
| +  StreamSubscription _subscription;
 | 
| +
 | 
| +  _FileSystemWatcher(this._path, this._events, this._recursive) {
 | 
| +    _controller = new StreamController(onListen: _listen, onCancel: _cancel);
 | 
| +  }
 | 
| +
 | 
| +  void _listen() {
 | 
| +    int socketId;
 | 
| +    try {
 | 
| +      socketId = _watchPath(_path, _events, identical(true, _recursive));
 | 
| +    } catch (e) {
 | 
| +      throw new FileException(
 | 
| +          "Failed to watch path",
 | 
| +          _path,
 | 
| +          e);
 | 
| +    }
 | 
| +    var socket = new _RawSocket(new _NativeSocket.watch(socketId));
 | 
| +    _subscription = socket.expand((event) {
 | 
| +      var events = [];
 | 
| +      var pair = {};
 | 
| +      if (event == RawSocketEvent.READ) {
 | 
| +        String getPath(event) {
 | 
| +          var path = _path;
 | 
| +          if (event[2] != null) {
 | 
| +            path += Platform.pathSeparator;
 | 
| +            path += event[2];
 | 
| +          }
 | 
| +          return path;
 | 
| +        }
 | 
| +        while (socket.available() > 0) {
 | 
| +          for (var event in _readEvents()) {
 | 
| +            if (event == null) continue;
 | 
| +            var path = getPath(event);
 | 
| +            if ((event[0] & FileSystemEvent.CREATE) != 0) {
 | 
| +              events.add(new FileSystemCreateEvent._(path));
 | 
| +            }
 | 
| +            if ((event[0] & FileSystemEvent.MODIFY) != 0) {
 | 
| +              events.add(new FileSystemModifyEvent._(path, true));
 | 
| +            }
 | 
| +            if ((event[0] & FileSystemEvent._MODIFY_ATTRIBUTES) != 0) {
 | 
| +              events.add(new FileSystemModifyEvent._(path, false));
 | 
| +            }
 | 
| +            if ((event[0] & FileSystemEvent.MOVE) != 0) {
 | 
| +              int link = event[1];
 | 
| +              if (link > 0) {
 | 
| +                if (pair.containsKey(link)) {
 | 
| +                  events.add(
 | 
| +                      new FileSystemMoveEvent._(getPath(pair[link]), path));
 | 
| +                  pair.remove(link);
 | 
| +                } else {
 | 
| +                  pair[link] = event;
 | 
| +                }
 | 
| +              } else {
 | 
| +                events.add(new FileSystemMoveEvent._(path, null));
 | 
| +              }
 | 
| +            }
 | 
| +            if ((event[0] & FileSystemEvent.DELETE) != 0) {
 | 
| +              events.add(new FileSystemDeleteEvent._(path));
 | 
| +            }
 | 
| +          }
 | 
| +        }
 | 
| +        for (var event in pair.values) {
 | 
| +          events.add(new FileSystemMoveEvent._(getPath(event), null));
 | 
| +        }
 | 
| +      } else if (event == RawSocketEvent.CLOSED) {
 | 
| +      } else if (event == RawSocketEvent.READ_CLOSED) {
 | 
| +      } else {
 | 
| +        assert(false);
 | 
| +      }
 | 
| +      return events;
 | 
| +    })
 | 
| +    .where((event) => (event.type & _events) != 0)
 | 
| +    .listen(_controller.add, onDone: _cancel);
 | 
| +  }
 | 
| +
 | 
| +  void _cancel() {
 | 
| +    _unwatchPath();
 | 
| +    if (_subscription != null) {
 | 
| +      _subscription.cancel();
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  Stream<FileSystemEvent> get stream => _controller.stream;
 | 
| +
 | 
| +  external int _watchPath(String path, int events, bool recursive);
 | 
| +  external void _unwatchPath();
 | 
| +  external List _readEvents();
 | 
|  }
 | 
| 
 |