Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of dart.io; | |
| 6 | |
| 7 | |
| 8 /** | |
| 9 * Base event class emitted by FileSystemWatcher. | |
| 10 */ | |
| 11 class FileSystemEvent { | |
| 12 /** | |
| 13 * The type of event. See [FileSystemEvent] for a list of events. | |
| 14 */ | |
| 15 final int type; | |
| 16 | |
| 17 /** | |
| 18 * The path that triggered the event. | |
| 19 */ | |
| 20 final String path; | |
| 21 | |
| 22 FileSystemEvent._(this.type, this.path); | |
| 23 } | |
| 24 | |
| 25 | |
| 26 /** | |
| 27 * File system event for newly created file system objects. | |
| 28 */ | |
| 29 class FileSystemCreateEvent extends FileSystemEvent { | |
| 30 FileSystemCreateEvent._(path) | |
| 31 : super._(FileSystemWatcher.CREATE_EVENT, path); | |
| 32 | |
| 33 String toString() => "FileSystemCreateEvent('$path')"; | |
| 34 } | |
| 35 | |
| 36 | |
| 37 /** | |
| 38 * File system event for modifications of file system objects. | |
| 39 */ | |
| 40 class FileSystemModifyEvent extends FileSystemEvent { | |
| 41 /** | |
| 42 * If the content was changed and not only the attributes, [contentChanged] | |
| 43 * is `true`. | |
| 44 */ | |
| 45 final bool contentChanged; | |
| 46 | |
| 47 FileSystemModifyEvent._(path, this.contentChanged) | |
| 48 : super._(FileSystemWatcher.MODIFY_EVENT, path); | |
| 49 | |
| 50 String toString() => | |
| 51 "FileSystemModifyEvent('$path', contentChanged=$contentChanged)"; | |
| 52 } | |
| 53 | |
| 54 | |
| 55 /** | |
| 56 * File system event for deletion of file system objects. | |
| 57 */ | |
| 58 class FileSystemDeleteEvent extends FileSystemEvent { | |
| 59 FileSystemDeleteEvent._(path) | |
| 60 : super._(FileSystemWatcher.DELETE_EVENT, path); | |
| 61 | |
| 62 String toString() => "FileSystemDeleteEvent('$path')"; | |
| 63 } | |
| 64 | |
| 65 | |
| 66 /** | |
| 67 * File system event for moving of file system objects. | |
| 68 */ | |
| 69 class FileSystemMoveEvent extends FileSystemEvent { | |
| 70 /** | |
| 71 * If the underlaying implementation is able to identify the destination of | |
| 72 * the moved file, [destination] will be set. Otherwise, it will be `null`. | |
| 73 */ | |
| 74 final String destination; | |
| 75 | |
| 76 FileSystemMoveEvent._(path, this.destination) | |
| 77 : super._(FileSystemWatcher.MOVE_EVENT, path); | |
| 78 | |
| 79 String toString() { | |
| 80 var buffer = new StringBuffer(); | |
| 81 buffer.write("FileSystemMoveEvent('$path'"); | |
| 82 if (destination != null) buffer.write(", '$destination'"); | |
| 83 buffer.write(')'); | |
| 84 return buffer.toString(); | |
| 85 } | |
| 86 } | |
| 87 | |
| 88 | |
| 89 /** | |
|
Søren Gjesse
2013/08/23 07:47:45
I am not sure about this new public class. Shouldn
| |
| 90 * Event-based watcher for the file system. The [FileSystemWatcher] uses | |
| 91 * platform-specific APIs for receiving events, thus behvaiour depends on the | |
| 92 * platform. | |
| 93 * | |
| 94 * * `Windows`: Uses `ReadDirectoryChangesW`. The implementation supports only | |
| 95 * watching dirctories but supports recursive watching. | |
| 96 * * `Linux`: Uses `inotify`. The implementation supports watching both files | |
| 97 * and dirctories, but doesn't support recursive watching. | |
| 98 * * `Mac OS`: Uses `FSEvents`. The implementation supports watching both | |
| 99 * files and dirctories, and also recursive watching. Note that FSEvents | |
| 100 * 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
| |
| 101 * | |
| 102 * 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
| |
| 103 * multiple paths. | |
| 104 */ | |
| 105 class FileSystemWatcher extends Stream<FileSystemEvent> { | |
| 106 static const int CREATE_EVENT = 1 << 0; | |
| 107 static const int MODIFY_EVENT = 1 << 1; | |
| 108 static const int DELETE_EVENT = 1 << 2; | |
| 109 static const int MOVE_EVENT = 1 << 3; | |
| 110 static const int ALL_EVENTS = | |
| 111 CREATE_EVENT | MODIFY_EVENT | DELETE_EVENT | MOVE_EVENT; | |
| 112 | |
| 113 static const int _MODIFY_ATTRIBUTES_EVENT = 1 << 4; | |
| 114 | |
| 115 int _id; | |
| 116 StreamController _controller; | |
| 117 Map _paths = {}; | |
| 118 Map _sockets = {}; | |
| 119 | |
| 120 FileSystemWatcher() { | |
| 121 _id = _initWatcher(); | |
| 122 _controller = new StreamController(onCancel: close); | |
| 123 | |
| 124 _listenOn(-1); | |
| 125 } | |
| 126 | |
| 127 StreamSubscription<FileSystemEvent> listen(void onData(FileSystemEvent event), | |
| 128 {void onError(error), | |
| 129 void onDone(), | |
| 130 bool cancelOnError}) { | |
| 131 return _controller.stream.listen(onData, | |
| 132 onError: onError, | |
| 133 onDone: onDone, | |
| 134 cancelOnError: cancelOnError); | |
| 135 } | |
| 136 | |
| 137 /** | |
| 138 * Start watching [path] for file system changes events. See | |
| 139 * [FileSystemWatcher] for behaviour depending on the platform. | |
| 140 * | |
| 141 * A single path can only be added once. | |
| 142 */ | |
| 143 void watchPath(String path, | |
| 144 {int events: ALL_EVENTS, | |
| 145 bool recursive: false}) { | |
| 146 recursive = identical(recursive, true); | |
| 147 if (Platform.isLinux && recursive) { | |
| 148 throw new ArgumentError("'recursive' watching is not available on Linux"); | |
| 149 } | |
| 150 if (_controller.isClosed) { | |
| 151 throw new StateError("FileSystemWatcher is already closed"); | |
| 152 } | |
| 153 for (var value in _paths.values) { | |
| 154 if (FileSystemEntity.identicalSync(value, path)) { | |
| 155 throw new ArgumentError("'$path' is already being watched"); | |
| 156 } | |
| 157 } | |
| 158 int id = _addPath(_id, path, events, recursive); | |
| 159 _paths[id] = path; | |
| 160 | |
| 161 _listenOn(id, events); | |
| 162 } | |
| 163 | |
| 164 /** | |
| 165 * Unwatch the path [path]. It's an error if the [path] is not being watched. | |
| 166 */ | |
| 167 void unwatchPath(String path) { | |
| 168 for (var key in _paths.keys) { | |
| 169 var value = _paths[key]; | |
| 170 if (FileSystemEntity.identicalSync(value, path)) { | |
| 171 _removePath(_id, key); | |
| 172 _paths.remove(key); | |
| 173 if (_sockets.containsKey(key)) { | |
| 174 _sockets[key].close(); | |
| 175 _sockets.remove(key); | |
| 176 } | |
| 177 return; | |
| 178 } | |
| 179 } | |
| 180 throw new ArgumentError("'$path' is not being watched"); | |
| 181 } | |
| 182 | |
| 183 /** | |
| 184 * Close the [FileSystemWatcher]. Once closed the [FileSystemWatcher] is | |
| 185 * rendered invalid and can no longer be used. | |
| 186 */ | |
| 187 void close() { | |
| 188 if (_controller.isClosed) return; | |
| 189 for (var id in _paths.keys) { | |
| 190 _removePath(_id, id); | |
| 191 } | |
| 192 _paths.clear(); | |
| 193 for (var socket in _sockets.values) { | |
| 194 socket.close(); | |
| 195 } | |
| 196 _sockets.clear(); | |
| 197 _controller.close(); | |
| 198 | |
| 199 _stopWatcher(_id); | |
| 200 } | |
| 201 | |
| 202 /** | |
| 203 * Get the list of paths currently being watched. | |
| 204 */ | |
| 205 List<String> get paths => _paths.values; | |
| 206 | |
| 207 void _listenOn(int pathId, [int events = ALL_EVENTS]) { | |
|
Søren Gjesse
2013/08/23 07:47:45
No need for events to be optional.
| |
| 208 int socketId = _getSocketId(_id, pathId); | |
| 209 if (socketId == -1) return; | |
| 210 var native = new _NativeSocket.normal(); | |
|
Søren Gjesse
2013/08/23 07:47:45
Instead of adding setSocketId could we add a new c
| |
| 211 native.isClosedWrite = true; | |
| 212 native.setSocketId(socketId); | |
| 213 var socket = new _RawSocket(native); | |
| 214 _sockets[pathId] = socket; | |
| 215 socket.expand((event) { | |
|
Søren Gjesse
2013/08/23 07:47:45
How about moving the closure in expand into a loca
| |
| 216 var events = []; | |
| 217 var pair = {}; | |
| 218 if (event == RawSocketEvent.READ) { | |
| 219 String getPath(event) { | |
| 220 var path = _paths[event[1]]; | |
| 221 if (event[2] != null) { | |
| 222 path += Platform.pathSeparator; | |
| 223 path += event[2]; | |
| 224 } | |
| 225 return path; | |
| 226 } | |
| 227 while (socket.available() > 0) { | |
| 228 for (var event in _readNextEvent(_id, pathId)) { | |
| 229 var path = getPath(event); | |
| 230 if ((event[0] & CREATE_EVENT) != 0) { | |
| 231 events.add(new FileSystemCreateEvent._(path)); | |
| 232 } | |
| 233 if ((event[0] & MODIFY_EVENT) != 0) { | |
| 234 events.add(new FileSystemModifyEvent._(path, true)); | |
| 235 } | |
| 236 if ((event[0] & _MODIFY_ATTRIBUTES_EVENT) != 0) { | |
| 237 events.add(new FileSystemModifyEvent._(path, false)); | |
| 238 } | |
| 239 if ((event[0] & MOVE_EVENT) != 0) { | |
| 240 int link = event[3]; | |
| 241 if (link > 0) { | |
| 242 if (pair.containsKey(link)) { | |
| 243 events.add( | |
| 244 new FileSystemMoveEvent._(getPath(pair[link]), path)); | |
| 245 pair.remove(link); | |
| 246 } else { | |
| 247 pair[link] = event; | |
| 248 } | |
| 249 } else { | |
| 250 events.add(new FileSystemMoveEvent._(path, null)); | |
| 251 } | |
| 252 } | |
| 253 if ((event[0] & DELETE_EVENT) != 0) { | |
| 254 events.add(new FileSystemDeleteEvent._(path)); | |
| 255 } | |
| 256 } | |
| 257 } | |
| 258 for (var event in pair.values) { | |
| 259 events.add(new FileSystemMoveEvent._(getPath(event), null)); | |
| 260 } | |
| 261 } else if (event == RawSocketEvent.CLOSED) { | |
| 262 if (_paths.containsKey(pathId)) { | |
| 263 _removePath(_id, pathId); | |
| 264 _paths.remove(pathId); | |
| 265 _sockets.remove(pathId); | |
| 266 } | |
| 267 } else if (event == RawSocketEvent.READ_CLOSED) { | |
| 268 } else { | |
| 269 assert(false); | |
| 270 } | |
| 271 return events; | |
| 272 }).where((event) => (event.type & events) != 0).listen(_controller.add); | |
| 273 } | |
| 274 | |
| 275 external int _initWatcher(); | |
| 276 external void _stopWatcher(int id); | |
| 277 external int _getSocketId(int id, int pathId); | |
| 278 external int _addPath(int id, String path, int events, bool recursive); | |
| 279 external void _removePath(int id, int pathId); | |
| 280 external List _readNextEvent(int id, int pathId); | |
| 281 } | |
| OLD | NEW |