OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library context.directory.manager; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:collection'; |
| 9 import 'dart:core' hide Resource; |
| 10 |
| 11 import 'package:analyzer/file_system/file_system.dart'; |
| 12 import 'package:watcher/watcher.dart'; |
| 13 |
| 14 /** |
| 15 * A function called when a watch [event] associated with a watched resource is |
| 16 * received. The list of [tokens] will contain all of the tokens associated with |
| 17 * folders containing (or the same as) the watched resource. |
| 18 */ |
| 19 typedef void HandleWatchEvent<T>(WatchEvent event, List<T> tokens); |
| 20 |
| 21 /** |
| 22 * An object that manages a collections of folders that need to be watched in |
| 23 * order to ensure that we are watching the minimum number of folders. |
| 24 * |
| 25 * Each folder can be watched multiple times. In order to differenciate between |
| 26 * the watch requests, each watch request has a *token* associated with it. The |
| 27 * tokens that are used must correctly implement both [==] and [hashCode]. |
| 28 */ |
| 29 class WatchManager<T> { |
| 30 /** |
| 31 * The resource provider used to convert paths to resources. |
| 32 */ |
| 33 final ResourceProvider provider; |
| 34 |
| 35 /** |
| 36 * The function that is invoked when a watch event is received. |
| 37 */ |
| 38 final HandleWatchEvent<T> handleWatchEvent; |
| 39 |
| 40 /** |
| 41 * A node representing the (conceptual) root of all other folders. |
| 42 */ |
| 43 final WatchNode<T> rootNode = new WatchNode<T>(null); |
| 44 |
| 45 /** |
| 46 * A table mapping the folders that are being watched to the nodes |
| 47 * representing those folders. |
| 48 */ |
| 49 final Map<Folder, WatchNode<T>> _watchedFolders = |
| 50 new HashMap<Folder, WatchNode<T>>(); |
| 51 |
| 52 /** |
| 53 * Initialize a newly created watch manager to use the resource [provider] to |
| 54 * convert file paths to resources and to call the [handleWatchEvent] function |
| 55 * to notify the owner of the manager when resources have been changed. |
| 56 */ |
| 57 WatchManager(this.provider, this.handleWatchEvent); |
| 58 |
| 59 /** |
| 60 * Record the fact that we are now watching the given [folder], and associate |
| 61 * that folder with the given [token]. If the folder is already being watched |
| 62 * and is already associated with the token, then this request is effectively |
| 63 * ignored. |
| 64 */ |
| 65 void addFolder(Folder folder, T token) { |
| 66 WatchNode<T> folderNode = _watchedFolders[folder]; |
| 67 // |
| 68 // If the folder was already being watched, just record the new token. |
| 69 // |
| 70 if (folderNode != null) { |
| 71 folderNode.tokens.add(token); |
| 72 return; |
| 73 } |
| 74 // |
| 75 // Otherwise, add the folder to the tree. |
| 76 // |
| 77 folderNode = new WatchNode<T>(folder); |
| 78 _watchedFolders[folder] = folderNode; |
| 79 folderNode.tokens.add(token); |
| 80 WatchNode<T> parentNode = rootNode.insert(folderNode); |
| 81 // |
| 82 // If we are not watching a folder that contains the folder, then create a |
| 83 // subscription for it. |
| 84 // |
| 85 if (parentNode == rootNode) { |
| 86 folderNode.subscription = folder.changes.listen(_handleWatchEvent); |
| 87 // |
| 88 // Any nodes that became children of the newly added folder would have |
| 89 // been top-level folders and would have been watched. We need to cancel |
| 90 // their subscriptions. |
| 91 // |
| 92 for (WatchNode<T> childNode in folderNode.children) { |
| 93 assert(childNode.subscription != null); |
| 94 if (childNode.subscription != null) { |
| 95 childNode.subscription.cancel(); |
| 96 childNode.subscription = null; |
| 97 } |
| 98 } |
| 99 } |
| 100 } |
| 101 |
| 102 /** |
| 103 * Record that we are no longer watching the given [folder] with the given |
| 104 * [token]. |
| 105 * |
| 106 * Throws a [StateError] if the folder is not be watched or is not associated |
| 107 * with the given token. |
| 108 */ |
| 109 void removeFolder(Folder folder, T token) { |
| 110 WatchNode<T> folderNode = _watchedFolders[folder]; |
| 111 if (folderNode == null) { |
| 112 assert(false); |
| 113 return; |
| 114 } |
| 115 Set<T> tokens = folderNode.tokens; |
| 116 if (!tokens.remove(token)) { |
| 117 assert(false); |
| 118 } |
| 119 // |
| 120 // If this was the last token associated with this folder, then remove the |
| 121 // folder from the tree. |
| 122 // |
| 123 if (tokens.isEmpty) { |
| 124 // |
| 125 // If the folder was a top-level folder, then we need to create |
| 126 // subscriptions for all of its children and cancel its subscription. |
| 127 // |
| 128 if (folderNode.subscription != null) { |
| 129 for (WatchNode<T> childNode in folderNode.children) { |
| 130 assert(childNode.subscription == null); |
| 131 childNode.subscription = |
| 132 childNode.folder.changes.listen(_handleWatchEvent); |
| 133 } |
| 134 folderNode.subscription.cancel(); |
| 135 folderNode.subscription = null; |
| 136 } |
| 137 folderNode.delete(); |
| 138 _watchedFolders.remove(folder); |
| 139 } |
| 140 } |
| 141 |
| 142 /** |
| 143 * Dispatch the given event by finding all of the tokens that contain the |
| 144 * resource and invoke the [handleWatchEvent] function. |
| 145 */ |
| 146 void _handleWatchEvent(WatchEvent event) { |
| 147 String path = event.path; |
| 148 List<T> tokens = <T>[]; |
| 149 WatchNode<T> parent = rootNode.findParent(path); |
| 150 while (parent != rootNode) { |
| 151 tokens.addAll(parent.tokens); |
| 152 parent = parent.parent; |
| 153 } |
| 154 if (tokens.isNotEmpty) { |
| 155 handleWatchEvent(event, tokens); |
| 156 } |
| 157 } |
| 158 } |
| 159 |
| 160 /** |
| 161 * The information kept by a [WatchManager] about a single folder that is being |
| 162 * watched. |
| 163 * |
| 164 * Watch nodes form a tree in which one node is a child of another node if the |
| 165 * child's folder is contained in the parent's folder and none of the folders |
| 166 * between the parent's folder and the child's folder are being watched. |
| 167 */ |
| 168 class WatchNode<T> { |
| 169 /** |
| 170 * The folder for which information is being maintained. This is `null` for |
| 171 * the unique "root" node that maintains references to all of the top-level |
| 172 * folders being watched. |
| 173 */ |
| 174 final Folder folder; |
| 175 |
| 176 /** |
| 177 * The parent of this node. |
| 178 */ |
| 179 WatchNode parent; |
| 180 |
| 181 /** |
| 182 * The information for the children of this node. |
| 183 */ |
| 184 final List<WatchNode> _children = <WatchNode>[]; |
| 185 |
| 186 /** |
| 187 * The tokens that were used to register interest in watching this folder. |
| 188 */ |
| 189 final Set<T> tokens = new HashSet<T>(); |
| 190 |
| 191 /** |
| 192 * The subscription being used to watch the folder, or `null` if the folder |
| 193 * is being watched as part of a containing folder (in other words, if the |
| 194 * parent is not the special "root"). |
| 195 */ |
| 196 StreamSubscription<WatchEvent> subscription; |
| 197 |
| 198 /** |
| 199 * Initialize a newly created node to represent the given [folder]. |
| 200 */ |
| 201 WatchNode(this.folder); |
| 202 |
| 203 /** |
| 204 * Return a list containing the children of this node. |
| 205 */ |
| 206 Iterable<WatchNode> get children => _children; |
| 207 |
| 208 /** |
| 209 * Remove this node from the tree of watched folders. |
| 210 */ |
| 211 void delete() { |
| 212 if (parent != null) { |
| 213 parent._removeChild(this); |
| 214 parent = null; |
| 215 } |
| 216 } |
| 217 |
| 218 /** |
| 219 * Return the highest node reachable from this node that contains the given |
| 220 * [filePath]. If no other node is found, return this node, even if this node |
| 221 * does not contain the path. |
| 222 */ |
| 223 WatchNode findParent(String filePath) { |
| 224 if (_children == null) { |
| 225 return this; |
| 226 } |
| 227 for (WatchNode childNode in _children) { |
| 228 if (childNode.folder.isOrContains(filePath)) { |
| 229 return childNode.findParent(filePath); |
| 230 } |
| 231 } |
| 232 return this; |
| 233 } |
| 234 |
| 235 /** |
| 236 * Insert the given [node] into the tree of watched folders, either as a child |
| 237 * of this node or as a descendent of one of this node's children. Return the |
| 238 * immediate parent of the newly added node. |
| 239 */ |
| 240 WatchNode insert(WatchNode node) { |
| 241 WatchNode parentNode = findParent(node.folder.path); |
| 242 parentNode._addChild(node, true); |
| 243 return parentNode; |
| 244 } |
| 245 |
| 246 @override |
| 247 String toString() => 'WatchNode (' |
| 248 'folder = ${folder == null ? '<root>' : folder.path}, ' |
| 249 'tokens = $tokens, ' |
| 250 'subscription = ${subscription == null ? 'null' : 'non-null'})'; |
| 251 |
| 252 /** |
| 253 * Add the given [newChild] as an immediate child of this node. |
| 254 * |
| 255 * If [checkChildren] is `true`, check to see whether any of the previously |
| 256 * existing children of this node should now be children of the new child, and |
| 257 * if so, move them. |
| 258 */ |
| 259 void _addChild(WatchNode newChild, bool checkChildren) { |
| 260 if (checkChildren) { |
| 261 Folder folder = newChild.folder; |
| 262 for (int i = _children.length - 1; i >= 0; i--) { |
| 263 WatchNode existingChild = _children[i]; |
| 264 if (folder.contains(existingChild.folder.path)) { |
| 265 newChild._addChild(existingChild, false); |
| 266 _children.removeAt(i); |
| 267 } |
| 268 } |
| 269 } |
| 270 newChild.parent = this; |
| 271 _children.add(newChild); |
| 272 } |
| 273 |
| 274 /** |
| 275 * Remove the given [node] from the list of children of this node. Any |
| 276 * children of the [node] will become children of this node. |
| 277 */ |
| 278 void _removeChild(WatchNode child) { |
| 279 _children.remove(child); |
| 280 Iterable<WatchNode> grandchildren = child.children; |
| 281 for (WatchNode grandchild in grandchildren) { |
| 282 grandchild.parent = this; |
| 283 _children.add(grandchild); |
| 284 } |
| 285 child._children.clear(); |
| 286 } |
| 287 } |
OLD | NEW |