890c99238c
R=paulberry@google.com, scheglov@google.com Review URL: https://codereview.chromium.org//882813007 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@43355 260f80e4-7a28-3924-810f-c04153c831b5
386 lines
9.6 KiB
Dart
386 lines
9.6 KiB
Dart
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
library memory_file_system;
|
|
|
|
import 'dart:async';
|
|
import 'dart:collection';
|
|
|
|
import 'package:analyzer/src/generated/engine.dart' show TimestampedData;
|
|
import 'package:analyzer/src/generated/source_io.dart';
|
|
import 'package:path/path.dart';
|
|
import 'package:watcher/watcher.dart';
|
|
|
|
import 'file_system.dart';
|
|
|
|
|
|
/**
|
|
* Exception thrown when a memory [Resource] file operation fails.
|
|
*/
|
|
class MemoryResourceException {
|
|
final path;
|
|
final message;
|
|
|
|
MemoryResourceException(this.path, this.message);
|
|
|
|
@override
|
|
String toString() {
|
|
return "MemoryResourceException(path=$path; message=$message)";
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* An in-memory implementation of [ResourceProvider].
|
|
* Use `/` as a path separator.
|
|
*/
|
|
class MemoryResourceProvider implements ResourceProvider {
|
|
final Map<String, _MemoryResource> _pathToResource =
|
|
new HashMap<String, _MemoryResource>();
|
|
final Map<String, String> _pathToContent = new HashMap<String, String>();
|
|
final Map<String, int> _pathToTimestamp = new HashMap<String, int>();
|
|
final Map<String, List<StreamController<WatchEvent>>> _pathToWatchers =
|
|
new HashMap<String, List<StreamController<WatchEvent>>>();
|
|
int nextStamp = 0;
|
|
|
|
@override
|
|
Context get pathContext => posix;
|
|
|
|
void deleteFile(String path) {
|
|
_checkFileAtPath(path);
|
|
_pathToResource.remove(path);
|
|
_pathToContent.remove(path);
|
|
_pathToTimestamp.remove(path);
|
|
_notifyWatchers(path, ChangeType.REMOVE);
|
|
}
|
|
|
|
@override
|
|
Resource getResource(String path) {
|
|
path = posix.normalize(path);
|
|
Resource resource = _pathToResource[path];
|
|
if (resource == null) {
|
|
resource = new _MemoryFile(this, path);
|
|
}
|
|
return resource;
|
|
}
|
|
|
|
@override
|
|
Folder getStateLocation(String pluginId) {
|
|
return newFolder('/user/home/$pluginId');
|
|
}
|
|
|
|
void modifyFile(String path, String content) {
|
|
_checkFileAtPath(path);
|
|
_pathToContent[path] = content;
|
|
_pathToTimestamp[path] = nextStamp++;
|
|
_notifyWatchers(path, ChangeType.MODIFY);
|
|
}
|
|
|
|
/**
|
|
* Create a resource representing a dummy link (that is, a File object which
|
|
* appears in its parent directory, but whose `exists` property is false)
|
|
*/
|
|
File newDummyLink(String path) {
|
|
path = posix.normalize(path);
|
|
newFolder(posix.dirname(path));
|
|
_MemoryDummyLink link = new _MemoryDummyLink(this, path);
|
|
_pathToResource[path] = link;
|
|
_pathToTimestamp[path] = nextStamp++;
|
|
_notifyWatchers(path, ChangeType.ADD);
|
|
return link;
|
|
}
|
|
|
|
File newFile(String path, String content) {
|
|
path = posix.normalize(path);
|
|
newFolder(posix.dirname(path));
|
|
_MemoryFile file = new _MemoryFile(this, path);
|
|
_pathToResource[path] = file;
|
|
_pathToContent[path] = content;
|
|
_pathToTimestamp[path] = nextStamp++;
|
|
_notifyWatchers(path, ChangeType.ADD);
|
|
return file;
|
|
}
|
|
|
|
Folder newFolder(String path) {
|
|
path = posix.normalize(path);
|
|
if (!path.startsWith('/')) {
|
|
throw new ArgumentError("Path must start with '/'");
|
|
}
|
|
_MemoryResource resource = _pathToResource[path];
|
|
if (resource == null) {
|
|
String parentPath = posix.dirname(path);
|
|
if (parentPath != path) {
|
|
newFolder(parentPath);
|
|
}
|
|
_MemoryFolder folder = new _MemoryFolder(this, path);
|
|
_pathToResource[path] = folder;
|
|
_pathToTimestamp[path] = nextStamp++;
|
|
return folder;
|
|
} else if (resource is _MemoryFolder) {
|
|
return resource;
|
|
} else {
|
|
String message =
|
|
'Folder expected at ' "'$path'" 'but ${resource.runtimeType} found';
|
|
throw new ArgumentError(message);
|
|
}
|
|
}
|
|
|
|
void _checkFileAtPath(String path) {
|
|
_MemoryResource resource = _pathToResource[path];
|
|
if (resource is! _MemoryFile) {
|
|
throw new ArgumentError(
|
|
'File expected at "$path" but ${resource.runtimeType} found');
|
|
}
|
|
}
|
|
|
|
void _notifyWatchers(String path, ChangeType changeType) {
|
|
_pathToWatchers.forEach(
|
|
(String watcherPath, List<StreamController<WatchEvent>> streamControllers) {
|
|
if (posix.isWithin(watcherPath, path)) {
|
|
for (StreamController<WatchEvent> streamController in streamControllers)
|
|
{
|
|
streamController.add(new WatchEvent(changeType, path));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* An in-memory implementation of [File] which acts like a symbolic link to a
|
|
* non-existent file.
|
|
*/
|
|
class _MemoryDummyLink extends _MemoryResource implements File {
|
|
_MemoryDummyLink(MemoryResourceProvider provider, String path)
|
|
: super(provider, path);
|
|
|
|
@override
|
|
bool get exists => false;
|
|
|
|
String get _content {
|
|
throw new MemoryResourceException(path, "File '$path' could not be read");
|
|
}
|
|
|
|
int get _timestamp => _provider._pathToTimestamp[path];
|
|
|
|
@override
|
|
Source createSource([Uri uri]) {
|
|
throw new MemoryResourceException(path, "File '$path' could not be read");
|
|
}
|
|
|
|
@override
|
|
bool isOrContains(String path) {
|
|
return path == this.path;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* An in-memory implementation of [File].
|
|
*/
|
|
class _MemoryFile extends _MemoryResource implements File {
|
|
_MemoryFile(MemoryResourceProvider provider, String path)
|
|
: super(provider, path);
|
|
|
|
String get _content {
|
|
String content = _provider._pathToContent[path];
|
|
if (content == null) {
|
|
throw new MemoryResourceException(path, "File '$path' does not exist");
|
|
}
|
|
return content;
|
|
}
|
|
|
|
int get _timestamp => _provider._pathToTimestamp[path];
|
|
|
|
@override
|
|
Source createSource([Uri uri]) {
|
|
if (uri == null) {
|
|
uri = posix.toUri(path);
|
|
}
|
|
return new _MemoryFileSource(this, uri);
|
|
}
|
|
|
|
@override
|
|
bool isOrContains(String path) {
|
|
return path == this.path;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* An in-memory implementation of [Source].
|
|
*/
|
|
class _MemoryFileSource extends Source {
|
|
final _MemoryFile _file;
|
|
|
|
final Uri uri;
|
|
|
|
_MemoryFileSource(this._file, this.uri);
|
|
|
|
@override
|
|
TimestampedData<String> get contents {
|
|
return new TimestampedData<String>(modificationStamp, _file._content);
|
|
}
|
|
|
|
@override
|
|
String get encoding {
|
|
return uri.toString();
|
|
}
|
|
|
|
@override
|
|
String get fullName => _file.path;
|
|
|
|
@override
|
|
int get hashCode => _file.hashCode;
|
|
|
|
@override
|
|
bool get isInSystemLibrary => uriKind == UriKind.DART_URI;
|
|
|
|
@override
|
|
int get modificationStamp => _file._timestamp;
|
|
|
|
@override
|
|
String get shortName => _file.shortName;
|
|
|
|
@override
|
|
UriKind get uriKind {
|
|
String scheme = uri.scheme;
|
|
if (scheme == PackageUriResolver.PACKAGE_SCHEME) {
|
|
return UriKind.PACKAGE_URI;
|
|
} else if (scheme == DartUriResolver.DART_SCHEME) {
|
|
return UriKind.DART_URI;
|
|
} else if (scheme == FileUriResolver.FILE_SCHEME) {
|
|
return UriKind.FILE_URI;
|
|
}
|
|
return UriKind.FILE_URI;
|
|
}
|
|
|
|
@override
|
|
bool operator ==(other) {
|
|
if (other is _MemoryFileSource) {
|
|
return other._file == _file;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@override
|
|
bool exists() => _file.exists;
|
|
|
|
@override
|
|
Uri resolveRelativeUri(Uri relativeUri) {
|
|
return uri.resolveUri(relativeUri);
|
|
}
|
|
|
|
@override
|
|
String toString() => _file.toString();
|
|
}
|
|
|
|
|
|
/**
|
|
* An in-memory implementation of [Folder].
|
|
*/
|
|
class _MemoryFolder extends _MemoryResource implements Folder {
|
|
_MemoryFolder(MemoryResourceProvider provider, String path)
|
|
: super(provider, path);
|
|
@override
|
|
Stream<WatchEvent> get changes {
|
|
StreamController<WatchEvent> streamController =
|
|
new StreamController<WatchEvent>();
|
|
if (!_provider._pathToWatchers.containsKey(path)) {
|
|
_provider._pathToWatchers[path] = <StreamController<WatchEvent>>[];
|
|
}
|
|
_provider._pathToWatchers[path].add(streamController);
|
|
streamController.done.then((_) {
|
|
_provider._pathToWatchers[path].remove(streamController);
|
|
if (_provider._pathToWatchers[path].isEmpty) {
|
|
_provider._pathToWatchers.remove(path);
|
|
}
|
|
});
|
|
return streamController.stream;
|
|
}
|
|
|
|
@override
|
|
String canonicalizePath(String relPath) {
|
|
relPath = posix.normalize(relPath);
|
|
String childPath = posix.join(path, relPath);
|
|
childPath = posix.normalize(childPath);
|
|
return childPath;
|
|
}
|
|
|
|
@override
|
|
bool contains(String path) {
|
|
return posix.isWithin(this.path, path);
|
|
}
|
|
|
|
@override
|
|
Resource getChild(String relPath) {
|
|
String childPath = canonicalizePath(relPath);
|
|
_MemoryResource resource = _provider._pathToResource[childPath];
|
|
if (resource == null) {
|
|
resource = new _MemoryFile(_provider, childPath);
|
|
}
|
|
return resource;
|
|
}
|
|
|
|
@override
|
|
List<Resource> getChildren() {
|
|
List<Resource> children = <Resource>[];
|
|
_provider._pathToResource.forEach((resourcePath, resource) {
|
|
if (posix.dirname(resourcePath) == path) {
|
|
children.add(resource);
|
|
}
|
|
});
|
|
return children;
|
|
}
|
|
|
|
@override
|
|
bool isOrContains(String path) {
|
|
if (path == this.path) {
|
|
return true;
|
|
}
|
|
return contains(path);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* An in-memory implementation of [Resource].
|
|
*/
|
|
abstract class _MemoryResource implements Resource {
|
|
final MemoryResourceProvider _provider;
|
|
final String path;
|
|
|
|
_MemoryResource(this._provider, this.path);
|
|
|
|
@override
|
|
bool get exists => _provider._pathToResource.containsKey(path);
|
|
|
|
@override
|
|
get hashCode => path.hashCode;
|
|
|
|
@override
|
|
Folder get parent {
|
|
String parentPath = posix.dirname(path);
|
|
if (parentPath == path) {
|
|
return null;
|
|
}
|
|
return _provider.getResource(parentPath);
|
|
}
|
|
|
|
@override
|
|
String get shortName => posix.basename(path);
|
|
|
|
@override
|
|
bool operator ==(other) {
|
|
if (runtimeType != other.runtimeType) {
|
|
return false;
|
|
}
|
|
return path == other.path;
|
|
}
|
|
|
|
@override
|
|
String toString() => path;
|
|
}
|