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 |