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

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

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 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
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.polling;
6
7 import 'dart:async';
8 import 'dart:io';
9
10 import '../async_queue.dart';
11 import '../directory_watcher.dart';
12 import '../resubscribable.dart';
13 import '../stat.dart';
14 import '../utils.dart';
15 import '../watch_event.dart';
16
17 /// Periodically polls a directory for changes.
18 class PollingDirectoryWatcher extends ResubscribableWatcher
19 implements DirectoryWatcher {
20 String get directory => path;
21
22 /// Creates a new polling watcher monitoring [directory].
23 ///
24 /// If [_pollingDelay] is passed, it specifies the amount of time the watcher
25 /// will pause between successive polls of the directory contents. Making this
26 /// shorter will give more immediate feedback at the expense of doing more IO
27 /// and higher CPU usage. Defaults to one second.
28 PollingDirectoryWatcher(String directory, {Duration pollingDelay})
29 : super(directory, () {
30 return new _PollingDirectoryWatcher(directory,
31 pollingDelay != null ? pollingDelay : new Duration(seconds: 1));
32 });
33 }
34
35 class _PollingDirectoryWatcher
36 implements DirectoryWatcher, ManuallyClosedWatcher {
37 String get directory => path;
38 final String path;
39
40 Stream<WatchEvent> get events => _events.stream;
41 final _events = new StreamController<WatchEvent>.broadcast();
42
43 bool get isReady => _ready.isCompleted;
44
45 Future get ready => _ready.future;
46 final _ready = new Completer();
47
48 /// The amount of time the watcher pauses between successive polls of the
49 /// directory contents.
50 final Duration _pollingDelay;
51
52 /// The previous modification times of the files in the directory.
53 ///
54 /// Used to tell which files have been modified.
55 final _lastModifieds = new Map<String, DateTime>();
56
57 /// The subscription used while [directory] is being listed.
58 ///
59 /// Will be `null` if a list is not currently happening.
60 StreamSubscription<FileSystemEntity> _listSubscription;
61
62 /// The queue of files waiting to be processed to see if they have been
63 /// modified.
64 ///
65 /// Processing a file is asynchronous, as is listing the directory, so the
66 /// queue exists to let each of those proceed at their own rate. The lister
67 /// will enqueue files as quickly as it can. Meanwhile, files are dequeued
68 /// and processed sequentially.
69 AsyncQueue<String> _filesToProcess;
70
71 /// The set of files that have been seen in the current directory listing.
72 ///
73 /// Used to tell which files have been removed: files that are in [_statuses]
74 /// but not in here when a poll completes have been removed.
75 final _polledFiles = new Set<String>();
76
77 _PollingDirectoryWatcher(this.path, this._pollingDelay) {
78 _filesToProcess = new AsyncQueue<String>(_processFile,
79 onError: (e, stackTrace) {
80 if (!_events.isClosed) _events.addError(e, stackTrace);
81 });
82
83 _poll();
84 }
85
86 void close() {
87 _events.close();
88
89 // If we're in the middle of listing the directory, stop.
90 if (_listSubscription != null) _listSubscription.cancel();
91
92 // Don't process any remaining files.
93 _filesToProcess.clear();
94 _polledFiles.clear();
95 _lastModifieds.clear();
96 }
97
98 /// Scans the contents of the directory once to see which files have been
99 /// added, removed, and modified.
100 void _poll() {
101 _filesToProcess.clear();
102 _polledFiles.clear();
103
104 endListing() {
105 assert(!_events.isClosed);
106 _listSubscription = null;
107
108 // Null tells the queue consumer that we're done listing.
109 _filesToProcess.add(null);
110 }
111
112 var stream = new Directory(path).list(recursive: true);
113 _listSubscription = stream.listen((entity) {
114 assert(!_events.isClosed);
115
116 if (entity is! File) return;
117 _filesToProcess.add(entity.path);
118 }, onError: (error, stackTrace) {
119 if (!isDirectoryNotFoundException(error)) {
120 // It's some unknown error. Pipe it over to the event stream so the
121 // user can see it.
122 _events.addError(error, stackTrace);
123 }
124
125 // When an error occurs, we end the listing normally, which has the
126 // desired effect of marking all files that were in the directory as
127 // being removed.
128 endListing();
129 }, onDone: endListing, cancelOnError: true);
130 }
131
132 /// Processes [file] to determine if it has been modified since the last
133 /// time it was scanned.
134 Future _processFile(String file) {
135 // `null` is the sentinel which means the directory listing is complete.
136 if (file == null) return _completePoll();
137
138 return getModificationTime(file).then((modified) {
139 if (_events.isClosed) return null;
140
141 var lastModified = _lastModifieds[file];
142
143 // If its modification time hasn't changed, assume the file is unchanged.
144 if (lastModified != null && lastModified == modified) {
145 // The file is still here.
146 _polledFiles.add(file);
147 return null;
148 }
149
150 if (_events.isClosed) return null;
151
152 _lastModifieds[file] = modified;
153 _polledFiles.add(file);
154
155 // Only notify if we're ready to emit events.
156 if (!isReady) return null;
157
158 var type = lastModified == null ? ChangeType.ADD : ChangeType.MODIFY;
159 _events.add(new WatchEvent(type, file));
160 });
161 }
162
163 /// After the directory listing is complete, this determines which files were
164 /// removed and then restarts the next poll.
165 Future _completePoll() {
166 // Any files that were not seen in the last poll but that we have a
167 // status for must have been removed.
168 var removedFiles = _lastModifieds.keys.toSet().difference(_polledFiles);
169 for (var removed in removedFiles) {
170 if (isReady) _events.add(new WatchEvent(ChangeType.REMOVE, removed));
171 _lastModifieds.remove(removed);
172 }
173
174 if (!isReady) _ready.complete();
175
176 // Wait and then poll again.
177 return new Future.delayed(_pollingDelay).then((_) {
178 if (_events.isClosed) return;
179 _poll();
180 });
181 }
182 }
OLDNEW
« no previous file with comments | « watcher/lib/src/directory_watcher/mac_os.dart ('k') | watcher/lib/src/directory_watcher/windows.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698