Merge pull request #31 from dart-lang/newlines

Convert all files to Unix newlines.
diff --git a/lib/src/directory_watcher/windows.dart b/lib/src/directory_watcher/windows.dart
index 67a2741..ec119f7 100644
--- a/lib/src/directory_watcher/windows.dart
+++ b/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/test/directory_watcher/windows_test.dart b/test/directory_watcher/windows_test.dart
index 4c77ced..55e40a9 100644
--- a/test/directory_watcher/windows_test.dart
+++ b/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>());
+  });
+}