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

Unified Diff: mojo/public/dart/third_party/watcher/lib/src/directory_watcher/polling.dart

Issue 1346773002: Stop running pub get at gclient sync time and fix build bugs (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 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 side-by-side diff with in-line comments
Download patch
Index: mojo/public/dart/third_party/watcher/lib/src/directory_watcher/polling.dart
diff --git a/mojo/public/dart/third_party/watcher/lib/src/directory_watcher/polling.dart b/mojo/public/dart/third_party/watcher/lib/src/directory_watcher/polling.dart
new file mode 100644
index 0000000000000000000000000000000000000000..7f417d630702bed0bec7a57d904f0b535de4be57
--- /dev/null
+++ b/mojo/public/dart/third_party/watcher/lib/src/directory_watcher/polling.dart
@@ -0,0 +1,182 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library watcher.directory_watcher.polling;
+
+import 'dart:async';
+import 'dart:io';
+
+import '../async_queue.dart';
+import '../directory_watcher.dart';
+import '../resubscribable.dart';
+import '../stat.dart';
+import '../utils.dart';
+import '../watch_event.dart';
+
+/// Periodically polls a directory for changes.
+class PollingDirectoryWatcher extends ResubscribableWatcher
+ implements DirectoryWatcher {
+ String get directory => path;
+
+ /// Creates a new polling watcher monitoring [directory].
+ ///
+ /// If [_pollingDelay] is passed, it specifies the amount of time the watcher
+ /// will pause between successive polls of the directory contents. Making this
+ /// shorter will give more immediate feedback at the expense of doing more IO
+ /// and higher CPU usage. Defaults to one second.
+ PollingDirectoryWatcher(String directory, {Duration pollingDelay})
+ : super(directory, () {
+ return new _PollingDirectoryWatcher(directory,
+ pollingDelay != null ? pollingDelay : new Duration(seconds: 1));
+ });
+}
+
+class _PollingDirectoryWatcher
+ implements DirectoryWatcher, ManuallyClosedWatcher {
+ String get directory => path;
+ final String path;
+
+ Stream<WatchEvent> get events => _events.stream;
+ final _events = new StreamController<WatchEvent>.broadcast();
+
+ bool get isReady => _ready.isCompleted;
+
+ Future get ready => _ready.future;
+ final _ready = new Completer();
+
+ /// The amount of time the watcher pauses between successive polls of the
+ /// directory contents.
+ final Duration _pollingDelay;
+
+ /// The previous modification times of the files in the directory.
+ ///
+ /// Used to tell which files have been modified.
+ final _lastModifieds = new Map<String, DateTime>();
+
+ /// The subscription used while [directory] is being listed.
+ ///
+ /// Will be `null` if a list is not currently happening.
+ StreamSubscription<FileSystemEntity> _listSubscription;
+
+ /// The queue of files waiting to be processed to see if they have been
+ /// modified.
+ ///
+ /// Processing a file is asynchronous, as is listing the directory, so the
+ /// queue exists to let each of those proceed at their own rate. The lister
+ /// will enqueue files as quickly as it can. Meanwhile, files are dequeued
+ /// and processed sequentially.
+ AsyncQueue<String> _filesToProcess;
+
+ /// The set of files that have been seen in the current directory listing.
+ ///
+ /// Used to tell which files have been removed: files that are in [_statuses]
+ /// but not in here when a poll completes have been removed.
+ final _polledFiles = new Set<String>();
+
+ _PollingDirectoryWatcher(this.path, this._pollingDelay) {
+ _filesToProcess = new AsyncQueue<String>(_processFile,
+ onError: (e, stackTrace) {
+ if (!_events.isClosed) _events.addError(e, stackTrace);
+ });
+
+ _poll();
+ }
+
+ void close() {
+ _events.close();
+
+ // If we're in the middle of listing the directory, stop.
+ if (_listSubscription != null) _listSubscription.cancel();
+
+ // Don't process any remaining files.
+ _filesToProcess.clear();
+ _polledFiles.clear();
+ _lastModifieds.clear();
+ }
+
+ /// Scans the contents of the directory once to see which files have been
+ /// added, removed, and modified.
+ void _poll() {
+ _filesToProcess.clear();
+ _polledFiles.clear();
+
+ endListing() {
+ assert(!_events.isClosed);
+ _listSubscription = null;
+
+ // Null tells the queue consumer that we're done listing.
+ _filesToProcess.add(null);
+ }
+
+ var stream = new Directory(path).list(recursive: true);
+ _listSubscription = stream.listen((entity) {
+ assert(!_events.isClosed);
+
+ if (entity is! File) return;
+ _filesToProcess.add(entity.path);
+ }, onError: (error, stackTrace) {
+ if (!isDirectoryNotFoundException(error)) {
+ // It's some unknown error. Pipe it over to the event stream so the
+ // user can see it.
+ _events.addError(error, stackTrace);
+ }
+
+ // When an error occurs, we end the listing normally, which has the
+ // desired effect of marking all files that were in the directory as
+ // being removed.
+ endListing();
+ }, onDone: endListing, cancelOnError: true);
+ }
+
+ /// Processes [file] to determine if it has been modified since the last
+ /// time it was scanned.
+ Future _processFile(String file) {
+ // `null` is the sentinel which means the directory listing is complete.
+ if (file == null) return _completePoll();
+
+ return getModificationTime(file).then((modified) {
+ if (_events.isClosed) return null;
+
+ var lastModified = _lastModifieds[file];
+
+ // If its modification time hasn't changed, assume the file is unchanged.
+ if (lastModified != null && lastModified == modified) {
+ // The file is still here.
+ _polledFiles.add(file);
+ return null;
+ }
+
+ if (_events.isClosed) return null;
+
+ _lastModifieds[file] = modified;
+ _polledFiles.add(file);
+
+ // Only notify if we're ready to emit events.
+ if (!isReady) return null;
+
+ var type = lastModified == null ? ChangeType.ADD : ChangeType.MODIFY;
+ _events.add(new WatchEvent(type, file));
+ });
+ }
+
+ /// After the directory listing is complete, this determines which files were
+ /// removed and then restarts the next poll.
+ Future _completePoll() {
+ // Any files that were not seen in the last poll but that we have a
+ // status for must have been removed.
+ var removedFiles = _lastModifieds.keys.toSet().difference(_polledFiles);
+ for (var removed in removedFiles) {
+ if (isReady) _events.add(new WatchEvent(ChangeType.REMOVE, removed));
+ _lastModifieds.remove(removed);
+ }
+
+ if (!isReady) _ready.complete();
+
+ // Wait and then poll again.
+ return new Future.delayed(_pollingDelay).then((_) {
+ if (_events.isClosed) return;
+ _poll();
+ });
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698