| 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 resource; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:collection'; | |
| 9 import 'dart:io' as io; | |
| 10 | |
| 11 import 'package:analyzer/src/generated/engine.dart' show TimestampedData; | |
| 12 import 'package:analyzer/src/generated/java_io.dart'; | |
| 13 import 'package:analyzer/src/generated/source_io.dart'; | |
| 14 import 'package:path/path.dart'; | |
| 15 import 'package:watcher/watcher.dart'; | |
| 16 | |
| 17 | |
| 18 /** | |
| 19 * [File]s are leaf [Resource]s which contain data. | |
| 20 */ | |
| 21 abstract class File extends Resource { | |
| 22 /** | |
| 23 * Create a new [Source] instance that serves this file. | |
| 24 */ | |
| 25 Source createSource(UriKind uriKind); | |
| 26 } | |
| 27 | |
| 28 | |
| 29 /** | |
| 30 * [Folder]s are [Resource]s which may contain files and/or other folders. | |
| 31 */ | |
| 32 abstract class Folder extends Resource { | |
| 33 /** | |
| 34 * Return an existing child [Resource] with the given [relPath]. | |
| 35 * Return a not existing [File] if no such child exist. | |
| 36 */ | |
| 37 Resource getChild(String relPath); | |
| 38 | |
| 39 /** | |
| 40 * Return a list of existing direct children [Resource]s (folders and files) | |
| 41 * in this folder, in no particular order. | |
| 42 */ | |
| 43 List<Resource> getChildren(); | |
| 44 | |
| 45 /** | |
| 46 * Watch for changes to the files inside this folder (and in any nested | |
| 47 * folders, including folders reachable via links). | |
| 48 */ | |
| 49 Stream<WatchEvent> get changes; | |
| 50 | |
| 51 /** | |
| 52 * If the path [path] is a relative path, convert it to an absolute path | |
| 53 * by interpreting it relative to this folder. If it is already an aboslute | |
| 54 * path, then don't change it. | |
| 55 * | |
| 56 * However, regardless of whether [path] is relative or absolute, normalize | |
| 57 * it by removing path components of the form '.' or '..'. | |
| 58 */ | |
| 59 String canonicalizePath(String path); | |
| 60 } | |
| 61 | |
| 62 | |
| 63 /** | |
| 64 * The abstract class [Resource] is an abstraction of file or folder. | |
| 65 */ | |
| 66 abstract class Resource { | |
| 67 /** | |
| 68 * Return `true` if this resource exists. | |
| 69 */ | |
| 70 bool get exists; | |
| 71 | |
| 72 /** | |
| 73 * Return the full path to this resource. | |
| 74 */ | |
| 75 String get path; | |
| 76 | |
| 77 /** | |
| 78 * Return a short version of the name that can be displayed to the user to | |
| 79 * denote this resource. | |
| 80 */ | |
| 81 String get shortName; | |
| 82 | |
| 83 /** | |
| 84 * Return the [Folder] that contains this resource, or `null` if this resource | |
| 85 * is a root folder. | |
| 86 */ | |
| 87 Folder get parent; | |
| 88 } | |
| 89 | |
| 90 | |
| 91 /** | |
| 92 * Instances of the class [ResourceProvider] convert [String] paths into | |
| 93 * [Resource]s. | |
| 94 */ | |
| 95 abstract class ResourceProvider { | |
| 96 /** | |
| 97 * Return the [Resource] that corresponds to the given [path]. | |
| 98 */ | |
| 99 Resource getResource(String path); | |
| 100 | |
| 101 /** | |
| 102 * Get the path context used by this resource provider. | |
| 103 */ | |
| 104 Context get pathContext; | |
| 105 } | |
| 106 | |
| 107 | |
| 108 /** | |
| 109 * An in-memory implementation of [Resource]. | |
| 110 */ | |
| 111 abstract class _MemoryResource implements Resource { | |
| 112 final MemoryResourceProvider _provider; | |
| 113 final String path; | |
| 114 | |
| 115 _MemoryResource(this._provider, this.path); | |
| 116 | |
| 117 @override | |
| 118 bool operator ==(other) { | |
| 119 if (runtimeType != other.runtimeType) { | |
| 120 return false; | |
| 121 } | |
| 122 return path == other.path; | |
| 123 } | |
| 124 | |
| 125 @override | |
| 126 bool get exists => _provider._pathToResource.containsKey(path); | |
| 127 | |
| 128 @override | |
| 129 get hashCode => path.hashCode; | |
| 130 | |
| 131 @override | |
| 132 String get shortName => posix.basename(path); | |
| 133 | |
| 134 @override | |
| 135 String toString() => path; | |
| 136 | |
| 137 @override | |
| 138 Folder get parent { | |
| 139 String parentPath = posix.dirname(path); | |
| 140 if (parentPath == path) { | |
| 141 return null; | |
| 142 } | |
| 143 return _provider.getResource(parentPath); | |
| 144 } | |
| 145 } | |
| 146 | |
| 147 | |
| 148 /** | |
| 149 * An in-memory implementation of [File]. | |
| 150 */ | |
| 151 class _MemoryFile extends _MemoryResource implements File { | |
| 152 _MemoryFile(MemoryResourceProvider provider, String path) : | |
| 153 super(provider, path); | |
| 154 | |
| 155 @override | |
| 156 Source createSource(UriKind uriKind) { | |
| 157 return new _MemoryFileSource(this, uriKind); | |
| 158 } | |
| 159 | |
| 160 String get _content { | |
| 161 String content = _provider._pathToContent[path]; | |
| 162 if (content == null) { | |
| 163 throw new MemoryResourceException(path, "File '$path' does not exist"); | |
| 164 } | |
| 165 return content; | |
| 166 } | |
| 167 | |
| 168 int get _timestamp => _provider._pathToTimestamp[path]; | |
| 169 } | |
| 170 | |
| 171 | |
| 172 /** | |
| 173 * Exception thrown when a memory [Resource] file operation fails. | |
| 174 */ | |
| 175 class MemoryResourceException { | |
| 176 final path; | |
| 177 final message; | |
| 178 | |
| 179 MemoryResourceException(this.path, this.message); | |
| 180 | |
| 181 @override | |
| 182 String toString() { | |
| 183 return "MemoryResourceException(path=$path; message=$message)"; | |
| 184 } | |
| 185 } | |
| 186 | |
| 187 | |
| 188 /** | |
| 189 * An in-memory implementation of [File] which acts like a symbolic link to a | |
| 190 * non-existent file. | |
| 191 */ | |
| 192 class _MemoryDummyLink extends _MemoryResource implements File { | |
| 193 _MemoryDummyLink(MemoryResourceProvider provider, String path) : | |
| 194 super(provider, path); | |
| 195 | |
| 196 @override | |
| 197 Source createSource(UriKind uriKind) { | |
| 198 throw new MemoryResourceException(path, "File '$path' could not be read"); | |
| 199 } | |
| 200 | |
| 201 String get _content { | |
| 202 throw new MemoryResourceException(path, "File '$path' could not be read"); | |
| 203 } | |
| 204 | |
| 205 int get _timestamp => _provider._pathToTimestamp[path]; | |
| 206 | |
| 207 @override | |
| 208 bool get exists => false; | |
| 209 } | |
| 210 | |
| 211 | |
| 212 /** | |
| 213 * An in-memory implementation of [Source]. | |
| 214 */ | |
| 215 class _MemoryFileSource implements Source { | |
| 216 final _MemoryFile _file; | |
| 217 | |
| 218 final UriKind uriKind; | |
| 219 | |
| 220 _MemoryFileSource(this._file, this.uriKind); | |
| 221 | |
| 222 @override | |
| 223 bool operator ==(other) { | |
| 224 if (other is _MemoryFileSource) { | |
| 225 return other._file == _file; | |
| 226 } | |
| 227 return false; | |
| 228 } | |
| 229 | |
| 230 @override | |
| 231 TimestampedData<String> get contents { | |
| 232 return new TimestampedData<String>(modificationStamp, _file._content); | |
| 233 } | |
| 234 | |
| 235 @override | |
| 236 String get encoding { | |
| 237 return '${new String.fromCharCode(uriKind.encoding)}${_file.path}'; | |
| 238 } | |
| 239 | |
| 240 @override | |
| 241 bool exists() => _file.exists; | |
| 242 | |
| 243 @override | |
| 244 String get fullName => _file.path; | |
| 245 | |
| 246 @override | |
| 247 int get hashCode => _file.hashCode; | |
| 248 | |
| 249 @override | |
| 250 bool get isInSystemLibrary => false; | |
| 251 | |
| 252 @override | |
| 253 int get modificationStamp => _file._timestamp; | |
| 254 | |
| 255 @override | |
| 256 Source resolveRelative(Uri relativeUri) { | |
| 257 String relativePath = posix.fromUri(relativeUri); | |
| 258 String folderPath = posix.dirname(_file.path); | |
| 259 String path = posix.join(folderPath, relativePath); | |
| 260 path = posix.normalize(path); | |
| 261 _MemoryFile file = new _MemoryFile(_file._provider, path); | |
| 262 return new _MemoryFileSource(file, uriKind); | |
| 263 } | |
| 264 | |
| 265 @override | |
| 266 String get shortName => _file.shortName; | |
| 267 } | |
| 268 | |
| 269 | |
| 270 /** | |
| 271 * An in-memory implementation of [Folder]. | |
| 272 */ | |
| 273 class _MemoryFolder extends _MemoryResource implements Folder { | |
| 274 _MemoryFolder(MemoryResourceProvider provider, String path) : | |
| 275 super(provider, path); | |
| 276 @override | |
| 277 Resource getChild(String relPath) { | |
| 278 String childPath = canonicalizePath(relPath); | |
| 279 _MemoryResource resource = _provider._pathToResource[childPath]; | |
| 280 if (resource == null) { | |
| 281 resource = new _MemoryFile(_provider, childPath); | |
| 282 } | |
| 283 return resource; | |
| 284 } | |
| 285 | |
| 286 @override | |
| 287 List<Resource> getChildren() { | |
| 288 List<Resource> children = <Resource>[]; | |
| 289 _provider._pathToResource.forEach((resourcePath, resource) { | |
| 290 if (posix.dirname(resourcePath) == path) { | |
| 291 children.add(resource); | |
| 292 } | |
| 293 }); | |
| 294 return children; | |
| 295 } | |
| 296 | |
| 297 @override | |
| 298 Stream<WatchEvent> get changes { | |
| 299 StreamController<WatchEvent> streamController = new StreamController<WatchEv
ent>(); | |
| 300 if (!_provider._pathToWatchers.containsKey(path)) { | |
| 301 _provider._pathToWatchers[path] = <StreamController<WatchEvent>>[]; | |
| 302 } | |
| 303 _provider._pathToWatchers[path].add(streamController); | |
| 304 streamController.done.then((_) { | |
| 305 _provider._pathToWatchers[path].remove(streamController); | |
| 306 if (_provider._pathToWatchers[path].isEmpty) { | |
| 307 _provider._pathToWatchers.remove(path); | |
| 308 } | |
| 309 }); | |
| 310 return streamController.stream; | |
| 311 } | |
| 312 | |
| 313 @override | |
| 314 String canonicalizePath(String relPath) { | |
| 315 relPath = posix.normalize(relPath); | |
| 316 String childPath = posix.join(path, relPath); | |
| 317 childPath = posix.normalize(childPath); | |
| 318 return childPath; | |
| 319 } | |
| 320 } | |
| 321 | |
| 322 | |
| 323 /** | |
| 324 * An in-memory implementation of [ResourceProvider]. | |
| 325 * Use `/` as a path separator. | |
| 326 */ | |
| 327 class MemoryResourceProvider implements ResourceProvider { | |
| 328 final Map<String, _MemoryResource> _pathToResource = | |
| 329 new HashMap<String, _MemoryResource>(); | |
| 330 final Map<String, String> _pathToContent = new HashMap<String, String>(); | |
| 331 final Map<String, int> _pathToTimestamp = new HashMap<String, int>(); | |
| 332 final Map<String, List<StreamController<WatchEvent>>> _pathToWatchers = | |
| 333 new HashMap<String, List<StreamController<WatchEvent>>>(); | |
| 334 int nextStamp = 0; | |
| 335 | |
| 336 @override | |
| 337 Resource getResource(String path) { | |
| 338 path = posix.normalize(path); | |
| 339 Resource resource = _pathToResource[path]; | |
| 340 if (resource == null) { | |
| 341 resource = new _MemoryFile(this, path); | |
| 342 } | |
| 343 return resource; | |
| 344 } | |
| 345 | |
| 346 Folder newFolder(String path) { | |
| 347 path = posix.normalize(path); | |
| 348 if (!path.startsWith('/')) { | |
| 349 throw new ArgumentError("Path must start with '/'"); | |
| 350 } | |
| 351 _MemoryResource resource = _pathToResource[path]; | |
| 352 if (resource == null) { | |
| 353 String parentPath = posix.dirname(path); | |
| 354 if (parentPath != path) { | |
| 355 newFolder(parentPath); | |
| 356 } | |
| 357 _MemoryFolder folder = new _MemoryFolder(this, path); | |
| 358 _pathToResource[path] = folder; | |
| 359 _pathToTimestamp[path] = nextStamp++; | |
| 360 return folder; | |
| 361 } else if (resource is _MemoryFolder) { | |
| 362 return resource; | |
| 363 } else { | |
| 364 String message = 'Folder expected at ' | |
| 365 "'$path'" | |
| 366 'but ${resource.runtimeType} found'; | |
| 367 throw new ArgumentError(message); | |
| 368 } | |
| 369 } | |
| 370 | |
| 371 File newFile(String path, String content) { | |
| 372 path = posix.normalize(path); | |
| 373 newFolder(posix.dirname(path)); | |
| 374 _MemoryFile file = new _MemoryFile(this, path); | |
| 375 _pathToResource[path] = file; | |
| 376 _pathToContent[path] = content; | |
| 377 _pathToTimestamp[path] = nextStamp++; | |
| 378 _notifyWatchers(path, ChangeType.ADD); | |
| 379 return file; | |
| 380 } | |
| 381 | |
| 382 /** | |
| 383 * Create a resource representing a dummy link (that is, a File object which | |
| 384 * appears in its parent directory, but whose `exists` property is false) | |
| 385 */ | |
| 386 File newDummyLink(String path) { | |
| 387 path = posix.normalize(path); | |
| 388 newFolder(posix.dirname(path)); | |
| 389 _MemoryDummyLink link = new _MemoryDummyLink(this, path); | |
| 390 _pathToResource[path] = link; | |
| 391 _pathToTimestamp[path] = nextStamp++; | |
| 392 _notifyWatchers(path, ChangeType.ADD); | |
| 393 return link; | |
| 394 } | |
| 395 | |
| 396 void _notifyWatchers(String path, ChangeType changeType) { | |
| 397 _pathToWatchers.forEach((String watcherPath, List<StreamController<WatchEven
t>> streamControllers) { | |
| 398 if (posix.isWithin(watcherPath, path)) { | |
| 399 for (StreamController<WatchEvent> streamController in streamControllers)
{ | |
| 400 streamController.add(new WatchEvent(changeType, path)); | |
| 401 } | |
| 402 } | |
| 403 }); | |
| 404 } | |
| 405 | |
| 406 void modifyFile(String path, String content) { | |
| 407 _checkFileAtPath(path); | |
| 408 _pathToContent[path] = content; | |
| 409 _pathToTimestamp[path] = nextStamp++; | |
| 410 _notifyWatchers(path, ChangeType.MODIFY); | |
| 411 } | |
| 412 | |
| 413 void _checkFileAtPath(String path) { | |
| 414 _MemoryResource resource = _pathToResource[path]; | |
| 415 if (resource is! _MemoryFile) { | |
| 416 throw new ArgumentError( | |
| 417 'File expected at "$path" but ${resource.runtimeType} found'); | |
| 418 } | |
| 419 } | |
| 420 | |
| 421 void deleteFile(String path) { | |
| 422 _checkFileAtPath(path); | |
| 423 _pathToResource.remove(path); | |
| 424 _pathToContent.remove(path); | |
| 425 _pathToTimestamp.remove(path); | |
| 426 _notifyWatchers(path, ChangeType.REMOVE); | |
| 427 } | |
| 428 | |
| 429 @override | |
| 430 Context get pathContext => posix; | |
| 431 } | |
| 432 | |
| 433 | |
| 434 /** | |
| 435 * A `dart:io` based implementation of [File]. | |
| 436 */ | |
| 437 class _PhysicalFile extends _PhysicalResource implements File { | |
| 438 _PhysicalFile(io.File file) : super(file); | |
| 439 | |
| 440 @override | |
| 441 Source createSource(UriKind uriKind) { | |
| 442 io.File file = _entry as io.File; | |
| 443 JavaFile javaFile = new JavaFile(file.absolute.path); | |
| 444 return new FileBasedSource.con2(javaFile, uriKind); | |
| 445 } | |
| 446 } | |
| 447 | |
| 448 | |
| 449 /** | |
| 450 * A `dart:io` based implementation of [Folder]. | |
| 451 */ | |
| 452 class _PhysicalFolder extends _PhysicalResource implements Folder { | |
| 453 _PhysicalFolder(io.Directory directory) : super(directory); | |
| 454 | |
| 455 @override | |
| 456 Resource getChild(String relPath) { | |
| 457 return PhysicalResourceProvider.INSTANCE.getResource(canonicalizePath(relPat
h)); | |
| 458 } | |
| 459 | |
| 460 @override | |
| 461 List<Resource> getChildren() { | |
| 462 List<Resource> children = <Resource>[]; | |
| 463 io.Directory directory = _entry as io.Directory; | |
| 464 List<io.FileSystemEntity> entries = directory.listSync(recursive: false); | |
| 465 int numEntries = entries.length; | |
| 466 for (int i = 0; i < numEntries; i++) { | |
| 467 io.FileSystemEntity entity = entries[i]; | |
| 468 if (entity is io.Directory) { | |
| 469 children.add(new _PhysicalFolder(entity)); | |
| 470 } else if (entity is io.File) { | |
| 471 children.add(new _PhysicalFile(entity)); | |
| 472 } | |
| 473 } | |
| 474 return children; | |
| 475 } | |
| 476 | |
| 477 @override | |
| 478 Stream<WatchEvent> get changes => new DirectoryWatcher(_entry.path).events; | |
| 479 | |
| 480 @override | |
| 481 String canonicalizePath(String relPath) { | |
| 482 return normalize(join(_entry.absolute.path, relPath)); | |
| 483 } | |
| 484 } | |
| 485 | |
| 486 | |
| 487 /** | |
| 488 * A `dart:io` based implementation of [Resource]. | |
| 489 */ | |
| 490 abstract class _PhysicalResource implements Resource { | |
| 491 final io.FileSystemEntity _entry; | |
| 492 | |
| 493 _PhysicalResource(this._entry); | |
| 494 | |
| 495 @override | |
| 496 bool get exists => _entry.existsSync(); | |
| 497 | |
| 498 @override | |
| 499 String get path => _entry.absolute.path; | |
| 500 | |
| 501 @override | |
| 502 get hashCode => path.hashCode; | |
| 503 | |
| 504 @override | |
| 505 bool operator==(other) { | |
| 506 if (runtimeType != other.runtimeType) { | |
| 507 return false; | |
| 508 } | |
| 509 return path == other.path; | |
| 510 } | |
| 511 | |
| 512 @override | |
| 513 String get shortName => basename(path); | |
| 514 | |
| 515 @override | |
| 516 String toString() => path; | |
| 517 | |
| 518 @override | |
| 519 Folder get parent { | |
| 520 String parentPath = dirname(path); | |
| 521 if (parentPath == path) { | |
| 522 return null; | |
| 523 } | |
| 524 return new _PhysicalFolder(new io.Directory(parentPath)); | |
| 525 } | |
| 526 } | |
| 527 | |
| 528 | |
| 529 /** | |
| 530 * A `dart:io` based implementation of [ResourceProvider]. | |
| 531 */ | |
| 532 class PhysicalResourceProvider implements ResourceProvider { | |
| 533 static final PhysicalResourceProvider INSTANCE = new PhysicalResourceProvider.
_(); | |
| 534 | |
| 535 PhysicalResourceProvider._(); | |
| 536 | |
| 537 @override | |
| 538 Resource getResource(String path) { | |
| 539 if (io.FileSystemEntity.isDirectorySync(path)) { | |
| 540 io.Directory directory = new io.Directory(path); | |
| 541 return new _PhysicalFolder(directory); | |
| 542 } else { | |
| 543 io.File file = new io.File(path); | |
| 544 return new _PhysicalFile(file); | |
| 545 } | |
| 546 } | |
| 547 | |
| 548 @override | |
| 549 Context get pathContext => io.Platform.isWindows ? windows : posix; | |
| 550 } | |
| 551 | |
| 552 | |
| 553 /** | |
| 554 * A [UriResolver] for [Resource]s. | |
| 555 */ | |
| 556 class ResourceUriResolver extends UriResolver { | |
| 557 /** | |
| 558 * The name of the `file` scheme. | |
| 559 */ | |
| 560 static String _FILE_SCHEME = "file"; | |
| 561 | |
| 562 final ResourceProvider _provider; | |
| 563 | |
| 564 ResourceUriResolver(this._provider); | |
| 565 | |
| 566 @override | |
| 567 Source fromEncoding(UriKind kind, Uri uri) { | |
| 568 if (kind == UriKind.FILE_URI) { | |
| 569 Resource resource = _provider.getResource(uri.path); | |
| 570 if (resource is File) { | |
| 571 return resource.createSource(kind); | |
| 572 } | |
| 573 } | |
| 574 return null; | |
| 575 } | |
| 576 | |
| 577 @override | |
| 578 Source resolveAbsolute(Uri uri) { | |
| 579 if (!_isFileUri(uri)) { | |
| 580 return null; | |
| 581 } | |
| 582 Resource resource = _provider.getResource(uri.path); | |
| 583 if (resource is File) { | |
| 584 return resource.createSource(UriKind.FILE_URI); | |
| 585 } | |
| 586 return null; | |
| 587 } | |
| 588 | |
| 589 /** | |
| 590 * Return `true` if the given URI is a `file` URI. | |
| 591 * | |
| 592 * @param uri the URI being tested | |
| 593 * @return `true` if the given URI is a `file` URI | |
| 594 */ | |
| 595 static bool _isFileUri(Uri uri) => uri.scheme == _FILE_SCHEME; | |
| 596 } | |
| OLD | NEW |