| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library resource; | 5 library memory_file_system; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 import 'dart:io' as io; | |
| 10 | 9 |
| 11 import 'package:analyzer/src/generated/engine.dart' show TimestampedData; | 10 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'; | 11 import 'package:analyzer/src/generated/source_io.dart'; |
| 14 import 'package:path/path.dart'; | 12 import 'package:path/path.dart'; |
| 15 import 'package:watcher/watcher.dart'; | 13 import 'package:watcher/watcher.dart'; |
| 16 | 14 |
| 17 | 15 import 'file_system.dart'; |
| 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 | 16 |
| 171 | 17 |
| 172 /** | 18 /** |
| 173 * Exception thrown when a memory [Resource] file operation fails. | 19 * Exception thrown when a memory [Resource] file operation fails. |
| 174 */ | 20 */ |
| 175 class MemoryResourceException { | 21 class MemoryResourceException { |
| 176 final path; | 22 final path; |
| 177 final message; | 23 final message; |
| 178 | 24 |
| 179 MemoryResourceException(this.path, this.message); | 25 MemoryResourceException(this.path, this.message); |
| 180 | 26 |
| 181 @override | 27 @override |
| 182 String toString() { | 28 String toString() { |
| 183 return "MemoryResourceException(path=$path; message=$message)"; | 29 return "MemoryResourceException(path=$path; message=$message)"; |
| 184 } | 30 } |
| 185 } | 31 } |
| 186 | 32 |
| 187 | 33 |
| 188 /** | 34 /** |
| 35 * An in-memory implementation of [ResourceProvider]. |
| 36 * Use `/` as a path separator. |
| 37 */ |
| 38 class MemoryResourceProvider implements ResourceProvider { |
| 39 final Map<String, _MemoryResource> _pathToResource = |
| 40 new HashMap<String, _MemoryResource>(); |
| 41 final Map<String, String> _pathToContent = new HashMap<String, String>(); |
| 42 final Map<String, int> _pathToTimestamp = new HashMap<String, int>(); |
| 43 final Map<String, List<StreamController<WatchEvent>>> _pathToWatchers = |
| 44 new HashMap<String, List<StreamController<WatchEvent>>>(); |
| 45 int nextStamp = 0; |
| 46 |
| 47 @override |
| 48 Context get pathContext => posix; |
| 49 |
| 50 void deleteFile(String path) { |
| 51 _checkFileAtPath(path); |
| 52 _pathToResource.remove(path); |
| 53 _pathToContent.remove(path); |
| 54 _pathToTimestamp.remove(path); |
| 55 _notifyWatchers(path, ChangeType.REMOVE); |
| 56 } |
| 57 |
| 58 @override |
| 59 Resource getResource(String path) { |
| 60 path = posix.normalize(path); |
| 61 Resource resource = _pathToResource[path]; |
| 62 if (resource == null) { |
| 63 resource = new _MemoryFile(this, path); |
| 64 } |
| 65 return resource; |
| 66 } |
| 67 |
| 68 void modifyFile(String path, String content) { |
| 69 _checkFileAtPath(path); |
| 70 _pathToContent[path] = content; |
| 71 _pathToTimestamp[path] = nextStamp++; |
| 72 _notifyWatchers(path, ChangeType.MODIFY); |
| 73 } |
| 74 |
| 75 /** |
| 76 * Create a resource representing a dummy link (that is, a File object which |
| 77 * appears in its parent directory, but whose `exists` property is false) |
| 78 */ |
| 79 File newDummyLink(String path) { |
| 80 path = posix.normalize(path); |
| 81 newFolder(posix.dirname(path)); |
| 82 _MemoryDummyLink link = new _MemoryDummyLink(this, path); |
| 83 _pathToResource[path] = link; |
| 84 _pathToTimestamp[path] = nextStamp++; |
| 85 _notifyWatchers(path, ChangeType.ADD); |
| 86 return link; |
| 87 } |
| 88 |
| 89 File newFile(String path, String content) { |
| 90 path = posix.normalize(path); |
| 91 newFolder(posix.dirname(path)); |
| 92 _MemoryFile file = new _MemoryFile(this, path); |
| 93 _pathToResource[path] = file; |
| 94 _pathToContent[path] = content; |
| 95 _pathToTimestamp[path] = nextStamp++; |
| 96 _notifyWatchers(path, ChangeType.ADD); |
| 97 return file; |
| 98 } |
| 99 |
| 100 Folder newFolder(String path) { |
| 101 path = posix.normalize(path); |
| 102 if (!path.startsWith('/')) { |
| 103 throw new ArgumentError("Path must start with '/'"); |
| 104 } |
| 105 _MemoryResource resource = _pathToResource[path]; |
| 106 if (resource == null) { |
| 107 String parentPath = posix.dirname(path); |
| 108 if (parentPath != path) { |
| 109 newFolder(parentPath); |
| 110 } |
| 111 _MemoryFolder folder = new _MemoryFolder(this, path); |
| 112 _pathToResource[path] = folder; |
| 113 _pathToTimestamp[path] = nextStamp++; |
| 114 return folder; |
| 115 } else if (resource is _MemoryFolder) { |
| 116 return resource; |
| 117 } else { |
| 118 String message = 'Folder expected at ' |
| 119 "'$path'" |
| 120 'but ${resource.runtimeType} found'; |
| 121 throw new ArgumentError(message); |
| 122 } |
| 123 } |
| 124 |
| 125 void _checkFileAtPath(String path) { |
| 126 _MemoryResource resource = _pathToResource[path]; |
| 127 if (resource is! _MemoryFile) { |
| 128 throw new ArgumentError( |
| 129 'File expected at "$path" but ${resource.runtimeType} found'); |
| 130 } |
| 131 } |
| 132 |
| 133 void _notifyWatchers(String path, ChangeType changeType) { |
| 134 _pathToWatchers.forEach((String watcherPath, List<StreamController<WatchEven
t>> streamControllers) { |
| 135 if (posix.isWithin(watcherPath, path)) { |
| 136 for (StreamController<WatchEvent> streamController in streamControllers)
{ |
| 137 streamController.add(new WatchEvent(changeType, path)); |
| 138 } |
| 139 } |
| 140 }); |
| 141 } |
| 142 } |
| 143 |
| 144 |
| 145 /** |
| 189 * An in-memory implementation of [File] which acts like a symbolic link to a | 146 * An in-memory implementation of [File] which acts like a symbolic link to a |
| 190 * non-existent file. | 147 * non-existent file. |
| 191 */ | 148 */ |
| 192 class _MemoryDummyLink extends _MemoryResource implements File { | 149 class _MemoryDummyLink extends _MemoryResource implements File { |
| 193 _MemoryDummyLink(MemoryResourceProvider provider, String path) : | 150 _MemoryDummyLink(MemoryResourceProvider provider, String path) : |
| 194 super(provider, path); | 151 super(provider, path); |
| 195 | 152 |
| 196 @override | 153 @override |
| 197 Source createSource(UriKind uriKind) { | 154 bool get exists => false; |
| 198 throw new MemoryResourceException(path, "File '$path' could not be read"); | |
| 199 } | |
| 200 | 155 |
| 201 String get _content { | 156 String get _content { |
| 202 throw new MemoryResourceException(path, "File '$path' could not be read"); | 157 throw new MemoryResourceException(path, "File '$path' could not be read"); |
| 203 } | 158 } |
| 204 | 159 |
| 205 int get _timestamp => _provider._pathToTimestamp[path]; | 160 int get _timestamp => _provider._pathToTimestamp[path]; |
| 206 | 161 |
| 207 @override | 162 @override |
| 208 bool get exists => false; | 163 Source createSource(UriKind uriKind) { |
| 164 throw new MemoryResourceException(path, "File '$path' could not be read"); |
| 165 } |
| 209 } | 166 } |
| 210 | 167 |
| 211 | 168 |
| 169 /** |
| 170 * An in-memory implementation of [File]. |
| 171 */ |
| 172 class _MemoryFile extends _MemoryResource implements File { |
| 173 _MemoryFile(MemoryResourceProvider provider, String path) : |
| 174 super(provider, path); |
| 175 |
| 176 String get _content { |
| 177 String content = _provider._pathToContent[path]; |
| 178 if (content == null) { |
| 179 throw new MemoryResourceException(path, "File '$path' does not exist"); |
| 180 } |
| 181 return content; |
| 182 } |
| 183 |
| 184 int get _timestamp => _provider._pathToTimestamp[path]; |
| 185 |
| 186 @override |
| 187 Source createSource(UriKind uriKind) { |
| 188 return new _MemoryFileSource(this, uriKind); |
| 189 } |
| 190 } |
| 191 |
| 192 |
| 212 /** | 193 /** |
| 213 * An in-memory implementation of [Source]. | 194 * An in-memory implementation of [Source]. |
| 214 */ | 195 */ |
| 215 class _MemoryFileSource implements Source { | 196 class _MemoryFileSource implements Source { |
| 216 final _MemoryFile _file; | 197 final _MemoryFile _file; |
| 217 | 198 |
| 218 final UriKind uriKind; | 199 final UriKind uriKind; |
| 219 | 200 |
| 220 _MemoryFileSource(this._file, this.uriKind); | 201 _MemoryFileSource(this._file, this.uriKind); |
| 221 | 202 |
| 222 @override | 203 @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 { | 204 TimestampedData<String> get contents { |
| 232 return new TimestampedData<String>(modificationStamp, _file._content); | 205 return new TimestampedData<String>(modificationStamp, _file._content); |
| 233 } | 206 } |
| 234 | 207 |
| 235 @override | 208 @override |
| 236 String get encoding { | 209 String get encoding { |
| 237 return '${new String.fromCharCode(uriKind.encoding)}${_file.path}'; | 210 return '${new String.fromCharCode(uriKind.encoding)}${_file.path}'; |
| 238 } | 211 } |
| 239 | 212 |
| 240 @override | 213 @override |
| 241 bool exists() => _file.exists; | |
| 242 | |
| 243 @override | |
| 244 String get fullName => _file.path; | 214 String get fullName => _file.path; |
| 245 | 215 |
| 246 @override | 216 @override |
| 247 int get hashCode => _file.hashCode; | 217 int get hashCode => _file.hashCode; |
| 248 | 218 |
| 249 @override | 219 @override |
| 250 bool get isInSystemLibrary => false; | 220 bool get isInSystemLibrary => false; |
| 251 | 221 |
| 252 @override | 222 @override |
| 253 int get modificationStamp => _file._timestamp; | 223 int get modificationStamp => _file._timestamp; |
| 254 | 224 |
| 255 @override | 225 @override |
| 226 String get shortName => _file.shortName; |
| 227 |
| 228 @override |
| 229 bool operator ==(other) { |
| 230 if (other is _MemoryFileSource) { |
| 231 return other._file == _file; |
| 232 } |
| 233 return false; |
| 234 } |
| 235 |
| 236 @override |
| 237 bool exists() => _file.exists; |
| 238 |
| 239 @override |
| 256 Source resolveRelative(Uri relativeUri) { | 240 Source resolveRelative(Uri relativeUri) { |
| 257 String relativePath = posix.fromUri(relativeUri); | 241 String relativePath = posix.fromUri(relativeUri); |
| 258 String folderPath = posix.dirname(_file.path); | 242 String folderPath = posix.dirname(_file.path); |
| 259 String path = posix.join(folderPath, relativePath); | 243 String path = posix.join(folderPath, relativePath); |
| 260 path = posix.normalize(path); | 244 path = posix.normalize(path); |
| 261 _MemoryFile file = new _MemoryFile(_file._provider, path); | 245 _MemoryFile file = new _MemoryFile(_file._provider, path); |
| 262 return new _MemoryFileSource(file, uriKind); | 246 return new _MemoryFileSource(file, uriKind); |
| 263 } | 247 } |
| 264 | |
| 265 @override | |
| 266 String get shortName => _file.shortName; | |
| 267 } | 248 } |
| 268 | 249 |
| 269 | 250 |
| 270 /** | 251 /** |
| 271 * An in-memory implementation of [Folder]. | 252 * An in-memory implementation of [Folder]. |
| 272 */ | 253 */ |
| 273 class _MemoryFolder extends _MemoryResource implements Folder { | 254 class _MemoryFolder extends _MemoryResource implements Folder { |
| 274 _MemoryFolder(MemoryResourceProvider provider, String path) : | 255 _MemoryFolder(MemoryResourceProvider provider, String path) : |
| 275 super(provider, path); | 256 super(provider, path); |
| 276 @override | 257 @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 { | 258 Stream<WatchEvent> get changes { |
| 299 StreamController<WatchEvent> streamController = new StreamController<WatchEv
ent>(); | 259 StreamController<WatchEvent> streamController = new StreamController<WatchEv
ent>(); |
| 300 if (!_provider._pathToWatchers.containsKey(path)) { | 260 if (!_provider._pathToWatchers.containsKey(path)) { |
| 301 _provider._pathToWatchers[path] = <StreamController<WatchEvent>>[]; | 261 _provider._pathToWatchers[path] = <StreamController<WatchEvent>>[]; |
| 302 } | 262 } |
| 303 _provider._pathToWatchers[path].add(streamController); | 263 _provider._pathToWatchers[path].add(streamController); |
| 304 streamController.done.then((_) { | 264 streamController.done.then((_) { |
| 305 _provider._pathToWatchers[path].remove(streamController); | 265 _provider._pathToWatchers[path].remove(streamController); |
| 306 if (_provider._pathToWatchers[path].isEmpty) { | 266 if (_provider._pathToWatchers[path].isEmpty) { |
| 307 _provider._pathToWatchers.remove(path); | 267 _provider._pathToWatchers.remove(path); |
| 308 } | 268 } |
| 309 }); | 269 }); |
| 310 return streamController.stream; | 270 return streamController.stream; |
| 311 } | 271 } |
| 312 | 272 |
| 313 @override | 273 @override |
| 314 String canonicalizePath(String relPath) { | 274 String canonicalizePath(String relPath) { |
| 315 relPath = posix.normalize(relPath); | 275 relPath = posix.normalize(relPath); |
| 316 String childPath = posix.join(path, relPath); | 276 String childPath = posix.join(path, relPath); |
| 317 childPath = posix.normalize(childPath); | 277 childPath = posix.normalize(childPath); |
| 318 return childPath; | 278 return childPath; |
| 319 } | 279 } |
| 280 |
| 281 @override |
| 282 Resource getChild(String relPath) { |
| 283 String childPath = canonicalizePath(relPath); |
| 284 _MemoryResource resource = _provider._pathToResource[childPath]; |
| 285 if (resource == null) { |
| 286 resource = new _MemoryFile(_provider, childPath); |
| 287 } |
| 288 return resource; |
| 289 } |
| 290 |
| 291 @override |
| 292 List<Resource> getChildren() { |
| 293 List<Resource> children = <Resource>[]; |
| 294 _provider._pathToResource.forEach((resourcePath, resource) { |
| 295 if (posix.dirname(resourcePath) == path) { |
| 296 children.add(resource); |
| 297 } |
| 298 }); |
| 299 return children; |
| 300 } |
| 320 } | 301 } |
| 321 | 302 |
| 322 | 303 |
| 323 /** | 304 /** |
| 324 * An in-memory implementation of [ResourceProvider]. | 305 * An in-memory implementation of [Resource]. |
| 325 * Use `/` as a path separator. | |
| 326 */ | 306 */ |
| 327 class MemoryResourceProvider implements ResourceProvider { | 307 abstract class _MemoryResource implements Resource { |
| 328 final Map<String, _MemoryResource> _pathToResource = | 308 final MemoryResourceProvider _provider; |
| 329 new HashMap<String, _MemoryResource>(); | 309 final String path; |
| 330 final Map<String, String> _pathToContent = new HashMap<String, String>(); | 310 |
| 331 final Map<String, int> _pathToTimestamp = new HashMap<String, int>(); | 311 _MemoryResource(this._provider, this.path); |
| 332 final Map<String, List<StreamController<WatchEvent>>> _pathToWatchers = | |
| 333 new HashMap<String, List<StreamController<WatchEvent>>>(); | |
| 334 int nextStamp = 0; | |
| 335 | 312 |
| 336 @override | 313 @override |
| 337 Resource getResource(String path) { | 314 bool get exists => _provider._pathToResource.containsKey(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 | 315 |
| 501 @override | 316 @override |
| 502 get hashCode => path.hashCode; | 317 get hashCode => path.hashCode; |
| 503 | 318 |
| 504 @override | 319 @override |
| 505 bool operator==(other) { | 320 Folder get parent { |
| 321 String parentPath = posix.dirname(path); |
| 322 if (parentPath == path) { |
| 323 return null; |
| 324 } |
| 325 return _provider.getResource(parentPath); |
| 326 } |
| 327 |
| 328 @override |
| 329 String get shortName => posix.basename(path); |
| 330 |
| 331 @override |
| 332 bool operator ==(other) { |
| 506 if (runtimeType != other.runtimeType) { | 333 if (runtimeType != other.runtimeType) { |
| 507 return false; | 334 return false; |
| 508 } | 335 } |
| 509 return path == other.path; | 336 return path == other.path; |
| 510 } | 337 } |
| 511 | 338 |
| 512 @override | 339 @override |
| 513 String get shortName => basename(path); | |
| 514 | |
| 515 @override | |
| 516 String toString() => path; | 340 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 } | 341 } |
| 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 |