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]. | |
|
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
| |
| 62 */ | |
| 63 void addFolder(Folder folder, T token) { | |
| 64 WatchNode<T> folderNode = _watchedFolders[folder]; | |
| 65 // | |
| 66 // If the folder was already being watched, just record the new token. | |
| 67 // | |
| 68 if (folderNode != null) { | |
| 69 folderNode.tokens.add(token); | |
| 70 return; | |
| 71 } | |
| 72 // | |
| 73 // Otherwise, add the folder to the tree. | |
| 74 // | |
| 75 folderNode = new WatchNode<T>(folder); | |
| 76 _watchedFolders[folder] = folderNode; | |
| 77 folderNode.tokens.add(token); | |
| 78 WatchNode<T> parentNode = rootNode.insert(folderNode); | |
| 79 // | |
| 80 // If we are not watching a folder that contains the folder, then create a | |
| 81 // subscription for it. | |
| 82 // | |
| 83 if (parentNode == rootNode) { | |
| 84 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
| |
| 85 _handleWatchEvent(event); | |
| 86 }); | |
| 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 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
| |
| 94 childNode.subscription.cancel(); | |
| 95 childNode.subscription = null; | |
| 96 } | |
| 97 } | |
| 98 } | |
| 99 } | |
| 100 | |
| 101 /** | |
| 102 * Record that we are no longer watching the given [folder] with the given | |
| 103 * [token]. | |
| 104 */ | |
|
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
| |
| 105 void removeFolder(Folder folder, T token) { | |
| 106 WatchNode<T> folderNode = _watchedFolders[folder]; | |
| 107 if (folderNode == null) { | |
| 108 return; | |
| 109 } | |
| 110 Set<T> tokens = folderNode.tokens; | |
| 111 tokens.remove(token); | |
| 112 // | |
| 113 // If this was the last token associated with this folder, then remove the | |
| 114 // folder from the tree. | |
| 115 // | |
| 116 if (tokens.isEmpty) { | |
| 117 // | |
| 118 // If the folder was a top-level folder, then we need to create | |
| 119 // 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
| |
| 120 // | |
| 121 if (folderNode.subscription != null) { | |
| 122 for (WatchNode<T> childNode in folderNode.children) { | |
| 123 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
| |
| 124 .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
| |
| 125 _handleWatchEvent(event); | |
| 126 }); | |
| 127 } | |
| 128 folderNode.subscription.cancel(); | |
| 129 folderNode.subscription = null; | |
| 130 } | |
| 131 folderNode.delete(); | |
| 132 _watchedFolders.remove(folder); | |
| 133 } | |
| 134 } | |
| 135 | |
| 136 /** | |
| 137 * Dispatch the given event by finding all of the tokens that contain the | |
| 138 * resource and invoke the [handleWatchEvent] function. | |
| 139 */ | |
| 140 void _handleWatchEvent(WatchEvent event) { | |
| 141 String path = event.path; | |
| 142 List<T> tokens = <T>[]; | |
| 143 // 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.
| |
| 144 // Folder folder = node.folder; | |
| 145 // if (folder.contains(path)) { | |
| 146 // tokens.addAll(_watchedFolders[folder].tokens); | |
| 147 // } | |
| 148 // node.forEachChild(visitNode); | |
| 149 // } | |
| 150 // visitNode(rootFolder.findParent(path)); | |
| 151 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
| |
| 152 while (parent != rootNode) { | |
| 153 tokens.addAll(parent.tokens); | |
| 154 parent = parent.parent; | |
| 155 } | |
| 156 if (tokens.isNotEmpty) { | |
| 157 handleWatchEvent(event, tokens); | |
| 158 } | |
| 159 } | |
| 160 } | |
| 161 | |
| 162 /** | |
| 163 * The information kept by a [WatchManager] about a single folder that is being | |
| 164 * watched. | |
| 165 * | |
| 166 * Watch nodes form a tree in which one node is a child of another node if the | |
| 167 * child's folder is contained in the parent's folder and none of the folders | |
| 168 * between the parent's folder and the child's folder are being watched. | |
| 169 */ | |
| 170 class WatchNode<T> { | |
| 171 /** | |
| 172 * An empty list of watch nodes. | |
| 173 */ | |
| 174 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
| |
| 175 | |
| 176 /** | |
| 177 * The folder for which information is being maintained. This is `null` for | |
| 178 * the unique "root" node that maintains references to all of the top-level | |
| 179 * folders being watched. | |
| 180 */ | |
| 181 Folder folder; | |
|
Paul Berry
2015/07/19 18:38:33
Can this be final?
Brian Wilkerson
2015/07/19 21:24:20
Done
| |
| 182 | |
| 183 /** | |
| 184 * The parent of this node. | |
| 185 */ | |
| 186 WatchNode parent; | |
| 187 | |
| 188 /** | |
| 189 * The information for the children of this node. | |
| 190 */ | |
| 191 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
| |
| 192 | |
| 193 /** | |
| 194 * The tokens that were used to register interest in watching this folder. | |
| 195 */ | |
| 196 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
| |
| 197 | |
| 198 /** | |
| 199 * The subscription being used to watch the folder, or `null` if the folder | |
| 200 * is being watched as part of a containing folder (in other words, if the | |
| 201 * parent is not the special "root"). | |
| 202 */ | |
| 203 StreamSubscription<WatchEvent> subscription; | |
| 204 | |
| 205 /** | |
| 206 * Initialize a newly created node to represent the given [folder]. | |
| 207 */ | |
| 208 WatchNode(this.folder); | |
| 209 | |
| 210 /** | |
| 211 * Return a list containing the children of this node. | |
| 212 */ | |
| 213 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
| |
| 214 | |
| 215 /** | |
| 216 * Remove this node from the tree of watched folders. | |
| 217 */ | |
| 218 void delete() { | |
| 219 if (parent != null) { | |
| 220 parent._removeChild(this); | |
| 221 } | |
|
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
| |
| 222 } | |
| 223 | |
| 224 /** | |
| 225 * Return the highest node reachable from this node that contains the given | |
| 226 * [filePath]. If no other node is found, return this node, even if this node | |
| 227 * does not contain the path. | |
| 228 */ | |
| 229 WatchNode findParent(String filePath) { | |
| 230 if (_children == null) { | |
| 231 return this; | |
| 232 } | |
| 233 for (WatchNode childNode in _children) { | |
| 234 if (childNode.folder.contains(filePath)) { | |
| 235 return childNode.findParent(filePath); | |
| 236 } | |
| 237 } | |
| 238 return this; | |
| 239 } | |
| 240 | |
| 241 @override | |
| 242 String toString() => 'WatchNode (' | |
| 243 'folder = ${folder == null ? '<root>' : folder.path}, ' | |
| 244 'tokens = $tokens, ' | |
| 245 'subscription = ${subscription == null ? 'null' : 'non-null'})'; | |
| 246 | |
| 247 /** | |
| 248 * Invoke the given [function] on each of the children of this node. | |
| 249 */ | |
| 250 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
| |
| 251 if (_children != null) { | |
| 252 _children.forEach(function); | |
| 253 } | |
| 254 } | |
| 255 | |
| 256 /** | |
| 257 * Insert the given [node] into the tree of watched folders, either as a child | |
| 258 * of this node or as a descendent of one of this node's children. Return the | |
| 259 * immediate parent of the newly added node. | |
| 260 */ | |
| 261 WatchNode insert(WatchNode node) { | |
| 262 WatchNode parentNode = findParent(node.folder.path); | |
| 263 parentNode._addChild(node, true); | |
| 264 return parentNode; | |
| 265 } | |
| 266 | |
| 267 /** | |
| 268 * Add the given [newChild] as an immediate child of this node. | |
| 269 * | |
| 270 * If [checkChildren] is `true`, check to see whether any of the previously | |
| 271 * existing children of this node should now be children of the new child, and | |
| 272 * if so, move them. | |
| 273 */ | |
| 274 void _addChild(WatchNode newChild, bool checkChildren) { | |
| 275 if (_children == null) { | |
| 276 _children = <WatchNode>[]; | |
| 277 } else if (checkChildren) { | |
| 278 Folder folder = newChild.folder; | |
| 279 for (int i = _children.length - 1; i >= 0; i--) { | |
| 280 WatchNode existingChild = _children[i]; | |
| 281 if (folder.contains(existingChild.folder.path)) { | |
| 282 newChild._addChild(existingChild, false); | |
| 283 _children.removeAt(i); | |
| 284 } | |
| 285 } | |
| 286 } | |
| 287 newChild.parent = this; | |
| 288 _children.add(newChild); | |
| 289 } | |
| 290 | |
| 291 /** | |
| 292 * Remove the given [node] from the list of children of this node. Any | |
| 293 * children of the [node] will become children of this node. | |
| 294 */ | |
| 295 void _removeChild(WatchNode child) { | |
| 296 _children.remove(child); | |
| 297 for (WatchNode grandchild in child.children) { | |
| 298 grandchild.parent = this; | |
| 299 _children.add(grandchild); | |
| 300 } | |
| 301 if (_children.isEmpty) { | |
| 302 _children = null; | |
| 303 } | |
| 304 } | |
|
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
| |
| 305 } | |
| OLD | NEW |