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

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: Address comments 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
« no previous file with comments | « no previous file | pkg/analysis_server/test/src/watch_manager_test.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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..d4df4d5861cd216b7ce85c97cc81119f61bb71d0
--- /dev/null
+++ b/pkg/analysis_server/lib/src/watch_manager.dart
@@ -0,0 +1,287 @@
+// 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]. If the folder is already being watched
+ * and is already associated with the token, then this request is effectively
+ * ignored.
+ */
+ 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(_handleWatchEvent);
+ //
+ // 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) {
+ assert(childNode.subscription != null);
+ if (childNode.subscription != null) {
+ childNode.subscription.cancel();
+ childNode.subscription = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Record that we are no longer watching the given [folder] with the given
+ * [token].
+ *
+ * Throws a [StateError] if the folder is not be watched or is not associated
+ * with the given token.
+ */
+ void removeFolder(Folder folder, T token) {
+ WatchNode<T> folderNode = _watchedFolders[folder];
+ if (folderNode == null) {
+ assert(false);
+ return;
+ }
+ Set<T> tokens = folderNode.tokens;
+ if (!tokens.remove(token)) {
+ assert(false);
+ }
+ //
+ // 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 its children and cancel its subscription.
+ //
+ if (folderNode.subscription != null) {
+ for (WatchNode<T> childNode in folderNode.children) {
+ assert(childNode.subscription == null);
+ childNode.subscription =
+ childNode.folder.changes.listen(_handleWatchEvent);
+ }
+ 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>[];
+ WatchNode<T> parent = rootNode.findParent(path);
+ 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> {
+ /**
+ * 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.
+ */
+ final Folder folder;
+
+ /**
+ * The parent of this node.
+ */
+ WatchNode parent;
+
+ /**
+ * The information for the children of this node.
+ */
+ final List<WatchNode> _children = <WatchNode>[];
+
+ /**
+ * The tokens that were used to register interest in watching this folder.
+ */
+ final Set<T> tokens = new HashSet<T>();
+
+ /**
+ * 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.
+ */
+ Iterable<WatchNode> get children => _children;
+
+ /**
+ * Remove this node from the tree of watched folders.
+ */
+ void delete() {
+ if (parent != null) {
+ parent._removeChild(this);
+ parent = null;
+ }
+ }
+
+ /**
+ * 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.isOrContains(filePath)) {
+ return childNode.findParent(filePath);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * 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;
+ }
+
+ @override
+ String toString() => 'WatchNode ('
+ 'folder = ${folder == null ? '<root>' : folder.path}, '
+ 'tokens = $tokens, '
+ 'subscription = ${subscription == null ? 'null' : 'non-null'})';
+
+ /**
+ * 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 (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);
+ Iterable<WatchNode> grandchildren = child.children;
+ for (WatchNode grandchild in grandchildren) {
+ grandchild.parent = this;
+ _children.add(grandchild);
+ }
+ child._children.clear();
+ }
+}
« no previous file with comments | « no previous file | pkg/analysis_server/test/src/watch_manager_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698