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

Side by Side 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, 3 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698