| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, 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 memory_file_system; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:collection'; | |
| 9 import 'dart:core' hide Resource; | |
| 10 | |
| 11 import 'package:analyzer/src/generated/engine.dart' show TimestampedData; | |
| 12 import 'package:analyzer/src/generated/source_io.dart'; | |
| 13 import 'package:path/path.dart'; | |
| 14 import 'package:watcher/watcher.dart'; | |
| 15 | |
| 16 import 'file_system.dart'; | |
| 17 | |
| 18 /** | |
| 19 * An in-memory implementation of [ResourceProvider]. | |
| 20 * Use `/` as a path separator. | |
| 21 */ | |
| 22 class MemoryResourceProvider implements ResourceProvider { | |
| 23 final Map<String, _MemoryResource> _pathToResource = | |
| 24 new HashMap<String, _MemoryResource>(); | |
| 25 final Map<String, String> _pathToContent = new HashMap<String, String>(); | |
| 26 final Map<String, int> _pathToTimestamp = new HashMap<String, int>(); | |
| 27 final Map<String, List<StreamController<WatchEvent>>> _pathToWatchers = | |
| 28 new HashMap<String, List<StreamController<WatchEvent>>>(); | |
| 29 int nextStamp = 0; | |
| 30 | |
| 31 @override | |
| 32 Context get pathContext => posix; | |
| 33 | |
| 34 /** | |
| 35 * Delete the file with the given path. | |
| 36 */ | |
| 37 void deleteFile(String path) { | |
| 38 _checkFileAtPath(path); | |
| 39 _pathToResource.remove(path); | |
| 40 _pathToContent.remove(path); | |
| 41 _pathToTimestamp.remove(path); | |
| 42 _notifyWatchers(path, ChangeType.REMOVE); | |
| 43 } | |
| 44 | |
| 45 /** | |
| 46 * Delete the folder with the given path | |
| 47 * and recurively delete nested files and folders. | |
| 48 */ | |
| 49 void deleteFolder(String path) { | |
| 50 _checkFolderAtPath(path); | |
| 51 _MemoryFolder folder = _pathToResource[path]; | |
| 52 for (Resource child in folder.getChildren()) { | |
| 53 if (child is File) { | |
| 54 deleteFile(child.path); | |
| 55 } else if (child is Folder) { | |
| 56 deleteFolder(child.path); | |
| 57 } else { | |
| 58 throw 'failed to delete resource: $child'; | |
| 59 } | |
| 60 } | |
| 61 _pathToResource.remove(path); | |
| 62 _pathToContent.remove(path); | |
| 63 _pathToTimestamp.remove(path); | |
| 64 _notifyWatchers(path, ChangeType.REMOVE); | |
| 65 } | |
| 66 | |
| 67 @override | |
| 68 File getFile(String path) => new _MemoryFile(this, path); | |
| 69 | |
| 70 @override | |
| 71 Folder getFolder(String path) => newFolder(path); | |
| 72 | |
| 73 @override | |
| 74 Resource getResource(String path) { | |
| 75 path = posix.normalize(path); | |
| 76 Resource resource = _pathToResource[path]; | |
| 77 if (resource == null) { | |
| 78 resource = new _MemoryFile(this, path); | |
| 79 } | |
| 80 return resource; | |
| 81 } | |
| 82 | |
| 83 @override | |
| 84 Folder getStateLocation(String pluginId) { | |
| 85 return newFolder('/user/home/$pluginId'); | |
| 86 } | |
| 87 | |
| 88 void modifyFile(String path, String content) { | |
| 89 _checkFileAtPath(path); | |
| 90 _pathToContent[path] = content; | |
| 91 _pathToTimestamp[path] = nextStamp++; | |
| 92 _notifyWatchers(path, ChangeType.MODIFY); | |
| 93 } | |
| 94 | |
| 95 /** | |
| 96 * Create a resource representing a dummy link (that is, a File object which | |
| 97 * appears in its parent directory, but whose `exists` property is false) | |
| 98 */ | |
| 99 File newDummyLink(String path) { | |
| 100 path = posix.normalize(path); | |
| 101 newFolder(posix.dirname(path)); | |
| 102 _MemoryDummyLink link = new _MemoryDummyLink(this, path); | |
| 103 _pathToResource[path] = link; | |
| 104 _pathToTimestamp[path] = nextStamp++; | |
| 105 _notifyWatchers(path, ChangeType.ADD); | |
| 106 return link; | |
| 107 } | |
| 108 | |
| 109 File newFile(String path, String content, [int stamp]) { | |
| 110 path = posix.normalize(path); | |
| 111 newFolder(posix.dirname(path)); | |
| 112 _MemoryFile file = new _MemoryFile(this, path); | |
| 113 _pathToResource[path] = file; | |
| 114 _pathToContent[path] = content; | |
| 115 _pathToTimestamp[path] = stamp != null ? stamp : nextStamp++; | |
| 116 _notifyWatchers(path, ChangeType.ADD); | |
| 117 return file; | |
| 118 } | |
| 119 | |
| 120 Folder newFolder(String path) { | |
| 121 path = posix.normalize(path); | |
| 122 if (!path.startsWith('/')) { | |
| 123 throw new ArgumentError("Path must start with '/'"); | |
| 124 } | |
| 125 _MemoryResource resource = _pathToResource[path]; | |
| 126 if (resource == null) { | |
| 127 String parentPath = posix.dirname(path); | |
| 128 if (parentPath != path) { | |
| 129 newFolder(parentPath); | |
| 130 } | |
| 131 _MemoryFolder folder = new _MemoryFolder(this, path); | |
| 132 _pathToResource[path] = folder; | |
| 133 _pathToTimestamp[path] = nextStamp++; | |
| 134 return folder; | |
| 135 } else if (resource is _MemoryFolder) { | |
| 136 return resource; | |
| 137 } else { | |
| 138 String message = | |
| 139 'Folder expected at ' "'$path'" 'but ${resource.runtimeType} found'; | |
| 140 throw new ArgumentError(message); | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 File updateFile(String path, String content, [int stamp]) { | |
| 145 path = posix.normalize(path); | |
| 146 newFolder(posix.dirname(path)); | |
| 147 _MemoryFile file = new _MemoryFile(this, path); | |
| 148 _pathToResource[path] = file; | |
| 149 _pathToContent[path] = content; | |
| 150 _pathToTimestamp[path] = stamp != null ? stamp : nextStamp++; | |
| 151 _notifyWatchers(path, ChangeType.MODIFY); | |
| 152 return file; | |
| 153 } | |
| 154 | |
| 155 void _checkFileAtPath(String path) { | |
| 156 _MemoryResource resource = _pathToResource[path]; | |
| 157 if (resource is! _MemoryFile) { | |
| 158 throw new ArgumentError( | |
| 159 'File expected at "$path" but ${resource.runtimeType} found'); | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 void _checkFolderAtPath(String path) { | |
| 164 _MemoryResource resource = _pathToResource[path]; | |
| 165 if (resource is! _MemoryFolder) { | |
| 166 throw new ArgumentError( | |
| 167 'Folder expected at "$path" but ${resource.runtimeType} found'); | |
| 168 } | |
| 169 } | |
| 170 | |
| 171 void _notifyWatchers(String path, ChangeType changeType) { | |
| 172 _pathToWatchers.forEach((String watcherPath, | |
| 173 List<StreamController<WatchEvent>> streamControllers) { | |
| 174 if (watcherPath == path || posix.isWithin(watcherPath, path)) { | |
| 175 for (StreamController<WatchEvent> streamController | |
| 176 in streamControllers) { | |
| 177 streamController.add(new WatchEvent(changeType, path)); | |
| 178 } | |
| 179 } | |
| 180 }); | |
| 181 } | |
| 182 } | |
| 183 | |
| 184 /** | |
| 185 * An in-memory implementation of [File] which acts like a symbolic link to a | |
| 186 * non-existent file. | |
| 187 */ | |
| 188 class _MemoryDummyLink extends _MemoryResource implements File { | |
| 189 _MemoryDummyLink(MemoryResourceProvider provider, String path) | |
| 190 : super(provider, path); | |
| 191 | |
| 192 @override | |
| 193 Stream<WatchEvent> get changes { | |
| 194 throw new FileSystemException(path, "File does not exist"); | |
| 195 } | |
| 196 | |
| 197 @override | |
| 198 bool get exists => false; | |
| 199 | |
| 200 int get modificationStamp { | |
| 201 int stamp = _provider._pathToTimestamp[path]; | |
| 202 if (stamp == null) { | |
| 203 throw new FileSystemException(path, "File does not exist"); | |
| 204 } | |
| 205 return stamp; | |
| 206 } | |
| 207 | |
| 208 String get _content { | |
| 209 throw new FileSystemException(path, 'File could not be read'); | |
| 210 } | |
| 211 | |
| 212 @override | |
| 213 Source createSource([Uri uri]) { | |
| 214 throw new FileSystemException(path, 'File could not be read'); | |
| 215 } | |
| 216 | |
| 217 @override | |
| 218 bool isOrContains(String path) { | |
| 219 return path == this.path; | |
| 220 } | |
| 221 | |
| 222 @override | |
| 223 String readAsStringSync() { | |
| 224 throw new FileSystemException(path, 'File could not be read'); | |
| 225 } | |
| 226 } | |
| 227 | |
| 228 /** | |
| 229 * An in-memory implementation of [File]. | |
| 230 */ | |
| 231 class _MemoryFile extends _MemoryResource implements File { | |
| 232 _MemoryFile(MemoryResourceProvider provider, String path) | |
| 233 : super(provider, path); | |
| 234 | |
| 235 @override | |
| 236 bool get exists => _provider._pathToResource[path] is _MemoryFile; | |
| 237 | |
| 238 int get modificationStamp { | |
| 239 int stamp = _provider._pathToTimestamp[path]; | |
| 240 if (stamp == null) { | |
| 241 throw new FileSystemException(path, 'File "$path" does not exist.'); | |
| 242 } | |
| 243 return stamp; | |
| 244 } | |
| 245 | |
| 246 String get _content { | |
| 247 String content = _provider._pathToContent[path]; | |
| 248 if (content == null) { | |
| 249 throw new FileSystemException(path, 'File "$path" does not exist.'); | |
| 250 } | |
| 251 return content; | |
| 252 } | |
| 253 | |
| 254 @override | |
| 255 Source createSource([Uri uri]) { | |
| 256 if (uri == null) { | |
| 257 uri = posix.toUri(path); | |
| 258 } | |
| 259 return new _MemoryFileSource(this, uri); | |
| 260 } | |
| 261 | |
| 262 @override | |
| 263 bool isOrContains(String path) { | |
| 264 return path == this.path; | |
| 265 } | |
| 266 | |
| 267 @override | |
| 268 String readAsStringSync() { | |
| 269 String content = _provider._pathToContent[path]; | |
| 270 if (content == null) { | |
| 271 throw new FileSystemException(path, 'File "$path" does not exist.'); | |
| 272 } | |
| 273 return content; | |
| 274 } | |
| 275 } | |
| 276 | |
| 277 /** | |
| 278 * An in-memory implementation of [Source]. | |
| 279 */ | |
| 280 class _MemoryFileSource extends Source { | |
| 281 /** | |
| 282 * Map from encoded URI/filepath pair to a unique integer identifier. This | |
| 283 * identifier is used for equality tests and hash codes. | |
| 284 * | |
| 285 * The URI and filepath are joined into a pair by separating them with an '@' | |
| 286 * character. | |
| 287 */ | |
| 288 static final Map<String, int> _idTable = new HashMap<String, int>(); | |
| 289 | |
| 290 final _MemoryFile file; | |
| 291 | |
| 292 final Uri uri; | |
| 293 | |
| 294 /** | |
| 295 * The unique ID associated with this [_MemoryFileSource]. | |
| 296 */ | |
| 297 final int id; | |
| 298 | |
| 299 _MemoryFileSource(_MemoryFile file, Uri uri) | |
| 300 : uri = uri, | |
| 301 file = file, | |
| 302 id = _idTable.putIfAbsent('$uri@${file.path}', () => _idTable.length); | |
| 303 | |
| 304 @override | |
| 305 TimestampedData<String> get contents { | |
| 306 return new TimestampedData<String>(modificationStamp, file._content); | |
| 307 } | |
| 308 | |
| 309 @override | |
| 310 String get encoding { | |
| 311 return uri.toString(); | |
| 312 } | |
| 313 | |
| 314 @override | |
| 315 String get fullName => file.path; | |
| 316 | |
| 317 @override | |
| 318 int get hashCode => id; | |
| 319 | |
| 320 @override | |
| 321 bool get isInSystemLibrary => uriKind == UriKind.DART_URI; | |
| 322 | |
| 323 @override | |
| 324 int get modificationStamp { | |
| 325 try { | |
| 326 return file.modificationStamp; | |
| 327 } on FileSystemException { | |
| 328 return -1; | |
| 329 } | |
| 330 } | |
| 331 | |
| 332 @override | |
| 333 String get shortName => file.shortName; | |
| 334 | |
| 335 @override | |
| 336 UriKind get uriKind { | |
| 337 String scheme = uri.scheme; | |
| 338 if (scheme == PackageUriResolver.PACKAGE_SCHEME) { | |
| 339 return UriKind.PACKAGE_URI; | |
| 340 } else if (scheme == DartUriResolver.DART_SCHEME) { | |
| 341 return UriKind.DART_URI; | |
| 342 } else if (scheme == FileUriResolver.FILE_SCHEME) { | |
| 343 return UriKind.FILE_URI; | |
| 344 } | |
| 345 return UriKind.FILE_URI; | |
| 346 } | |
| 347 | |
| 348 @override | |
| 349 bool operator ==(other) { | |
| 350 return other is _MemoryFileSource && other.id == id; | |
| 351 } | |
| 352 | |
| 353 @override | |
| 354 bool exists() => file.exists; | |
| 355 | |
| 356 @override | |
| 357 Uri resolveRelativeUri(Uri relativeUri) { | |
| 358 return uri.resolveUri(relativeUri); | |
| 359 } | |
| 360 | |
| 361 @override | |
| 362 String toString() => file.toString(); | |
| 363 } | |
| 364 | |
| 365 /** | |
| 366 * An in-memory implementation of [Folder]. | |
| 367 */ | |
| 368 class _MemoryFolder extends _MemoryResource implements Folder { | |
| 369 _MemoryFolder(MemoryResourceProvider provider, String path) | |
| 370 : super(provider, path); | |
| 371 | |
| 372 @override | |
| 373 bool get exists => _provider._pathToResource[path] is _MemoryFolder; | |
| 374 | |
| 375 @override | |
| 376 String canonicalizePath(String relPath) { | |
| 377 relPath = posix.normalize(relPath); | |
| 378 String childPath = posix.join(path, relPath); | |
| 379 childPath = posix.normalize(childPath); | |
| 380 return childPath; | |
| 381 } | |
| 382 | |
| 383 @override | |
| 384 bool contains(String path) { | |
| 385 return posix.isWithin(this.path, path); | |
| 386 } | |
| 387 | |
| 388 @override | |
| 389 Resource getChild(String relPath) { | |
| 390 String childPath = canonicalizePath(relPath); | |
| 391 _MemoryResource resource = _provider._pathToResource[childPath]; | |
| 392 if (resource == null) { | |
| 393 resource = new _MemoryFile(_provider, childPath); | |
| 394 } | |
| 395 return resource; | |
| 396 } | |
| 397 | |
| 398 @override | |
| 399 _MemoryFolder getChildAssumingFolder(String relPath) { | |
| 400 String childPath = canonicalizePath(relPath); | |
| 401 _MemoryResource resource = _provider._pathToResource[childPath]; | |
| 402 if (resource is _MemoryFolder) { | |
| 403 return resource; | |
| 404 } | |
| 405 return new _MemoryFolder(_provider, childPath); | |
| 406 } | |
| 407 | |
| 408 @override | |
| 409 List<Resource> getChildren() { | |
| 410 if (!exists) { | |
| 411 throw new FileSystemException(path, 'Folder does not exist.'); | |
| 412 } | |
| 413 List<Resource> children = <Resource>[]; | |
| 414 _provider._pathToResource.forEach((resourcePath, resource) { | |
| 415 if (posix.dirname(resourcePath) == path) { | |
| 416 children.add(resource); | |
| 417 } | |
| 418 }); | |
| 419 return children; | |
| 420 } | |
| 421 | |
| 422 @override | |
| 423 bool isOrContains(String path) { | |
| 424 if (path == this.path) { | |
| 425 return true; | |
| 426 } | |
| 427 return contains(path); | |
| 428 } | |
| 429 } | |
| 430 | |
| 431 /** | |
| 432 * An in-memory implementation of [Resource]. | |
| 433 */ | |
| 434 abstract class _MemoryResource implements Resource { | |
| 435 final MemoryResourceProvider _provider; | |
| 436 final String path; | |
| 437 | |
| 438 _MemoryResource(this._provider, this.path); | |
| 439 | |
| 440 Stream<WatchEvent> get changes { | |
| 441 StreamController<WatchEvent> streamController = | |
| 442 new StreamController<WatchEvent>(); | |
| 443 if (!_provider._pathToWatchers.containsKey(path)) { | |
| 444 _provider._pathToWatchers[path] = <StreamController<WatchEvent>>[]; | |
| 445 } | |
| 446 _provider._pathToWatchers[path].add(streamController); | |
| 447 streamController.done.then((_) { | |
| 448 _provider._pathToWatchers[path].remove(streamController); | |
| 449 if (_provider._pathToWatchers[path].isEmpty) { | |
| 450 _provider._pathToWatchers.remove(path); | |
| 451 } | |
| 452 }); | |
| 453 return streamController.stream; | |
| 454 } | |
| 455 | |
| 456 @override | |
| 457 get hashCode => path.hashCode; | |
| 458 | |
| 459 @override | |
| 460 Folder get parent { | |
| 461 String parentPath = posix.dirname(path); | |
| 462 if (parentPath == path) { | |
| 463 return null; | |
| 464 } | |
| 465 return _provider.getResource(parentPath); | |
| 466 } | |
| 467 | |
| 468 @override | |
| 469 String get shortName => posix.basename(path); | |
| 470 | |
| 471 @override | |
| 472 bool operator ==(other) { | |
| 473 if (runtimeType != other.runtimeType) { | |
| 474 return false; | |
| 475 } | |
| 476 return path == other.path; | |
| 477 } | |
| 478 | |
| 479 @override | |
| 480 String toString() => path; | |
| 481 } | |
| OLD | NEW |