Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(255)

Unified Diff: sdk/lib/io/file_system_watcher.dart

Issue 19263003: Add FileSystemWatcher class to dart:io. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Cleanup Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: sdk/lib/io/file_system_watcher.dart
diff --git a/sdk/lib/io/file_system_watcher.dart b/sdk/lib/io/file_system_watcher.dart
new file mode 100644
index 0000000000000000000000000000000000000000..5b9b3f7ebe17fc90db6e042317201789f37b65db
--- /dev/null
+++ b/sdk/lib/io/file_system_watcher.dart
@@ -0,0 +1,281 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of dart.io;
+
+
+/**
+ * Base event class emitted by FileSystemWatcher.
+ */
+class FileSystemEvent {
+ /**
+ * The type of event. See [FileSystemEvent] for a list of events.
+ */
+ final int type;
+
+ /**
+ * The path that triggered the event.
+ */
+ final String path;
+
+ FileSystemEvent._(this.type, this.path);
+}
+
+
+/**
+ * File system event for newly created file system objects.
+ */
+class FileSystemCreateEvent extends FileSystemEvent {
+ FileSystemCreateEvent._(path)
+ : super._(FileSystemWatcher.CREATE_EVENT, 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._(FileSystemWatcher.MODIFY_EVENT, path);
+
+ String toString() =>
+ "FileSystemModifyEvent('$path', contentChanged=$contentChanged)";
+}
+
+
+/**
+ * File system event for deletion of file system objects.
+ */
+class FileSystemDeleteEvent extends FileSystemEvent {
+ FileSystemDeleteEvent._(path)
+ : super._(FileSystemWatcher.DELETE_EVENT, 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._(FileSystemWatcher.MOVE_EVENT, path);
+
+ String toString() {
+ var buffer = new StringBuffer();
+ buffer.write("FileSystemMoveEvent('$path'");
+ if (destination != null) buffer.write(", '$destination'");
+ buffer.write(')');
+ return buffer.toString();
+ }
+}
+
+
+/**
Søren Gjesse 2013/08/23 07:47:45 I am not sure about this new public class. Shouldn
+ * Event-based watcher for the file system. The [FileSystemWatcher] uses
+ * platform-specific APIs for receiving events, 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, so when disabled, some events are ignore.
Søren Gjesse 2013/08/23 07:47:45 some events are ignore -> the events are filtered
+ *
+ * On some platforms, it can be benaficial to use the same FileSystemWatcher for
Søren Gjesse 2013/08/23 07:47:45 I don't like this comment. We should handle figure
+ * multiple paths.
+ */
+class FileSystemWatcher extends Stream<FileSystemEvent> {
+ static const int CREATE_EVENT = 1 << 0;
+ static const int MODIFY_EVENT = 1 << 1;
+ static const int DELETE_EVENT = 1 << 2;
+ static const int MOVE_EVENT = 1 << 3;
+ static const int ALL_EVENTS =
+ CREATE_EVENT | MODIFY_EVENT | DELETE_EVENT | MOVE_EVENT;
+
+ static const int _MODIFY_ATTRIBUTES_EVENT = 1 << 4;
+
+ int _id;
+ StreamController _controller;
+ Map _paths = {};
+ Map _sockets = {};
+
+ FileSystemWatcher() {
+ _id = _initWatcher();
+ _controller = new StreamController(onCancel: close);
+
+ _listenOn(-1);
+ }
+
+ StreamSubscription<FileSystemEvent> listen(void onData(FileSystemEvent event),
+ {void onError(error),
+ void onDone(),
+ bool cancelOnError}) {
+ return _controller.stream.listen(onData,
+ onError: onError,
+ onDone: onDone,
+ cancelOnError: cancelOnError);
+ }
+
+ /**
+ * Start watching [path] for file system changes events. See
+ * [FileSystemWatcher] for behaviour depending on the platform.
+ *
+ * A single path can only be added once.
+ */
+ void watchPath(String path,
+ {int events: ALL_EVENTS,
+ bool recursive: false}) {
+ recursive = identical(recursive, true);
+ if (Platform.isLinux && recursive) {
+ throw new ArgumentError("'recursive' watching is not available on Linux");
+ }
+ if (_controller.isClosed) {
+ throw new StateError("FileSystemWatcher is already closed");
+ }
+ for (var value in _paths.values) {
+ if (FileSystemEntity.identicalSync(value, path)) {
+ throw new ArgumentError("'$path' is already being watched");
+ }
+ }
+ int id = _addPath(_id, path, events, recursive);
+ _paths[id] = path;
+
+ _listenOn(id, events);
+ }
+
+ /**
+ * Unwatch the path [path]. It's an error if the [path] is not being watched.
+ */
+ void unwatchPath(String path) {
+ for (var key in _paths.keys) {
+ var value = _paths[key];
+ if (FileSystemEntity.identicalSync(value, path)) {
+ _removePath(_id, key);
+ _paths.remove(key);
+ if (_sockets.containsKey(key)) {
+ _sockets[key].close();
+ _sockets.remove(key);
+ }
+ return;
+ }
+ }
+ throw new ArgumentError("'$path' is not being watched");
+ }
+
+ /**
+ * Close the [FileSystemWatcher]. Once closed the [FileSystemWatcher] is
+ * rendered invalid and can no longer be used.
+ */
+ void close() {
+ if (_controller.isClosed) return;
+ for (var id in _paths.keys) {
+ _removePath(_id, id);
+ }
+ _paths.clear();
+ for (var socket in _sockets.values) {
+ socket.close();
+ }
+ _sockets.clear();
+ _controller.close();
+
+ _stopWatcher(_id);
+ }
+
+ /**
+ * Get the list of paths currently being watched.
+ */
+ List<String> get paths => _paths.values;
+
+ void _listenOn(int pathId, [int events = ALL_EVENTS]) {
Søren Gjesse 2013/08/23 07:47:45 No need for events to be optional.
+ int socketId = _getSocketId(_id, pathId);
+ if (socketId == -1) return;
+ var native = new _NativeSocket.normal();
Søren Gjesse 2013/08/23 07:47:45 Instead of adding setSocketId could we add a new c
+ native.isClosedWrite = true;
+ native.setSocketId(socketId);
+ var socket = new _RawSocket(native);
+ _sockets[pathId] = socket;
+ socket.expand((event) {
Søren Gjesse 2013/08/23 07:47:45 How about moving the closure in expand into a loca
+ var events = [];
+ var pair = {};
+ if (event == RawSocketEvent.READ) {
+ String getPath(event) {
+ var path = _paths[event[1]];
+ if (event[2] != null) {
+ path += Platform.pathSeparator;
+ path += event[2];
+ }
+ return path;
+ }
+ while (socket.available() > 0) {
+ for (var event in _readNextEvent(_id, pathId)) {
+ var path = getPath(event);
+ if ((event[0] & CREATE_EVENT) != 0) {
+ events.add(new FileSystemCreateEvent._(path));
+ }
+ if ((event[0] & MODIFY_EVENT) != 0) {
+ events.add(new FileSystemModifyEvent._(path, true));
+ }
+ if ((event[0] & _MODIFY_ATTRIBUTES_EVENT) != 0) {
+ events.add(new FileSystemModifyEvent._(path, false));
+ }
+ if ((event[0] & MOVE_EVENT) != 0) {
+ int link = event[3];
+ 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] & DELETE_EVENT) != 0) {
+ events.add(new FileSystemDeleteEvent._(path));
+ }
+ }
+ }
+ for (var event in pair.values) {
+ events.add(new FileSystemMoveEvent._(getPath(event), null));
+ }
+ } else if (event == RawSocketEvent.CLOSED) {
+ if (_paths.containsKey(pathId)) {
+ _removePath(_id, pathId);
+ _paths.remove(pathId);
+ _sockets.remove(pathId);
+ }
+ } else if (event == RawSocketEvent.READ_CLOSED) {
+ } else {
+ assert(false);
+ }
+ return events;
+ }).where((event) => (event.type & events) != 0).listen(_controller.add);
+ }
+
+ external int _initWatcher();
+ external void _stopWatcher(int id);
+ external int _getSocketId(int id, int pathId);
+ external int _addPath(int id, String path, int events, bool recursive);
+ external void _removePath(int id, int pathId);
+ external List _readNextEvent(int id, int pathId);
+}

Powered by Google App Engine
This is Rietveld 408576698