| 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();
|
| }
|
|
|