Convert all files to Unix newlines.
diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 67a2741..ec119f7 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart
@@ -1,408 +1,408 @@ -// Copyright (c) 2014, 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. -// TODO(rnystrom): Merge with mac_os version. - -import 'dart:async'; -import 'dart:collection'; -import 'dart:io'; - -import 'package:path/path.dart' as p; - -import '../constructable_file_system_event.dart'; -import '../directory_watcher.dart'; -import '../path_set.dart'; -import '../resubscribable.dart'; -import '../utils.dart'; -import '../watch_event.dart'; - -class WindowsDirectoryWatcher extends ResubscribableWatcher - implements DirectoryWatcher { - String get directory => path; - - WindowsDirectoryWatcher(String directory) - : super(directory, () => new _WindowsDirectoryWatcher(directory)); -} - -class _EventBatcher { - static const Duration _BATCH_DELAY = const Duration(milliseconds: 100); - final List<FileSystemEvent> events = []; - Timer timer; - - void addEvent(FileSystemEvent event, void callback()) { - events.add(event); - if (timer != null) { - timer.cancel(); - } - timer = new Timer(_BATCH_DELAY, callback); - } - - void cancelTimer() { - timer.cancel(); - } -} - -class _WindowsDirectoryWatcher - implements DirectoryWatcher, ManuallyClosedWatcher { - String get directory => path; - final String path; - - Stream<WatchEvent> get events => _eventsController.stream; - final _eventsController = new StreamController<WatchEvent>.broadcast(); - - bool get isReady => _readyCompleter.isCompleted; - - Future get ready => _readyCompleter.future; - final _readyCompleter = new Completer(); - - final Map<String, _EventBatcher> _eventBatchers = - new HashMap<String, _EventBatcher>(); - - /// The set of files that are known to exist recursively within the watched - /// directory. - /// - /// The state of files on the filesystem is compared against this to determine - /// the real change that occurred. This is also used to emit REMOVE events - /// when subdirectories are moved out of the watched directory. - final PathSet _files; - - /// The subscription to the stream returned by [Directory.watch]. - StreamSubscription<FileSystemEvent> _watchSubscription; - - /// The subscription to the stream returned by [Directory.watch] of the - /// parent directory to [directory]. This is needed to detect changes to - /// [directory], as they are not included on Windows. - StreamSubscription<FileSystemEvent> _parentWatchSubscription; - - /// The subscription to the [Directory.list] call for the initial listing of - /// the directory to determine its initial state. - StreamSubscription<FileSystemEntity> _initialListSubscription; - - /// The subscriptions to the [Directory.list] calls for listing the contents - /// of subdirectories that were moved into the watched directory. - final Set<StreamSubscription<FileSystemEntity>> _listSubscriptions - = new HashSet<StreamSubscription<FileSystemEntity>>(); - - _WindowsDirectoryWatcher(String path) - : path = path, - _files = new PathSet(path) { - // Before we're ready to emit events, wait for [_listDir] to complete. - _listDir().then((_) { - _startWatch(); - _startParentWatcher(); - _readyCompleter.complete(); - }); - } - - void close() { - if (_watchSubscription != null) _watchSubscription.cancel(); - if (_parentWatchSubscription != null) _parentWatchSubscription.cancel(); - if (_initialListSubscription != null) _initialListSubscription.cancel(); - for (var sub in _listSubscriptions) { - sub.cancel(); - } - _listSubscriptions.clear(); - for (var batcher in _eventBatchers.values) { - batcher.cancelTimer(); - } - _eventBatchers.clear(); - _watchSubscription = null; - _parentWatchSubscription = null; - _initialListSubscription = null; - _eventsController.close(); - } - - /// On Windows, if [directory] is deleted, we will not receive any event. - /// - /// Instead, we add a watcher on the parent folder (if any), that can notify - /// us about [path]. This also includes events such as moves. - void _startParentWatcher() { - var absoluteDir = p.absolute(path); - var parent = p.dirname(absoluteDir); - // Check if [path] is already the root directory. - if (FileSystemEntity.identicalSync(parent, path)) return; - var parentStream = new Directory(parent).watch(recursive: false); - _parentWatchSubscription = parentStream.listen((event) { - // Only look at events for 'directory'. - if (p.basename(event.path) != p.basename(absoluteDir)) return; - // Test if the directory is removed. FileSystemEntity.typeSync will - // return NOT_FOUND if it's unable to decide upon the type, including - // access denied issues, which may happen when the directory is deleted. - // FileSystemMoveEvent and FileSystemDeleteEvent events will always mean - // the directory is now gone. - if (event is FileSystemMoveEvent || - event is FileSystemDeleteEvent || - (FileSystemEntity.typeSync(path) == - FileSystemEntityType.NOT_FOUND)) { - for (var path in _files.paths) { - _emitEvent(ChangeType.REMOVE, path); - } - _files.clear(); - close(); - } - }, onError: (error) { - // Ignore errors, simply close the stream. The user listens on - // [directory], and while it can fail to listen on the parent, we may - // still be able to listen on the path requested. - _parentWatchSubscription.cancel(); - _parentWatchSubscription = null; - }); - } - - void _onEvent(FileSystemEvent event) { - assert(isReady); - final batcher = _eventBatchers.putIfAbsent( - event.path, () => new _EventBatcher()); - batcher.addEvent(event, () { - _eventBatchers.remove(event.path); - _onBatch(batcher.events); - }); - } - - /// The callback that's run when [Directory.watch] emits a batch of events. - void _onBatch(List<FileSystemEvent> batch) { - _sortEvents(batch).forEach((path, eventSet) { - - var canonicalEvent = _canonicalEvent(eventSet); - var events = canonicalEvent == null ? - _eventsBasedOnFileSystem(path) : [canonicalEvent]; - - for (var event in events) { - if (event is FileSystemCreateEvent) { - if (!event.isDirectory) { - if (_files.contains(path)) continue; - - _emitEvent(ChangeType.ADD, path); - _files.add(path); - continue; - } - - if (_files.containsDir(path)) continue; - - var stream = new Directory(path).list(recursive: true); - StreamSubscription<FileSystemEntity> subscription; - subscription = stream.listen((entity) { - if (entity is Directory) return; - if (_files.contains(path)) return; - - _emitEvent(ChangeType.ADD, entity.path); - _files.add(entity.path); - }, onDone: () { - _listSubscriptions.remove(subscription); - }, onError: (e, stackTrace) { - _listSubscriptions.remove(subscription); - _emitError(e, stackTrace); - }, cancelOnError: true); - _listSubscriptions.add(subscription); - } else if (event is FileSystemModifyEvent) { - if (!event.isDirectory) { - _emitEvent(ChangeType.MODIFY, path); - } - } else { - assert(event is FileSystemDeleteEvent); - for (var removedPath in _files.remove(path)) { - _emitEvent(ChangeType.REMOVE, removedPath); - } - } - } - }); - } - - /// Sort all the events in a batch into sets based on their path. - /// - /// A single input event may result in multiple events in the returned map; - /// for example, a MOVE event becomes a DELETE event for the source and a - /// CREATE event for the destination. - /// - /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it - /// contain any events relating to [path]. - Map<String, Set<FileSystemEvent>> _sortEvents(List<FileSystemEvent> batch) { - var eventsForPaths = <String, Set>{}; - - // Events within directories that already have events are superfluous; the - // directory's full contents will be examined anyway, so we ignore such - // events. Emitting them could cause useless or out-of-order events. - var directories = unionAll(batch.map((event) { - if (!event.isDirectory) return new Set(); - if (event is FileSystemMoveEvent) { - return new Set.from([event.path, event.destination]); - } - return new Set.from([event.path]); - })); - - isInModifiedDirectory(path) => - directories.any((dir) => path != dir && path.startsWith(dir)); - - addEvent(path, event) { - if (isInModifiedDirectory(path)) return; - var set = eventsForPaths.putIfAbsent(path, () => new Set()); - set.add(event); - } - - for (var event in batch) { - if (event is FileSystemMoveEvent) { - addEvent(event.destination, event); - } - addEvent(event.path, event); - } - - return eventsForPaths; - } - - /// Returns the canonical event from a batch of events on the same path, if - /// one exists. - /// - /// If [batch] doesn't contain any contradictory events (e.g. DELETE and - /// CREATE, or events with different values for [isDirectory]), this returns a - /// single event that describes what happened to the path in question. - /// - /// If [batch] does contain contradictory events, this returns `null` to - /// indicate that the state of the path on the filesystem should be checked to - /// determine what occurred. - FileSystemEvent _canonicalEvent(Set<FileSystemEvent> batch) { - // An empty batch indicates that we've learned earlier that the batch is - // contradictory (e.g. because of a move). - if (batch.isEmpty) return null; - - var type = batch.first.type; - var isDir = batch.first.isDirectory; - - for (var event in batch.skip(1)) { - // If one event reports that the file is a directory and another event - // doesn't, that's a contradiction. - if (isDir != event.isDirectory) return null; - - // Modify events don't contradict either CREATE or REMOVE events. We can - // safely assume the file was modified after a CREATE or before the - // REMOVE; otherwise there will also be a REMOVE or CREATE event - // (respectively) that will be contradictory. - if (event is FileSystemModifyEvent) continue; - assert(event is FileSystemCreateEvent || - event is FileSystemDeleteEvent || - event is FileSystemMoveEvent); - - // If we previously thought this was a MODIFY, we now consider it to be a - // CREATE or REMOVE event. This is safe for the same reason as above. - if (type == FileSystemEvent.MODIFY) { - type = event.type; - continue; - } - - // A CREATE event contradicts a REMOVE event and vice versa. - assert(type == FileSystemEvent.CREATE || - type == FileSystemEvent.DELETE || - type == FileSystemEvent.MOVE); - if (type != event.type) return null; - } - - switch (type) { - case FileSystemEvent.CREATE: - return new ConstructableFileSystemCreateEvent(batch.first.path, isDir); - case FileSystemEvent.DELETE: - return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); - case FileSystemEvent.MODIFY: - return new ConstructableFileSystemModifyEvent( - batch.first.path, isDir, false); - case FileSystemEvent.MOVE: - return null; - default: throw 'unreachable'; - } - } - - /// Returns one or more events that describe the change between the last known - /// state of [path] and its current state on the filesystem. - /// - /// This returns a list whose order should be reflected in the events emitted - /// to the user, unlike the batched events from [Directory.watch]. The - /// returned list may be empty, indicating that no changes occurred to [path] - /// (probably indicating that it was created and then immediately deleted). - List<FileSystemEvent> _eventsBasedOnFileSystem(String path) { - var fileExisted = _files.contains(path); - var dirExisted = _files.containsDir(path); - var fileExists = new File(path).existsSync(); - var dirExists = new Directory(path).existsSync(); - - var events = <FileSystemEvent>[]; - if (fileExisted) { - if (fileExists) { - events.add(new ConstructableFileSystemModifyEvent(path, false, false)); - } else { - events.add(new ConstructableFileSystemDeleteEvent(path, false)); - } - } else if (dirExisted) { - if (dirExists) { - // If we got contradictory events for a directory that used to exist and - // still exists, we need to rescan the whole thing in case it was - // replaced with a different directory. - events.add(new ConstructableFileSystemDeleteEvent(path, true)); - events.add(new ConstructableFileSystemCreateEvent(path, true)); - } else { - events.add(new ConstructableFileSystemDeleteEvent(path, true)); - } - } - - if (!fileExisted && fileExists) { - events.add(new ConstructableFileSystemCreateEvent(path, false)); - } else if (!dirExisted && dirExists) { - events.add(new ConstructableFileSystemCreateEvent(path, true)); - } - - return events; - } - - /// The callback that's run when the [Directory.watch] stream is closed. - /// Note that this is unlikely to happen on Windows, unless the system itself - /// closes the handle. - void _onDone() { - _watchSubscription = null; - - // Emit remove events for any remaining files. - for (var file in _files.paths) { - _emitEvent(ChangeType.REMOVE, file); - } - _files.clear(); - close(); - } - - /// Start or restart the underlying [Directory.watch] stream. - void _startWatch() { - // Batch the events together so that we can dedup events. - var innerStream = new Directory(path).watch(recursive: true); - _watchSubscription = innerStream.listen(_onEvent, - onError: _eventsController.addError, - onDone: _onDone); - } - - /// Starts or restarts listing the watched directory to get an initial picture - /// of its state. - Future _listDir() { - assert(!isReady); - if (_initialListSubscription != null) _initialListSubscription.cancel(); - - _files.clear(); - var completer = new Completer(); - var stream = new Directory(path).list(recursive: true); - void handleEntity(entity) { - if (entity is! Directory) _files.add(entity.path); - } - _initialListSubscription = stream.listen( - handleEntity, - onError: _emitError, - onDone: completer.complete, - cancelOnError: true); - return completer.future; - } - - /// Emit an event with the given [type] and [path]. - void _emitEvent(ChangeType type, String path) { - if (!isReady) return; - - _eventsController.add(new WatchEvent(type, path)); - } - - /// Emit an error, then close the watcher. - void _emitError(error, StackTrace stackTrace) { - _eventsController.addError(error, stackTrace); - close(); - } -} +// Copyright (c) 2014, 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. +// TODO(rnystrom): Merge with mac_os version. + +import 'dart:async'; +import 'dart:collection'; +import 'dart:io'; + +import 'package:path/path.dart' as p; + +import '../constructable_file_system_event.dart'; +import '../directory_watcher.dart'; +import '../path_set.dart'; +import '../resubscribable.dart'; +import '../utils.dart'; +import '../watch_event.dart'; + +class WindowsDirectoryWatcher extends ResubscribableWatcher + implements DirectoryWatcher { + String get directory => path; + + WindowsDirectoryWatcher(String directory) + : super(directory, () => new _WindowsDirectoryWatcher(directory)); +} + +class _EventBatcher { + static const Duration _BATCH_DELAY = const Duration(milliseconds: 100); + final List<FileSystemEvent> events = []; + Timer timer; + + void addEvent(FileSystemEvent event, void callback()) { + events.add(event); + if (timer != null) { + timer.cancel(); + } + timer = new Timer(_BATCH_DELAY, callback); + } + + void cancelTimer() { + timer.cancel(); + } +} + +class _WindowsDirectoryWatcher + implements DirectoryWatcher, ManuallyClosedWatcher { + String get directory => path; + final String path; + + Stream<WatchEvent> get events => _eventsController.stream; + final _eventsController = new StreamController<WatchEvent>.broadcast(); + + bool get isReady => _readyCompleter.isCompleted; + + Future get ready => _readyCompleter.future; + final _readyCompleter = new Completer(); + + final Map<String, _EventBatcher> _eventBatchers = + new HashMap<String, _EventBatcher>(); + + /// The set of files that are known to exist recursively within the watched + /// directory. + /// + /// The state of files on the filesystem is compared against this to determine + /// the real change that occurred. This is also used to emit REMOVE events + /// when subdirectories are moved out of the watched directory. + final PathSet _files; + + /// The subscription to the stream returned by [Directory.watch]. + StreamSubscription<FileSystemEvent> _watchSubscription; + + /// The subscription to the stream returned by [Directory.watch] of the + /// parent directory to [directory]. This is needed to detect changes to + /// [directory], as they are not included on Windows. + StreamSubscription<FileSystemEvent> _parentWatchSubscription; + + /// The subscription to the [Directory.list] call for the initial listing of + /// the directory to determine its initial state. + StreamSubscription<FileSystemEntity> _initialListSubscription; + + /// The subscriptions to the [Directory.list] calls for listing the contents + /// of subdirectories that were moved into the watched directory. + final Set<StreamSubscription<FileSystemEntity>> _listSubscriptions + = new HashSet<StreamSubscription<FileSystemEntity>>(); + + _WindowsDirectoryWatcher(String path) + : path = path, + _files = new PathSet(path) { + // Before we're ready to emit events, wait for [_listDir] to complete. + _listDir().then((_) { + _startWatch(); + _startParentWatcher(); + _readyCompleter.complete(); + }); + } + + void close() { + if (_watchSubscription != null) _watchSubscription.cancel(); + if (_parentWatchSubscription != null) _parentWatchSubscription.cancel(); + if (_initialListSubscription != null) _initialListSubscription.cancel(); + for (var sub in _listSubscriptions) { + sub.cancel(); + } + _listSubscriptions.clear(); + for (var batcher in _eventBatchers.values) { + batcher.cancelTimer(); + } + _eventBatchers.clear(); + _watchSubscription = null; + _parentWatchSubscription = null; + _initialListSubscription = null; + _eventsController.close(); + } + + /// On Windows, if [directory] is deleted, we will not receive any event. + /// + /// Instead, we add a watcher on the parent folder (if any), that can notify + /// us about [path]. This also includes events such as moves. + void _startParentWatcher() { + var absoluteDir = p.absolute(path); + var parent = p.dirname(absoluteDir); + // Check if [path] is already the root directory. + if (FileSystemEntity.identicalSync(parent, path)) return; + var parentStream = new Directory(parent).watch(recursive: false); + _parentWatchSubscription = parentStream.listen((event) { + // Only look at events for 'directory'. + if (p.basename(event.path) != p.basename(absoluteDir)) return; + // Test if the directory is removed. FileSystemEntity.typeSync will + // return NOT_FOUND if it's unable to decide upon the type, including + // access denied issues, which may happen when the directory is deleted. + // FileSystemMoveEvent and FileSystemDeleteEvent events will always mean + // the directory is now gone. + if (event is FileSystemMoveEvent || + event is FileSystemDeleteEvent || + (FileSystemEntity.typeSync(path) == + FileSystemEntityType.NOT_FOUND)) { + for (var path in _files.paths) { + _emitEvent(ChangeType.REMOVE, path); + } + _files.clear(); + close(); + } + }, onError: (error) { + // Ignore errors, simply close the stream. The user listens on + // [directory], and while it can fail to listen on the parent, we may + // still be able to listen on the path requested. + _parentWatchSubscription.cancel(); + _parentWatchSubscription = null; + }); + } + + void _onEvent(FileSystemEvent event) { + assert(isReady); + final batcher = _eventBatchers.putIfAbsent( + event.path, () => new _EventBatcher()); + batcher.addEvent(event, () { + _eventBatchers.remove(event.path); + _onBatch(batcher.events); + }); + } + + /// The callback that's run when [Directory.watch] emits a batch of events. + void _onBatch(List<FileSystemEvent> batch) { + _sortEvents(batch).forEach((path, eventSet) { + + var canonicalEvent = _canonicalEvent(eventSet); + var events = canonicalEvent == null ? + _eventsBasedOnFileSystem(path) : [canonicalEvent]; + + for (var event in events) { + if (event is FileSystemCreateEvent) { + if (!event.isDirectory) { + if (_files.contains(path)) continue; + + _emitEvent(ChangeType.ADD, path); + _files.add(path); + continue; + } + + if (_files.containsDir(path)) continue; + + var stream = new Directory(path).list(recursive: true); + StreamSubscription<FileSystemEntity> subscription; + subscription = stream.listen((entity) { + if (entity is Directory) return; + if (_files.contains(path)) return; + + _emitEvent(ChangeType.ADD, entity.path); + _files.add(entity.path); + }, onDone: () { + _listSubscriptions.remove(subscription); + }, onError: (e, stackTrace) { + _listSubscriptions.remove(subscription); + _emitError(e, stackTrace); + }, cancelOnError: true); + _listSubscriptions.add(subscription); + } else if (event is FileSystemModifyEvent) { + if (!event.isDirectory) { + _emitEvent(ChangeType.MODIFY, path); + } + } else { + assert(event is FileSystemDeleteEvent); + for (var removedPath in _files.remove(path)) { + _emitEvent(ChangeType.REMOVE, removedPath); + } + } + } + }); + } + + /// Sort all the events in a batch into sets based on their path. + /// + /// A single input event may result in multiple events in the returned map; + /// for example, a MOVE event becomes a DELETE event for the source and a + /// CREATE event for the destination. + /// + /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it + /// contain any events relating to [path]. + Map<String, Set<FileSystemEvent>> _sortEvents(List<FileSystemEvent> batch) { + var eventsForPaths = <String, Set>{}; + + // Events within directories that already have events are superfluous; the + // directory's full contents will be examined anyway, so we ignore such + // events. Emitting them could cause useless or out-of-order events. + var directories = unionAll(batch.map((event) { + if (!event.isDirectory) return new Set(); + if (event is FileSystemMoveEvent) { + return new Set.from([event.path, event.destination]); + } + return new Set.from([event.path]); + })); + + isInModifiedDirectory(path) => + directories.any((dir) => path != dir && path.startsWith(dir)); + + addEvent(path, event) { + if (isInModifiedDirectory(path)) return; + var set = eventsForPaths.putIfAbsent(path, () => new Set()); + set.add(event); + } + + for (var event in batch) { + if (event is FileSystemMoveEvent) { + addEvent(event.destination, event); + } + addEvent(event.path, event); + } + + return eventsForPaths; + } + + /// Returns the canonical event from a batch of events on the same path, if + /// one exists. + /// + /// If [batch] doesn't contain any contradictory events (e.g. DELETE and + /// CREATE, or events with different values for [isDirectory]), this returns a + /// single event that describes what happened to the path in question. + /// + /// If [batch] does contain contradictory events, this returns `null` to + /// indicate that the state of the path on the filesystem should be checked to + /// determine what occurred. + FileSystemEvent _canonicalEvent(Set<FileSystemEvent> batch) { + // An empty batch indicates that we've learned earlier that the batch is + // contradictory (e.g. because of a move). + if (batch.isEmpty) return null; + + var type = batch.first.type; + var isDir = batch.first.isDirectory; + + for (var event in batch.skip(1)) { + // If one event reports that the file is a directory and another event + // doesn't, that's a contradiction. + if (isDir != event.isDirectory) return null; + + // Modify events don't contradict either CREATE or REMOVE events. We can + // safely assume the file was modified after a CREATE or before the + // REMOVE; otherwise there will also be a REMOVE or CREATE event + // (respectively) that will be contradictory. + if (event is FileSystemModifyEvent) continue; + assert(event is FileSystemCreateEvent || + event is FileSystemDeleteEvent || + event is FileSystemMoveEvent); + + // If we previously thought this was a MODIFY, we now consider it to be a + // CREATE or REMOVE event. This is safe for the same reason as above. + if (type == FileSystemEvent.MODIFY) { + type = event.type; + continue; + } + + // A CREATE event contradicts a REMOVE event and vice versa. + assert(type == FileSystemEvent.CREATE || + type == FileSystemEvent.DELETE || + type == FileSystemEvent.MOVE); + if (type != event.type) return null; + } + + switch (type) { + case FileSystemEvent.CREATE: + return new ConstructableFileSystemCreateEvent(batch.first.path, isDir); + case FileSystemEvent.DELETE: + return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); + case FileSystemEvent.MODIFY: + return new ConstructableFileSystemModifyEvent( + batch.first.path, isDir, false); + case FileSystemEvent.MOVE: + return null; + default: throw 'unreachable'; + } + } + + /// Returns one or more events that describe the change between the last known + /// state of [path] and its current state on the filesystem. + /// + /// This returns a list whose order should be reflected in the events emitted + /// to the user, unlike the batched events from [Directory.watch]. The + /// returned list may be empty, indicating that no changes occurred to [path] + /// (probably indicating that it was created and then immediately deleted). + List<FileSystemEvent> _eventsBasedOnFileSystem(String path) { + var fileExisted = _files.contains(path); + var dirExisted = _files.containsDir(path); + var fileExists = new File(path).existsSync(); + var dirExists = new Directory(path).existsSync(); + + var events = <FileSystemEvent>[]; + if (fileExisted) { + if (fileExists) { + events.add(new ConstructableFileSystemModifyEvent(path, false, false)); + } else { + events.add(new ConstructableFileSystemDeleteEvent(path, false)); + } + } else if (dirExisted) { + if (dirExists) { + // If we got contradictory events for a directory that used to exist and + // still exists, we need to rescan the whole thing in case it was + // replaced with a different directory. + events.add(new ConstructableFileSystemDeleteEvent(path, true)); + events.add(new ConstructableFileSystemCreateEvent(path, true)); + } else { + events.add(new ConstructableFileSystemDeleteEvent(path, true)); + } + } + + if (!fileExisted && fileExists) { + events.add(new ConstructableFileSystemCreateEvent(path, false)); + } else if (!dirExisted && dirExists) { + events.add(new ConstructableFileSystemCreateEvent(path, true)); + } + + return events; + } + + /// The callback that's run when the [Directory.watch] stream is closed. + /// Note that this is unlikely to happen on Windows, unless the system itself + /// closes the handle. + void _onDone() { + _watchSubscription = null; + + // Emit remove events for any remaining files. + for (var file in _files.paths) { + _emitEvent(ChangeType.REMOVE, file); + } + _files.clear(); + close(); + } + + /// Start or restart the underlying [Directory.watch] stream. + void _startWatch() { + // Batch the events together so that we can dedup events. + var innerStream = new Directory(path).watch(recursive: true); + _watchSubscription = innerStream.listen(_onEvent, + onError: _eventsController.addError, + onDone: _onDone); + } + + /// Starts or restarts listing the watched directory to get an initial picture + /// of its state. + Future _listDir() { + assert(!isReady); + if (_initialListSubscription != null) _initialListSubscription.cancel(); + + _files.clear(); + var completer = new Completer(); + var stream = new Directory(path).list(recursive: true); + void handleEntity(entity) { + if (entity is! Directory) _files.add(entity.path); + } + _initialListSubscription = stream.listen( + handleEntity, + onError: _emitError, + onDone: completer.complete, + cancelOnError: true); + return completer.future; + } + + /// Emit an event with the given [type] and [path]. + void _emitEvent(ChangeType type, String path) { + if (!isReady) return; + + _eventsController.add(new WatchEvent(type, path)); + } + + /// Emit an error, then close the watcher. + void _emitError(error, StackTrace stackTrace) { + _eventsController.addError(error, stackTrace); + close(); + } +}
diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index bee3546..35b6517 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml
@@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+3 +version: 0.9.8-dev author: Dart Team <misc@dartlang.org> homepage: https://github.com/dart-lang/watcher description: >
diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 4c77ced..55e40a9 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart
@@ -1,25 +1,25 @@ -// Copyright (c) 2014, 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. - -@TestOn('windows') - -import 'package:scheduled_test/scheduled_test.dart'; -import 'package:watcher/src/directory_watcher/windows.dart'; -import 'package:watcher/watcher.dart'; - -import 'shared.dart'; -import '../utils.dart'; - -void main() { - watcherFactory = (dir) => new WindowsDirectoryWatcher(dir); - - setUp(createSandbox); - - sharedTests(); - - test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { - expect(new DirectoryWatcher('.'), - new isInstanceOf<WindowsDirectoryWatcher>()); - }); -} +// Copyright (c) 2014, 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. + +@TestOn('windows') + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/src/directory_watcher/windows.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +void main() { + watcherFactory = (dir) => new WindowsDirectoryWatcher(dir); + + setUp(createSandbox); + + sharedTests(); + + test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { + expect(new DirectoryWatcher('.'), + new isInstanceOf<WindowsDirectoryWatcher>()); + }); +}