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 |