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

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