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 |