Chromium Code Reviews| 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); | |
|
Paul Berry
2015/07/19 22:49:08
Perhaps I should have been clearer about my sugges
Brian Wilkerson
2015/07/20 15:52:17
Ok. The 'if' is restored.
| |
| 94 childNode.subscription.cancel(); | |
| 95 childNode.subscription = null; | |
| 96 } | |
| 97 } | |
| 98 } | |
| 99 | |
| 100 /** | |
| 101 * Record that we are no longer watching the given [folder] with the given | |
| 102 * [token]. | |
| 103 * | |
| 104 * Throws a [StateError] if the folder is not be watched or is not associated | |
| 105 * with the given token. | |
| 106 */ | |
| 107 void removeFolder(Folder folder, T token) { | |
| 108 WatchNode<T> folderNode = _watchedFolders[folder]; | |
| 109 if (folderNode == null) { | |
| 110 throw new StateError('Folder (${folder.path}) is not being watched.'); | |
|
Paul Berry
2015/07/19 22:49:08
I have a similar concern here. By throwing an exc
Brian Wilkerson
2015/07/20 15:52:17
Ok. I've put the old code back and added the asser
| |
| 111 } | |
| 112 Set<T> tokens = folderNode.tokens; | |
| 113 if (!tokens.remove(token)) { | |
| 114 throw new StateError( | |
| 115 'Folder (${folder.path}) is not associated with token ($token)'); | |
| 116 } | |
| 117 // | |
| 118 // If this was the last token associated with this folder, then remove the | |
| 119 // folder from the tree. | |
| 120 // | |
| 121 if (tokens.isEmpty) { | |
| 122 // | |
| 123 // If the folder was a top-level folder, then we need to create | |
| 124 // subscriptions for all of its children and cancel its subscription. | |
| 125 // | |
| 126 if (folderNode.subscription != null) { | |
| 127 for (WatchNode<T> childNode in folderNode.children) { | |
| 128 assert(childNode.subscription == null); | |
| 129 childNode.subscription = | |
| 130 childNode.folder.changes.listen(_handleWatchEvent); | |
| 131 } | |
| 132 folderNode.subscription.cancel(); | |
| 133 folderNode.subscription = null; | |
| 134 } | |
| 135 folderNode.delete(); | |
| 136 _watchedFolders.remove(folder); | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 /** | |
| 141 * Dispatch the given event by finding all of the tokens that contain the | |
| 142 * resource and invoke the [handleWatchEvent] function. | |
| 143 */ | |
| 144 void _handleWatchEvent(WatchEvent event) { | |
| 145 String path = event.path; | |
| 146 List<T> tokens = <T>[]; | |
| 147 WatchNode<T> parent = rootNode.findParent(path); | |
| 148 while (parent != rootNode) { | |
| 149 tokens.addAll(parent.tokens); | |
| 150 parent = parent.parent; | |
| 151 } | |
| 152 if (tokens.isNotEmpty) { | |
| 153 handleWatchEvent(event, tokens); | |
| 154 } | |
| 155 } | |
| 156 } | |
| 157 | |
| 158 /** | |
| 159 * The information kept by a [WatchManager] about a single folder that is being | |
| 160 * watched. | |
| 161 * | |
| 162 * Watch nodes form a tree in which one node is a child of another node if the | |
| 163 * child's folder is contained in the parent's folder and none of the folders | |
| 164 * between the parent's folder and the child's folder are being watched. | |
| 165 */ | |
| 166 class WatchNode<T> { | |
| 167 /** | |
| 168 * The folder for which information is being maintained. This is `null` for | |
| 169 * the unique "root" node that maintains references to all of the top-level | |
| 170 * folders being watched. | |
| 171 */ | |
| 172 final Folder folder; | |
| 173 | |
| 174 /** | |
| 175 * The parent of this node. | |
| 176 */ | |
| 177 WatchNode parent; | |
| 178 | |
| 179 /** | |
| 180 * The information for the children of this node. | |
| 181 */ | |
| 182 final List<WatchNode> _children = <WatchNode>[]; | |
| 183 | |
| 184 /** | |
| 185 * The tokens that were used to register interest in watching this folder. | |
| 186 */ | |
| 187 final Set<T> tokens = new HashSet<T>(); | |
| 188 | |
| 189 /** | |
| 190 * The subscription being used to watch the folder, or `null` if the folder | |
| 191 * is being watched as part of a containing folder (in other words, if the | |
| 192 * parent is not the special "root"). | |
| 193 */ | |
| 194 StreamSubscription<WatchEvent> subscription; | |
| 195 | |
| 196 /** | |
| 197 * Initialize a newly created node to represent the given [folder]. | |
| 198 */ | |
| 199 WatchNode(this.folder); | |
| 200 | |
| 201 /** | |
| 202 * Return a list containing the children of this node. | |
| 203 */ | |
| 204 Iterable<WatchNode> get children => _children; | |
| 205 | |
| 206 /** | |
| 207 * Remove this node from the tree of watched folders. | |
| 208 */ | |
| 209 void delete() { | |
| 210 if (parent != null) { | |
| 211 parent._removeChild(this); | |
| 212 parent = null; | |
| 213 } | |
| 214 } | |
| 215 | |
| 216 /** | |
| 217 * Return the highest node reachable from this node that contains the given | |
| 218 * [filePath]. If no other node is found, return this node, even if this node | |
| 219 * does not contain the path. | |
| 220 */ | |
| 221 WatchNode findParent(String filePath) { | |
| 222 if (_children == null) { | |
| 223 return this; | |
| 224 } | |
| 225 for (WatchNode childNode in _children) { | |
| 226 if (childNode.folder.isOrContains(filePath)) { | |
| 227 return childNode.findParent(filePath); | |
| 228 } | |
| 229 } | |
| 230 return this; | |
| 231 } | |
| 232 | |
| 233 /** | |
| 234 * Insert the given [node] into the tree of watched folders, either as a child | |
| 235 * of this node or as a descendent of one of this node's children. Return the | |
| 236 * immediate parent of the newly added node. | |
| 237 */ | |
| 238 WatchNode insert(WatchNode node) { | |
| 239 WatchNode parentNode = findParent(node.folder.path); | |
| 240 parentNode._addChild(node, true); | |
| 241 return parentNode; | |
| 242 } | |
| 243 | |
| 244 @override | |
| 245 String toString() => 'WatchNode (' | |
| 246 'folder = ${folder == null ? '<root>' : folder.path}, ' | |
| 247 'tokens = $tokens, ' | |
| 248 'subscription = ${subscription == null ? 'null' : 'non-null'})'; | |
| 249 | |
| 250 /** | |
| 251 * Add the given [newChild] as an immediate child of this node. | |
| 252 * | |
| 253 * If [checkChildren] is `true`, check to see whether any of the previously | |
| 254 * existing children of this node should now be children of the new child, and | |
| 255 * if so, move them. | |
| 256 */ | |
| 257 void _addChild(WatchNode newChild, bool checkChildren) { | |
| 258 if (checkChildren) { | |
| 259 Folder folder = newChild.folder; | |
| 260 for (int i = _children.length - 1; i >= 0; i--) { | |
| 261 WatchNode existingChild = _children[i]; | |
| 262 if (folder.contains(existingChild.folder.path)) { | |
| 263 newChild._addChild(existingChild, false); | |
| 264 _children.removeAt(i); | |
| 265 } | |
| 266 } | |
| 267 } | |
| 268 newChild.parent = this; | |
| 269 _children.add(newChild); | |
| 270 } | |
| 271 | |
| 272 /** | |
| 273 * Remove the given [node] from the list of children of this node. Any | |
| 274 * children of the [node] will become children of this node. | |
| 275 */ | |
| 276 void _removeChild(WatchNode child) { | |
| 277 _children.remove(child); | |
| 278 for (WatchNode grandchild in child.children) { | |
| 279 grandchild.parent = this; | |
| 280 _children.add(grandchild); | |
| 281 child._children.remove(grandchild); | |
|
Paul Berry
2015/07/19 22:49:08
I don't think this is safe, since it causes child.
Brian Wilkerson
2015/07/20 15:52:17
No, I forgot to re-run the tests. I did fail. They
| |
| 282 } | |
| 283 } | |
| 284 } | |
| OLD | NEW |