Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(368)

Side by Side Diff: pkg/analysis_server/lib/src/watch_manager.dart

Issue 1244613004: Implement support for watching directories containing implicitly analyzed files (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698