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

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: Address comments 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
« no previous file with comments | « no previous file | pkg/analysis_server/test/src/watch_manager_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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]. 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 }
OLDNEW
« no previous file with comments | « no previous file | pkg/analysis_server/test/src/watch_manager_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698