| 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();
|
| + }
|
| +}
|
|
|