| Index: runtime/bin/file_patch.dart
|
| diff --git a/runtime/bin/file_patch.dart b/runtime/bin/file_patch.dart
|
| index 9f1f6d06aa165d8289fc49861e936487021335ca..46a640dcdc8e8dbf29e066e405d4a0d5d24b6381 100644
|
| --- a/runtime/bin/file_patch.dart
|
| +++ b/runtime/bin/file_patch.dart
|
| @@ -20,6 +20,7 @@ patch class _File {
|
| /* patch */ static int _openStdio(int fd) native "File_OpenStdio";
|
| }
|
|
|
| +
|
| patch class _RandomAccessFile {
|
| /* patch */ static int _close(int id) native "File_Close";
|
| /* patch */ static _readByte(int id) native "File_ReadByte";
|
| @@ -37,52 +38,125 @@ patch class _RandomAccessFile {
|
| /* patch */ static _flush(int id) native "File_Flush";
|
| }
|
|
|
| -patch class _FileSystemWatcher {
|
| - /* patch */ factory _FileSystemWatcher(
|
| - String path, int events, bool recursive)
|
| - => new _FileSystemWatcherImpl(path, events, recursive);
|
|
|
| - /* patch */ static bool get isSupported => _FileSystemWatcherImpl.isSupported;
|
| +class _WatcherPath {
|
| + final int pathId;
|
| + final String path;
|
| + final int events;
|
| + int count = 0;
|
| + _WatcherPath(this.pathId, this.path, this.events);
|
| }
|
|
|
| -class _FileSystemWatcherImpl
|
| - extends NativeFieldWrapperClass1
|
| - implements _FileSystemWatcher {
|
| +
|
| +patch class _FileSystemWatcher {
|
| + static int _id;
|
| + static final Map<int, _FileSystemWatcherPath> _idMap = {};
|
| +
|
| final String _path;
|
| final int _events;
|
| final bool _recursive;
|
|
|
| - StreamController _controller;
|
| - StreamSubscription _subscription;
|
| + _WatcherPath _watcherPath;
|
| +
|
| + StreamController _broadcastController;
|
| +
|
| + /* patch */ static Stream<FileSystemEvent> watch(
|
| + String path, int events, bool recursive) {
|
| + if (Platform.isLinux) {
|
| + return new _InotifyFileSystemWatcher(path, events, recursive).stream;
|
| + }
|
| + if (Platform.isWindows) {
|
| + return new _Win32FileSystemWatcher(path, events, recursive).stream;
|
| + }
|
| + if (Platform.isMacOS) {
|
| + return new _FSEventStreamFileSystemWatcher(
|
| + path, events, recursive).stream;
|
| + }
|
| + throw new FileSystemException(
|
| + "File system watching is not supported on this platform");
|
| + }
|
|
|
| - _FileSystemWatcherImpl(this._path, this._events, this._recursive) {
|
| + _FileSystemWatcher._(this._path, this._events, this._recursive) {
|
| if (!isSupported) {
|
| throw new FileSystemException(
|
| - "File system watching is not supported on this system",
|
| + "File system watching is not supported on this platform",
|
| _path);
|
| }
|
| - _controller = new StreamController.broadcast(onListen: _listen,
|
| - onCancel: _cancel);
|
| + _broadcastController = new StreamController.broadcast(onListen: _listen,
|
| + onCancel: _cancel);
|
| }
|
|
|
| + Stream get stream => _broadcastController.stream;
|
| +
|
| void _listen() {
|
| - int socketId;
|
| + if (_id == null) {
|
| + try {
|
| + _id = _initWatcher();
|
| + _newWatcher();
|
| + } catch (e) {
|
| + _broadcastController.addError(new FileSystemException(
|
| + "Failed to initialize file system entity watcher"));
|
| + _broadcastController.close();
|
| + return;
|
| + }
|
| + }
|
| + var pathId;
|
| try {
|
| - socketId = _watchPath(_path, _events, identical(true, _recursive));
|
| + pathId = _watchPath(_id, _path, _events, _recursive);
|
| } catch (e) {
|
| - _controller.addError(new FileSystemException(
|
| + _broadcastController.addError(new FileSystemException(
|
| "Failed to watch path", _path, e));
|
| - _controller.close();
|
| + _broadcastController.close();
|
| return;
|
| }
|
| + if (!_idMap.containsKey(pathId)) {
|
| + _idMap[pathId] = new _WatcherPath(pathId, _path, _events);
|
| + }
|
| + _watcherPath = _idMap[pathId];
|
| + _watcherPath.count++;
|
| + _pathWatched().pipe(_broadcastController);
|
| + }
|
| +
|
| + void _cancel() {
|
| + if (_watcherPath != null) {
|
| + assert(_watcherPath.count > 0);
|
| + _watcherPath.count--;
|
| + if (_watcherPath.count == 0) {
|
| + _pathWatchedEnd();
|
| + _unwatchPath(_id, _watcherPath.pathId);
|
| + _idMap.remove(_watcherPath.pathId);
|
| + }
|
| + _watcherPath = null;
|
| + }
|
| + if (_idMap.isEmpty && _id != null) {
|
| + _closeWatcher(_id);
|
| + _doneWatcher();
|
| + _id = null;
|
| + }
|
| + }
|
| +
|
| + // Called when (and after) a new watcher instance is created and available.
|
| + void _newWatcher() {}
|
| + // Called when a watcher is no longer needed.
|
| + void _doneWatcher() {}
|
| + // Called when a new path is being watched.
|
| + Stream _pathWatched() {}
|
| + // Called when a path is no longer being watched.
|
| + void _donePathWatched() {}
|
| +
|
| + static _WatcherPath _pathFromPathId(int pathId) {
|
| + return _idMap[pathId];
|
| + }
|
| +
|
| + static Stream _listenOnSocket(int socketId, int id, int pathId) {
|
| var socket = new _RawSocket(new _NativeSocket.watch(socketId));
|
| - _subscription = socket.expand((event) {
|
| - bool stop = false;
|
| + return socket.expand((event) {
|
| + var stops = [];
|
| var events = [];
|
| var pair = {};
|
| if (event == RawSocketEvent.READ) {
|
| String getPath(event) {
|
| - var path = _path;
|
| + var path = _pathFromPathId(event[4]).path;
|
| if (event[2] != null && event[2].isNotEmpty) {
|
| path += Platform.pathSeparator;
|
| path += event[2];
|
| @@ -96,87 +170,190 @@ class _FileSystemWatcherImpl
|
| }
|
| return (event[0] & FileSystemEvent._IS_DIR) != 0;
|
| }
|
| - void add(event) {
|
| - if ((event.type & _events) == 0) return;
|
| - events.add(event);
|
| + void add(id, event) {
|
| + if ((event.type & _pathFromPathId(id).events) == 0) return;
|
| + events.add([id, event]);
|
| }
|
| void rewriteMove(event, isDir) {
|
| if (event[3]) {
|
| - add(new FileSystemCreateEvent._(getPath(event), isDir));
|
| + add(event[4], new FileSystemCreateEvent._(getPath(event), isDir));
|
| } else {
|
| - add(new FileSystemDeleteEvent._(getPath(event), isDir));
|
| + add(event[4], new FileSystemDeleteEvent._(getPath(event), isDir));
|
| }
|
| }
|
| while (socket.available() > 0) {
|
| - for (var event in _readEvents()) {
|
| + for (var event in _readEvents(id, pathId)) {
|
| if (event == null) continue;
|
| + int pathId = event[4];
|
| bool isDir = getIsDir(event);
|
| var path = getPath(event);
|
| if ((event[0] & FileSystemEvent.CREATE) != 0) {
|
| - add(new FileSystemCreateEvent._(path, isDir));
|
| + add(event[4], new FileSystemCreateEvent._(path, isDir));
|
| }
|
| if ((event[0] & FileSystemEvent.MODIFY) != 0) {
|
| - add(new FileSystemModifyEvent._(path, isDir, true));
|
| + add(event[4], new FileSystemModifyEvent._(path, isDir, true));
|
| }
|
| if ((event[0] & FileSystemEvent._MODIFY_ATTRIBUTES) != 0) {
|
| - add(new FileSystemModifyEvent._(path, isDir, false));
|
| + add(event[4], new FileSystemModifyEvent._(path, isDir, 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]), isDir, path));
|
| - pair.remove(link);
|
| + pair.putIfAbsent(pathId, () => {});
|
| + if (pair[pathId].containsKey(link)) {
|
| + add(event[4],
|
| + new FileSystemMoveEvent._(
|
| + getPath(pair[pathId][link]), isDir, path));
|
| + pair[pathId].remove(link);
|
| } else {
|
| - pair[link] = event;
|
| + pair[pathId][link] = event;
|
| }
|
| } else {
|
| rewriteMove(event, isDir);
|
| }
|
| }
|
| if ((event[0] & FileSystemEvent.DELETE) != 0) {
|
| - add(new FileSystemDeleteEvent._(path, isDir));
|
| + add(event[4], new FileSystemDeleteEvent._(path, isDir));
|
| }
|
| if ((event[0] & FileSystemEvent._DELETE_SELF) != 0) {
|
| - add(new FileSystemDeleteEvent._(path, isDir));
|
| - stop = true;
|
| + add(event[4], new FileSystemDeleteEvent._(path, isDir));
|
| + // Signal done event.
|
| + stops.add([event[4], null]);
|
| }
|
| }
|
| }
|
| - for (var event in pair.values) {
|
| - rewriteMove(event, getIsDir(event));
|
| + for (var map in pair.values) {
|
| + for (var event in map.values) {
|
| + rewriteMove(event, getIsDir(event));
|
| + }
|
| }
|
| } else if (event == RawSocketEvent.CLOSED) {
|
| } else if (event == RawSocketEvent.READ_CLOSED) {
|
| } else {
|
| assert(false);
|
| }
|
| - if (stop) socket.close();
|
| + events.addAll(stops);
|
| return events;
|
| - })
|
| - .listen(_controller.add, onDone: _cancel);
|
| + });
|
| }
|
|
|
| - void _cancel() {
|
| - if (_subscription != null) {
|
| - _unwatchPath();
|
| - _subscription.cancel();
|
| - _subscription = null;
|
| + /* patch */ static bool get isSupported
|
| + native "FileSystemWatcher_IsSupported";
|
| +
|
| + static int _initWatcher() native "FileSystemWatcher_InitWatcher";
|
| + static void _closeWatcher(int id) native "FileSystemWatcher_CloseWatcher";
|
| +
|
| + static int _watchPath(int id, String path, int events, bool recursive)
|
| + native "FileSystemWatcher_WatchPath";
|
| + static void _unwatchPath(int id, int path_id)
|
| + native "FileSystemWatcher_UnwatchPath";
|
| + static List _readEvents(int id, int path_id)
|
| + native "FileSystemWatcher_ReadEvents";
|
| + static int _getSocketId(int id, int path_id)
|
| + native "FileSystemWatcher_GetSocketId";
|
| +}
|
| +
|
| +
|
| +class _InotifyFileSystemWatcher extends _FileSystemWatcher {
|
| + static final Map<int, StreamController> _idMap = {};
|
| + static StreamSubscription _subscription;
|
| +
|
| + _InotifyFileSystemWatcher(path, events, recursive)
|
| + : super._(path, events, recursive);
|
| +
|
| + void _newWatcher() {
|
| + int id = _FileSystemWatcher._id;
|
| + _subscription = _FileSystemWatcher._listenOnSocket(id, id, 0)
|
| + .listen((event) {
|
| + if (_idMap.containsKey(event[0])) {
|
| + if (event[1] != null) {
|
| + _idMap[event[0]].add(event[1]);
|
| + } else {
|
| + _idMap[event[0]].close();
|
| + }
|
| + }
|
| + });
|
| + }
|
| +
|
| + void _doneWatcher() {
|
| + _subscription.cancel();
|
| + }
|
| +
|
| + Stream _pathWatched() {
|
| + var pathId = _watcherPath.pathId;
|
| + if (!_idMap.containsKey(pathId)) {
|
| + _idMap[pathId] = new StreamController.broadcast();
|
| }
|
| + return _idMap[pathId].stream;
|
| + }
|
| +
|
| + void _pathWatchedEnd() {
|
| + var pathId = _watcherPath.pathId;
|
| + if (!_idMap.containsKey(pathId)) return;
|
| + _idMap[pathId].close();
|
| + _idMap.remove(pathId);
|
| + }
|
| +}
|
| +
|
| +
|
| +class _Win32FileSystemWatcher extends _FileSystemWatcher {
|
| + StreamSubscription _subscription;
|
| + StreamController _controller;
|
| +
|
| + _Win32FileSystemWatcher(path, events, recursive)
|
| + : super._(path, events, recursive);
|
| +
|
| + Stream _pathWatched() {
|
| + var pathId = _watcherPath.pathId;
|
| + _controller = new StreamController();
|
| + _subscription = _FileSystemWatcher._listenOnSocket(pathId, 0, pathId)
|
| + .listen((event) {
|
| + assert(event[0] == pathId);
|
| + if (event[1] != null) {
|
| + _controller.add(event[1]);
|
| + } else {
|
| + _controller.close();
|
| + }
|
| + });
|
| + return _controller.stream;
|
| + }
|
| +
|
| + void _pathWatchedEnd() {
|
| + _subscription.cancel();
|
| _controller.close();
|
| }
|
| +}
|
|
|
| - Stream<FileSystemEvent> get stream => _controller.stream;
|
|
|
| - static bool get isSupported native "FileSystemWatcher_IsSupported";
|
| +class _FSEventStreamFileSystemWatcher extends _FileSystemWatcher {
|
| + StreamSubscription _subscription;
|
| + StreamController _controller;
|
|
|
| - int _watchPath(String path, int events, bool recursive)
|
| - native "FileSystemWatcher_WatchPath";
|
| - void _unwatchPath() native "FileSystemWatcher_UnwatchPath";
|
| - List _readEvents() native "FileSystemWatcher_ReadEvents";
|
| + _FSEventStreamFileSystemWatcher(path, events, recursive)
|
| + : super._(path, events, recursive);
|
| +
|
| + Stream _pathWatched() {
|
| + var pathId = _watcherPath.pathId;
|
| + var socketId = _FileSystemWatcher._getSocketId(0, pathId);
|
| + _controller = new StreamController();
|
| + _subscription = _FileSystemWatcher._listenOnSocket(socketId, 0, pathId)
|
| + .listen((event) {
|
| + if (event[1] != null) {
|
| + _controller.add(event[1]);
|
| + } else {
|
| + _controller.close();
|
| + }
|
| + });
|
| + return _controller.stream;
|
| + }
|
| +
|
| + void _pathWatchedEnd() {
|
| + _subscription.cancel();
|
| + _controller.close();
|
| + }
|
| }
|
|
|
| +
|
| Uint8List _makeUint8ListView(Uint8List source, int offsetInBytes, int length) {
|
| return new Uint8List.view(source.buffer, offsetInBytes, length);
|
| }
|
|
|