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

Unified Diff: pkg/analysis_server/lib/src/watch_manager.dart

Issue 1244613004: Implement support for watching directories containing implicitly analyzed files (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Created 5 years, 5 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: pkg/analysis_server/lib/src/watch_manager.dart
diff --git a/pkg/analysis_server/lib/src/watch_manager.dart b/pkg/analysis_server/lib/src/watch_manager.dart
new file mode 100644
index 0000000000000000000000000000000000000000..4dbcc676b6f2a235e04c1681652254ae75806761
--- /dev/null
+++ b/pkg/analysis_server/lib/src/watch_manager.dart
@@ -0,0 +1,305 @@
+// Copyright (c) 2015, 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 context.directory.manager;
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:core' hide Resource;
+
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:watcher/watcher.dart';
+
+/**
+ * A function called when a watch [event] associated with a watched resource is
+ * received. The list of [tokens] will contain all of the tokens associated with
+ * folders containing (or the same as) the watched resource.
+ */
+typedef void HandleWatchEvent<T>(WatchEvent event, List<T> tokens);
+
+/**
+ * An object that manages a collections of folders that need to be watched in
+ * order to ensure that we are watching the minimum number of folders.
+ *
+ * Each folder can be watched multiple times. In order to differenciate between
+ * the watch requests, each watch request has a *token* associated with it. The
+ * tokens that are used must correctly implement both [==] and [hashCode].
+ */
+class WatchManager<T> {
+ /**
+ * The resource provider used to convert paths to resources.
+ */
+ final ResourceProvider provider;
+
+ /**
+ * The function that is invoked when a watch event is received.
+ */
+ final HandleWatchEvent<T> handleWatchEvent;
+
+ /**
+ * A node representing the (conceptual) root of all other folders.
+ */
+ final WatchNode<T> rootNode = new WatchNode<T>(null);
+
+ /**
+ * A table mapping the folders that are being watched to the nodes
+ * representing those folders.
+ */
+ final Map<Folder, WatchNode<T>> _watchedFolders =
+ new HashMap<Folder, WatchNode<T>>();
+
+ /**
+ * Initialize a newly created watch manager to use the resource [provider] to
+ * convert file paths to resources and to call the [handleWatchEvent] function
+ * to notify the owner of the manager when resources have been changed.
+ */
+ WatchManager(this.provider, this.handleWatchEvent);
+
+ /**
+ * Record the fact that we are now watching the given [folder], and associate
+ * that folder with the given [token].
Paul Berry 2015/07/19 18:38:34 Documentation should say what happens if the same
Brian Wilkerson 2015/07/19 21:24:19 Done
+ */
+ void addFolder(Folder folder, T token) {
+ WatchNode<T> folderNode = _watchedFolders[folder];
+ //
+ // If the folder was already being watched, just record the new token.
+ //
+ if (folderNode != null) {
+ folderNode.tokens.add(token);
+ return;
+ }
+ //
+ // Otherwise, add the folder to the tree.
+ //
+ folderNode = new WatchNode<T>(folder);
+ _watchedFolders[folder] = folderNode;
+ folderNode.tokens.add(token);
+ WatchNode<T> parentNode = rootNode.insert(folderNode);
+ //
+ // If we are not watching a folder that contains the folder, then create a
+ // subscription for it.
+ //
+ if (parentNode == rootNode) {
+ folderNode.subscription = folder.changes.listen((WatchEvent event) {
Paul Berry 2015/07/19 18:38:34 How about just: folderNode.subscription = folder.
Brian Wilkerson 2015/07/19 21:24:20 Done
+ _handleWatchEvent(event);
+ });
+ //
+ // Any nodes that became children of the newly added folder would have
+ // been top-level folders and would have been watched. We need to cancel
+ // their subscriptions.
+ //
+ for (WatchNode<T> childNode in folderNode.children) {
+ if (childNode.subscription != null) {
Paul Berry 2015/07/19 18:38:33 Consider adding: assert(childNode.subscription !=
Brian Wilkerson 2015/07/19 21:24:20 Done
+ childNode.subscription.cancel();
+ childNode.subscription = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Record that we are no longer watching the given [folder] with the given
+ * [token].
+ */
Paul Berry 2015/07/19 18:38:33 I like the fact that nothing happens if removeFold
Brian Wilkerson 2015/07/19 21:24:20 Done
+ void removeFolder(Folder folder, T token) {
+ WatchNode<T> folderNode = _watchedFolders[folder];
+ if (folderNode == null) {
+ return;
+ }
+ Set<T> tokens = folderNode.tokens;
+ tokens.remove(token);
+ //
+ // If this was the last token associated with this folder, then remove the
+ // folder from the tree.
+ //
+ if (tokens.isEmpty) {
+ //
+ // If the folder was a top-level folder, then we need to create
+ // subscriptions for all of it's children and cancel it's subscription.
Paul Berry 2015/07/19 18:38:33 Nit: both "it's" should be "its".
Brian Wilkerson 2015/07/19 21:24:20 Done
+ //
+ if (folderNode.subscription != null) {
+ for (WatchNode<T> childNode in folderNode.children) {
+ childNode.subscription = childNode.folder.changes
Paul Berry 2015/07/19 18:38:33 Consider adding "assert(childNode.subscription ==
Brian Wilkerson 2015/07/19 21:24:20 Done
+ .listen((WatchEvent event) {
Paul Berry 2015/07/19 18:38:33 Again, ".listen(_handleWatchEvent);" should be suf
Brian Wilkerson 2015/07/19 21:24:20 Done
+ _handleWatchEvent(event);
+ });
+ }
+ folderNode.subscription.cancel();
+ folderNode.subscription = null;
+ }
+ folderNode.delete();
+ _watchedFolders.remove(folder);
+ }
+ }
+
+ /**
+ * Dispatch the given event by finding all of the tokens that contain the
+ * resource and invoke the [handleWatchEvent] function.
+ */
+ void _handleWatchEvent(WatchEvent event) {
+ String path = event.path;
+ List<T> tokens = <T>[];
+// void visitNode(WatchNode node) {
Paul Berry 2015/07/19 18:38:34 Was this commented-out code left here by accident?
Brian Wilkerson 2015/07/19 21:24:20 It was missed while cleaning-up.
+// Folder folder = node.folder;
+// if (folder.contains(path)) {
+// tokens.addAll(_watchedFolders[folder].tokens);
+// }
+// node.forEachChild(visitNode);
+// }
+// visitNode(rootFolder.findParent(path));
+ WatchNode<T> parent = rootNode.findParent(path);
Paul Berry 2015/07/19 18:38:34 What happens if path is identical to the path to o
Brian Wilkerson 2015/07/19 21:24:20 Done
+ while (parent != rootNode) {
+ tokens.addAll(parent.tokens);
+ parent = parent.parent;
+ }
+ if (tokens.isNotEmpty) {
+ handleWatchEvent(event, tokens);
+ }
+ }
+}
+
+/**
+ * The information kept by a [WatchManager] about a single folder that is being
+ * watched.
+ *
+ * Watch nodes form a tree in which one node is a child of another node if the
+ * child's folder is contained in the parent's folder and none of the folders
+ * between the parent's folder and the child's folder are being watched.
+ */
+class WatchNode<T> {
+ /**
+ * An empty list of watch nodes.
+ */
+ static List<WatchNode> EMPTY_LIST = <WatchNode>[];
Paul Berry 2015/07/19 18:38:33 I think this goes away if you take my suggestion b
Brian Wilkerson 2015/07/19 21:24:20 Done
+
+ /**
+ * The folder for which information is being maintained. This is `null` for
+ * the unique "root" node that maintains references to all of the top-level
+ * folders being watched.
+ */
+ Folder folder;
Paul Berry 2015/07/19 18:38:33 Can this be final?
Brian Wilkerson 2015/07/19 21:24:20 Done
+
+ /**
+ * The parent of this node.
+ */
+ WatchNode parent;
+
+ /**
+ * The information for the children of this node.
+ */
+ List<WatchNode> _children = null;
Paul Berry 2015/07/19 18:38:33 It seems like premature optimization to use `null`
Brian Wilkerson 2015/07/19 21:24:20 Done
+
+ /**
+ * The tokens that were used to register interest in watching this folder.
+ */
+ Set<T> tokens = new HashSet<T>();
Paul Berry 2015/07/19 18:38:34 This can be final.
Brian Wilkerson 2015/07/19 21:24:19 Done
+
+ /**
+ * The subscription being used to watch the folder, or `null` if the folder
+ * is being watched as part of a containing folder (in other words, if the
+ * parent is not the special "root").
+ */
+ StreamSubscription<WatchEvent> subscription;
+
+ /**
+ * Initialize a newly created node to represent the given [folder].
+ */
+ WatchNode(this.folder);
+
+ /**
+ * Return a list containing the children of this node.
+ */
+ List<WatchNode> get children => _children == null ? EMPTY_LIST : _children;
Paul Berry 2015/07/19 18:38:34 Consider changing the return type to Iterable<Watc
Brian Wilkerson 2015/07/19 21:24:20 Done
+
+ /**
+ * Remove this node from the tree of watched folders.
+ */
+ void delete() {
+ if (parent != null) {
+ parent._removeChild(this);
+ }
Paul Berry 2015/07/19 18:38:34 Consider setting parent to null so the node return
Brian Wilkerson 2015/07/19 21:24:20 Done
+ }
+
+ /**
+ * Return the highest node reachable from this node that contains the given
+ * [filePath]. If no other node is found, return this node, even if this node
+ * does not contain the path.
+ */
+ WatchNode findParent(String filePath) {
+ if (_children == null) {
+ return this;
+ }
+ for (WatchNode childNode in _children) {
+ if (childNode.folder.contains(filePath)) {
+ return childNode.findParent(filePath);
+ }
+ }
+ return this;
+ }
+
+ @override
+ String toString() => 'WatchNode ('
+ 'folder = ${folder == null ? '<root>' : folder.path}, '
+ 'tokens = $tokens, '
+ 'subscription = ${subscription == null ? 'null' : 'non-null'})';
+
+ /**
+ * Invoke the given [function] on each of the children of this node.
+ */
+ void forEachChild(void function(WatchNode<T> node)) {
Paul Berry 2015/07/19 18:38:33 This method is only called from commented-out code
Brian Wilkerson 2015/07/19 21:24:20 Done
+ if (_children != null) {
+ _children.forEach(function);
+ }
+ }
+
+ /**
+ * Insert the given [node] into the tree of watched folders, either as a child
+ * of this node or as a descendent of one of this node's children. Return the
+ * immediate parent of the newly added node.
+ */
+ WatchNode insert(WatchNode node) {
+ WatchNode parentNode = findParent(node.folder.path);
+ parentNode._addChild(node, true);
+ return parentNode;
+ }
+
+ /**
+ * Add the given [newChild] as an immediate child of this node.
+ *
+ * If [checkChildren] is `true`, check to see whether any of the previously
+ * existing children of this node should now be children of the new child, and
+ * if so, move them.
+ */
+ void _addChild(WatchNode newChild, bool checkChildren) {
+ if (_children == null) {
+ _children = <WatchNode>[];
+ } else if (checkChildren) {
+ Folder folder = newChild.folder;
+ for (int i = _children.length - 1; i >= 0; i--) {
+ WatchNode existingChild = _children[i];
+ if (folder.contains(existingChild.folder.path)) {
+ newChild._addChild(existingChild, false);
+ _children.removeAt(i);
+ }
+ }
+ }
+ newChild.parent = this;
+ _children.add(newChild);
+ }
+
+ /**
+ * Remove the given [node] from the list of children of this node. Any
+ * children of the [node] will become children of this node.
+ */
+ void _removeChild(WatchNode child) {
+ _children.remove(child);
+ for (WatchNode grandchild in child.children) {
+ grandchild.parent = this;
+ _children.add(grandchild);
+ }
+ if (_children.isEmpty) {
+ _children = null;
+ }
+ }
Paul Berry 2015/07/19 18:38:33 Consider setting child._children to null so that t
Brian Wilkerson 2015/07/19 21:24:21 I can't because now there's an assumption that _ch
+}

Powered by Google App Engine
This is Rietveld 408576698