| 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 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 } |  | 
| OLD | NEW | 
|---|