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

Side by Side Diff: pkg/watcher/lib/src/directory_watcher.dart

Issue 18612013: File watching package. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 5 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 library watcher.directory_watcher;
6
7 import 'dart:async';
8 import 'dart:io';
9
10 import 'package:crypto/crypto.dart';
11
12 import 'change_type.dart';
13 import 'stat.dart';
14 import 'watch_event.dart';
15
16 /// Watches the contents of a directory and emits [WatchEvents] when something
nweiz 2013/07/11 00:25:01 "[WatchEvent]s"
Bob Nystrom 2013/07/11 18:33:20 Done.
17 /// in the directory has changed.
18 class DirectoryWatcher {
19 /// The directory whose contents are being monitored.
20 final String directory;
21
22 /// The [Stream] of file modification events that have occurred to files in
nweiz 2013/07/11 00:25:01 "file modification events" -> "events" Also, ment
Bob Nystrom 2013/07/11 18:33:20 Done.
23 /// [directory].
24 ///
25 /// Changes will only be monitored while this stream has subscribers. Any
26 /// file changes that occur during periods when there are no subscribers
27 /// will not be reported the next time a subscriber is added.
28 Stream<WatchEvent> get events => _events.stream;
29
nweiz 2013/07/11 00:25:01 Style nit: I'd get rid of this newline to indicate
Bob Nystrom 2013/07/11 18:33:20 Done.
30 StreamController<WatchEvent> _events;
Siggi Cherem (dart-lang) 2013/07/10 22:53:53 change to final?
Bob Nystrom 2013/07/10 23:04:39 Wish I could. :( The only way to set onListen and
nweiz 2013/07/11 00:25:01 I'd call this _eventsController.
Bob Nystrom 2013/07/11 18:33:20 Since the stream itself isn't stored, I think that
31
32 _WatchState _state = _WatchState.notWatching;
33
34 /// The previous status of the files in the directory. Used to tell which
35 /// files have been modified.
nweiz 2013/07/11 00:25:01 Style nit: first sentence should be its own paragr
Bob Nystrom 2013/07/11 18:33:20 Done.
36 final _statuses = new Map<String, _FileStatus>();
37
38 /// Creates a new [DirectoryWatcher] monitoring [directory].
39 DirectoryWatcher(this.directory) {
40 _events = new StreamController<WatchEvent>.broadcast(onListen: () {
41 _state = _state.listen(this);
42 }, onCancel: () {
43 _state = _state.cancel(this);
44 });
45 }
46
47 /// Starts the asynchronous polling process.
48 ///
49 /// Scans the contents of the directory and compares the results to the
50 /// previous scan. Loops to continue monitoring as long as there are
51 /// subscribers to the [events] stream.
52 Future _watch() {
53 var files = new Set<String>();
54
55 var stream = new Directory(directory).list(
56 recursive: true, followLinks: true);
nweiz 2013/07/11 00:25:01 I'd much rather we use pub's listDirectory here, e
Bob Nystrom 2013/07/11 18:33:20 I think it's important for watcher to use a stream
nweiz 2013/07/11 22:29:00 They've marked an important bug as wontfix: https:
Bob Nystrom 2013/07/12 00:31:09 I think you marked that bug WontFix. I changed thi
nweiz 2013/07/12 01:04:28 Soren incorrectly marked it as fixed. I changed th
Bob Nystrom 2013/07/12 17:46:16 Yes, for now I'm OK with not supported symlinked d
57
58 var futures = [];
59 return stream.forEach((entity) {
60 if (entity is File) {
61 files.add(entity.path);
62 futures.add(_refreshFile(entity.path));
63 }
64 }).then((_) {
65 // Once the listing is done, make sure to wait until each file is also
66 // done.
67 return Future.wait(futures);
68 }).then((_) {
nweiz 2013/07/11 00:25:01 I think the following would be a little cleaner he
Bob Nystrom 2013/07/11 18:33:20 Had to stick a toList() in there, but this is a bi
69 var removedFiles = _statuses.keys.toSet().difference(files);
70 for (var removed in removedFiles) {
71 if (_state.shouldNotify) {
nweiz 2013/07/11 00:25:01 It's a little weird that this is a flag, but you j
Bob Nystrom 2013/07/11 18:33:20 Changed to check shouldNotify below.
72 _events.add(new WatchEvent(ChangeType.REMOVE, removed));
73 }
74 _statuses.remove(removed);
75 }
76
77 _state = _state.finish(this);
78
79 // If the new state isn't watching, just stop.
80 if (!_state.shouldWatch) return;
81
82 // If we're in the "watching" state, add a bit of delay before restarting
83 // just so that we don't whale on the file system.
84 // TODO(rnystrom): Tune this and/or make it tunable?
85 if (_state == _WatchState.watching) {
86 return new Future.delayed(new Duration(seconds: 1));
87 }
88
89 // Otherwise, loop.
90 return _watch();
nweiz 2013/07/11 00:25:01 Shouldn't this just be "return;"? Otherwise, would
Bob Nystrom 2013/07/11 18:33:20 Done.
91 }).then((_) {
92 // Make sure we haven't transitioned to a non-watching state during the
93 // delay.
94 if (_state.shouldWatch) _watch();
95 });
96 }
97
98 /// Compares the current state of the file at [path] to the state it was in
99 /// the last time it was scanned.
100 Future _refreshFile(String path) {
101 return getModificationTime(path).then((modified) {
102 var lastStatus = _statuses[path];
103
104 // If it's modification time hasn't changed, assume the file is unchanged.
105 if (lastStatus != null && lastStatus.modified == modified) {
106 return false;
nweiz 2013/07/11 00:25:01 Is this return value used anywhere?
Bob Nystrom 2013/07/11 18:33:20 Oops. Not anymore. Used to have the later future s
107 }
108
109 return _hashFile(path).then((hash) {
110 var status = new _FileStatus(modified, hash);
111 _statuses[path] = status;
112
113 if (!_state.shouldNotify) return;
nweiz 2013/07/11 00:25:01 Fold this into the following "if", or vice versa.
Bob Nystrom 2013/07/11 18:33:20 Done.
114
115 // Only notify if the file contents are changed.
116 if (lastStatus == null || !_sameHash(lastStatus.hash, hash)) {
Siggi Cherem (dart-lang) 2013/07/10 22:53:53 consider making this a configuration option - some
Bob Nystrom 2013/07/10 23:04:39 I expect this to get more configurable over time,
117 var change = lastStatus == null ? ChangeType.ADD : ChangeType.MODIFY;
118 _events.add(new WatchEvent(change, path));
119 }
120 });
121 });
122 }
123
124 /// Calculates the SHA-1 hash of the file at [path].
125 Future<List<int>> _hashFile(String path) {
126 return new File(path).readAsBytes().then((bytes) {
127 var sha1 = new SHA1();
128 sha1.add(bytes);
129 return sha1.close();
130 });
131 }
132
133 /// Returns `true` if [a] and [b] are the same hash value, i.e. the same
134 /// series of byte values.
135 bool _sameHash(List<int> a, List<int> b) {
Siggi Cherem (dart-lang) 2013/07/10 22:53:53 alternatively call CrytoUtils.bytesToHex and compa
Bob Nystrom 2013/07/10 23:04:39 Hmm, is that faster? Seems like a roundabout way t
136 // Hashes should always be the same size.
137 assert(a.length == b.length);
138
139 for (var i = 0; i < a.length; i++) {
140 if (a[i] != b[i]) return false;
141 }
142
143 return true;
144 }
145 }
146
147 /// An "event" that is sent to the [_WatchState] FSM to trigger state
148 /// transitions.
149 typedef _WatchState _WatchStateEvent(DirectoryWatcher watcher);
150
151 /// The different states that the watcher can be in and the transitions between
152 /// them.
153 ///
154 /// This class defines a finite state machine for keeping track of what the
155 /// asynchronous file polling is doing. Each instance of this is a state in the
156 /// machine and its [listen], [cancel], and [finish] fields define the state
157 /// transitions when those events occur.
158 class _WatchState {
159 /// The watcher has no subscribers.
160 static final notWatching = new _WatchState(
161 listen: (watcher) {
162 watcher._watch();
nweiz 2013/07/11 00:25:01 If you move this into DirectoryWatcher, then _Watc
Bob Nystrom 2013/07/11 18:33:20 You mean move the state instances?
nweiz 2013/07/11 22:29:00 No, I mean the call to "watcher._watch" in particu
Bob Nystrom 2013/07/12 00:31:09 Agreed on both accounts. In this case, I think hav
nweiz 2013/07/12 01:04:28 Okay.
163 return _WatchState.scanning;
164 });
165
166 /// The watcher has subscribers and is scanning for pre-existing files.
167 static final scanning = new _WatchState(
168 cancel: (_) => _WatchState.cancelling,
169 finish: (_) => _WatchState.watching,
170 shouldWatch: true);
171
172 /// The watcher was unsubscribed while polling and we're waiting for the poll
173 /// to finish.
174 static final cancelling = new _WatchState(
175 listen: (_) => _WatchState.scanning,
176 finish: (_) => _WatchState.notWatching);
177
178 /// The watcher has subscribers, we have scanned for pre-existing files and
179 /// now we're waiting for changes to come in.
nweiz 2013/07/11 00:25:01 "waiting for changes to come in" -> "polling for c
Bob Nystrom 2013/07/11 18:33:20 Done.
180 static final watching = new _WatchState(
181 cancel: (_) => _WatchState.cancelling,
182 finish: (_) => _WatchState.watching,
183 shouldWatch: true, shouldNotify: true);
184
185 /// Asserts that an event is not expected for some state.
186 static _WatchState _badState(DirectoryWatcher watcher) {
187 // Should not receive this event in this state.
188 assert(false);
nweiz 2013/07/11 00:25:01 If it's possible, it would be nice to provide some
Bob Nystrom 2013/07/11 18:33:20 I just went ahead and deleted this. It only exist(
189 }
190
191 /// Called when the first subscriber to the watcher has been added.
192 final _WatchStateEvent listen;
193
194 /// Called when all subscriptions on the watcher have been cancelled.
195 final _WatchStateEvent cancel;
196
197 /// Called when a poll loop has finished.
198 final _WatchStateEvent finish;
199
200 /// If the directory watcher should be watching the file system while in
201 /// this state.
202 final bool shouldWatch;
203
204 /// `true` if a change event should be sent for a file modification while
205 /// in this state.
206 final bool shouldNotify;
207
208 _WatchState({
209 _WatchStateEvent listen,
210 _WatchStateEvent cancel,
211 _WatchStateEvent finish,
212 this.shouldWatch: false,
213 this.shouldNotify: false})
214 : listen = listen != null ? listen : _badState,
215 cancel = cancel != null ? cancel : _badState,
216 finish = finish != null ? finish : _badState;
217 }
218
219 class _FileStatus {
220 /// The last time the file was modified.
221 DateTime modified;
222
223 /// The SHA-1 hash of the contents of the file.
224 List<int> hash;
225
226 _FileStatus(this.modified, this.hash);
227 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698