OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, 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.file_watcher.native; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:io'; | |
9 | |
10 import '../file_watcher.dart'; | |
11 import '../resubscribable.dart'; | |
12 import '../utils.dart'; | |
13 import '../watch_event.dart'; | |
14 | |
15 /// Uses the native file system notifications to watch for filesystem events. | |
16 /// | |
17 /// Single-file notifications are much simpler than those for multiple files, so | |
18 /// this doesn't need to be split out into multiple OS-specific classes. | |
19 class NativeFileWatcher extends ResubscribableWatcher implements FileWatcher { | |
20 NativeFileWatcher(String path) | |
21 : super(path, () => new _NativeFileWatcher(path)); | |
22 } | |
23 | |
24 class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { | |
25 final String path; | |
26 | |
27 Stream<WatchEvent> get events => _eventsController.stream; | |
28 final _eventsController = new StreamController<WatchEvent>.broadcast(); | |
29 | |
30 bool get isReady => _readyCompleter.isCompleted; | |
31 | |
32 Future get ready => _readyCompleter.future; | |
33 final _readyCompleter = new Completer(); | |
34 | |
35 StreamSubscription _subscription; | |
36 | |
37 _NativeFileWatcher(this.path) { | |
38 _listen(); | |
39 | |
40 // We don't need to do any initial set-up, so we're ready immediately after | |
41 // being listened to. | |
42 _readyCompleter.complete(); | |
43 } | |
44 | |
45 void _listen() { | |
46 // Batch the events together so that we can dedup them. | |
47 _subscription = new File(path).watch() | |
48 .transform(new BatchedStreamTransformer<FileSystemEvent>()) | |
49 .listen(_onBatch, onError: _eventsController.addError, onDone: _onDone); | |
50 } | |
51 | |
52 void _onBatch(List<FileSystemEvent> batch) { | |
53 if (batch.any((event) => event.type == FileSystemEvent.DELETE)) { | |
54 // If the file is deleted, the underlying stream will close. We handle | |
55 // emitting our own REMOVE event in [_onDone]. | |
56 return; | |
57 } | |
58 | |
59 _eventsController.add(new WatchEvent(ChangeType.MODIFY, path)); | |
60 } | |
61 | |
62 _onDone() async { | |
63 var fileExists = await new File(path).exists(); | |
64 | |
65 // Check for this after checking whether the file exists because it's | |
66 // possible that [close] was called between [File.exists] being called and | |
67 // it completing. | |
68 if (_eventsController.isClosed) return; | |
69 | |
70 if (fileExists) { | |
71 // If the file exists now, it was probably removed and quickly replaced; | |
72 // this can happen for example when another file is moved on top of it. | |
73 // Re-subscribe and report a modify event. | |
74 _eventsController.add(new WatchEvent(ChangeType.MODIFY, path)); | |
75 _listen(); | |
76 } else { | |
77 _eventsController.add(new WatchEvent(ChangeType.REMOVE, path)); | |
78 close(); | |
79 } | |
80 } | |
81 | |
82 void close() { | |
83 if (_subscription != null) _subscription.cancel(); | |
84 _subscription = null; | |
85 _eventsController.close(); | |
86 } | |
87 } | |
OLD | NEW |