OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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 resoure.resource; | |
6 | |
7 import "dart:async" show Future, Stream; | 5 import "dart:async" show Future, Stream; |
8 import "dart:convert" show Encoding; | 6 import "dart:convert" show Encoding; |
9 import "dart:isolate" show Isolate; | 7 import "dart:isolate" show Isolate; |
10 import "package_resolver.dart"; | 8 import "loader.dart"; |
11 import "io.dart" as io; // Loading strategy. TODO: Be configuration dependent. | |
12 | |
13 /// A strategy for resolving package URIs | |
14 abstract class PackageResolver { | |
15 /// Cache of the current resolver, accessible directly after it has first | |
16 /// been found asynchronously. | |
17 static PackageResolver _current; | |
18 | |
19 /// The package resolution strategy used by the current isolate. | |
20 static final Future<PackageResolver> current = _findIsolateResolution(); | |
21 | |
22 PackageResolver(); | |
23 | |
24 /// Creates a resolver using a map from package name to package location. | |
25 factory PackageResolver.fromMap(Map<String, Uri> packages) = MapResolver; | |
26 | |
27 /// Creates a resolver using a package root. | |
28 factory PackageResolver.fromRoot(Uri packageRoot) = RootResolver; | |
29 | |
30 /// Resolves a package URI to its location. | |
31 /// | |
32 /// If [uri] does not have `package` as scheme, it is returned again. | |
33 /// Otherwise the package name is looked up, and if found, a location | |
34 /// for the package file is returned. | |
35 Future<Uri> resolve(Uri uri); | |
36 | |
37 /// Returns a [Resource] for the [uri] as resolved by this resolver. | |
38 Resource resource(Uri uri) { | |
39 return new _UriResource(this, uri); | |
40 } | |
41 | |
42 /// Finds the way the current isolate resolves package URIs. | |
43 /// | |
44 /// Is only called once, and when it has been called, the [_current] | |
45 /// resolver is initialized, so [UriResource] will be initialized | |
46 /// with the resolver directly. | |
47 static Future<PackageResolver> _findIsolateResolution() async { | |
48 var pair = await Future.wait([Isolate.packageRoot, Isolate.packageMap]); | |
49 Uri root = pair[0]; | |
50 if (root != null) { | |
51 _current = new RootResolver(root); | |
52 } else { | |
53 Map<String, Uri> map = pair[1]; | |
54 _current = new MapResolver(map); | |
55 } | |
56 return _current; | |
57 } | |
58 } | |
59 | 9 |
60 /// A resource that can be read into the program. | 10 /// A resource that can be read into the program. |
61 /// | 11 /// |
62 /// A resource is data that can be located using a URI and read into | 12 /// A resource is data that can be located using a URI and read into |
63 /// the program at runtime. | 13 /// the program at runtime. |
64 /// The URI may use the `package` scheme to read resources provided | 14 /// The URI may use the `package` scheme to read resources provided |
65 /// along with package sources. | 15 /// along with package sources. |
66 abstract class Resource { | 16 abstract class Resource { |
67 /// Creates a resource object with the given [uri] as location. | 17 /// Creates a resource object with the given [uri] as location. |
68 /// | 18 /// |
69 /// The `uri` is a string containing a valid URI. | 19 /// The [uri] must be either a [Uri] or a string containing a valid URI. |
70 /// If the string is not a valid URI, using any of the functions on | 20 /// If the string is not a valid URI, using any of the functions on |
71 /// the resource object will fail. | 21 /// the resource object will fail. |
72 /// | 22 /// |
73 /// The URI may be relative, in which case it will be resolved | 23 /// The URI may be relative, in which case it will be resolved |
74 /// against [Uri.base] before being used. | 24 /// against [Uri.base] before being used. |
75 /// | 25 /// |
76 /// The URI may use the `package` scheme, which is always supported. | 26 /// The URI may use the `package` scheme, which is always supported. |
77 /// Other schemes may also be supported where possible. | 27 /// Other schemes may also be supported where possible. |
78 const factory Resource(String uri) = _StringResource; | 28 /// |
| 29 /// If [loader] is provided, it is used to load absolute non-package URIs. |
| 30 /// Package: URIs are resolved to a non-package URI before being loaded, so |
| 31 /// the loader doesn't have to support package: URIs, nor does it need to |
| 32 /// support relative URI references. |
| 33 /// If [loader] is omitted, a default implementation is used which supports |
| 34 /// as many of `http`, `https`, `file` and `data` as are available on the |
| 35 /// current platform. |
| 36 const factory Resource(uri, {ResourceLoader loader}) = _Resource; |
79 | 37 |
80 /// Creates a resource object with the given [uri] as location. | 38 /// The location URI of this resource. |
81 /// | |
82 /// The URI may be relative, in which case it will be resolved | |
83 /// against [Uri.base] before being used. | |
84 /// | |
85 /// The URI may use the `package` scheme, which is always supported. | |
86 /// Other schemes may also be supported where possible. | |
87 factory Resource.forUri(Uri uri) => | |
88 new _UriResource(PackageResolver._current, uri); | |
89 | |
90 /// The location `uri` of this resource. | |
91 /// | 39 /// |
92 /// This is a [Uri] of the `uri` parameter given to the constructor. | 40 /// This is a [Uri] of the `uri` parameter given to the constructor. |
93 /// If the parameter was not a valid URI, reading `uri` may fail. | 41 /// If the parameter was a string that did not contain a valid URI, |
| 42 /// reading `uri` will fail. |
94 Uri get uri; | 43 Uri get uri; |
95 | 44 |
| 45 /// Reads the resource content as a stream of bytes. |
96 Stream<List<int>> openRead(); | 46 Stream<List<int>> openRead(); |
97 | 47 |
| 48 /// Reads the resource content as a single list of bytes. |
98 Future<List<int>> readAsBytes(); | 49 Future<List<int>> readAsBytes(); |
99 | 50 |
100 /// Read the resource content as a string. | 51 /// Reads the resource content as a string. |
101 /// | 52 /// |
102 /// The content is decoded into a string using an [Encoding]. | 53 /// The content is decoded into a string using an [Encoding]. |
103 /// If no other encoding is provided, it defaults to UTF-8. | 54 /// If no other encoding is provided, it defaults to UTF-8. |
104 Future<String> readAsString({Encoding encoding}); | 55 Future<String> readAsString({Encoding encoding}); |
105 } | 56 } |
106 | 57 |
107 class _StringResource implements Resource { | 58 class _Resource implements Resource { |
108 final String _uri; | 59 /// Loading strategy for the resource. |
109 | 60 final ResourceLoader _loader; |
110 const _StringResource(String uri) : _uri = uri; | |
111 | |
112 Uri get uri => Uri.parse(_uri); | |
113 | |
114 Stream<List<int>> openRead() { | |
115 return new _UriResource(PackageResolver._current, uri).openRead(); | |
116 } | |
117 Future<List<int>> readAsBytes() { | |
118 return new _UriResource(PackageResolver._current, uri).readAsBytes(); | |
119 } | |
120 Future<String> readAsString({Encoding encoding}) { | |
121 return new _UriResource(PackageResolver._current, uri) | |
122 .readAsString(encoding: encoding); | |
123 } | |
124 } | |
125 | |
126 class _UriResource implements Resource { | |
127 /// The strategy for resolving package: URIs. | |
128 /// | |
129 /// May be null intially. If so, the [PackageResolver.current] resolver is | |
130 /// used (and cached for later use). | |
131 PackageResolver _resolver; | |
132 | 61 |
133 /// The URI of the resource. | 62 /// The URI of the resource. |
134 final Uri uri; | 63 final _uri; |
135 | 64 |
136 _UriResource(this.resolver, Uri uri); | 65 const _Resource(uri, {ResourceLoader loader}) |
| 66 : _uri = uri, _loader = (loader != null) ? loader : const DefaultLoader(); |
| 67 // TODO: Make this `loader ?? const DefaultLoader()` when ?? is const. |
| 68 |
| 69 Uri get uri => (_uri is String) ? Uri.parse(_uri) : (_uri as Uri); |
137 | 70 |
138 Stream<List<int>> openRead() async* { | 71 Stream<List<int>> openRead() async* { |
139 Uri uri = await _resolve(this.uri); | 72 Uri uri = await _resolvedUri; |
140 return io.readAsStream(uri); | 73 yield* _loader.openRead(uri); |
141 } | 74 } |
142 | 75 |
143 Future<List<int>> readAsBytes() async { | 76 Future<List<int>> readAsBytes() async { |
144 Uri uri = await _resolve(this.uri); | 77 Uri uri = await _resolvedUri; |
145 return io.readAsBytes(uri); | 78 return _loader.readAsBytes(uri); |
146 } | 79 } |
147 | 80 |
148 Future<String> readAsString({Encoding encoding}) async { | 81 Future<String> readAsString({Encoding encoding}) async { |
149 Uri uri = await _resolve(this.uri); | 82 Uri uri = await _resolvedUri; |
150 return io.readAsString(uri, encoding); | 83 return _loader.readAsString(uri, encoding: encoding); |
151 } | 84 } |
152 | 85 |
153 static void _checkPackageUri(Uri uri) { | 86 Future<Uri> get _resolvedUri => resolveUri(uri); |
154 if (uri.scheme != "package") { | |
155 throw new ArgumentError.value(uri, "Not a package: URI"); | |
156 } | |
157 if (uri.hasAuthority) { | |
158 throw new ArgumentError.value(uri, | |
159 "A package: URI must not have an authority part"); | |
160 } | |
161 if (uri.path.isEmpty || uri.path.startsWith('/')) { | |
162 throw new ArgumentError.value(uri, | |
163 "A package: URI must have the form " | |
164 "'package:packageName/packagePath'"); | |
165 } | |
166 if (uri.hasQuery) { | |
167 throw new ArgumentError.value(uri, | |
168 "A package: URI must not have a query part"); | |
169 } | |
170 if (uri.hasFragment) { | |
171 throw new ArgumentError.value(uri, | |
172 "A package: URI must not have a fragment part"); | |
173 } | |
174 } | |
175 | |
176 Future<Uri> _resolve(Uri uri) async { | |
177 if (uri.scheme != "package") { | |
178 return Uri.base.resolveUri(uri); | |
179 } | |
180 _checkPackageUri(uri); | |
181 _resolver ??= await PackageResolver._current; | |
182 return _resolver.resolve(uri); | |
183 } | |
184 } | 87 } |
185 | |
186 /// A [PackageResolver] based on a packags map. | |
187 class MapResolver extends PackageResolver { | |
188 Map<String, Uri> _mapping; | |
189 | |
190 MapResolver(this._mapping); | |
191 | |
192 Uri resolve(Uri uri) { | |
193 if (uri.scheme != "package") return uri; | |
194 var path = uri.path; | |
195 int slashIndex = path.indexOf('/'); | |
196 if (slashIndex <= 0) { | |
197 throw new ArgumentError.value(uri, "Invalid package URI"); | |
198 } | |
199 int packageName = path.substring(0, slashIndex); | |
200 var base = _mapping[packageName]; | |
201 if (base != null) { | |
202 int packagePath = path.substring(slashIndex + 1); | |
203 return base.resolveUri(new Uri(path: packagePath)); | |
204 } | |
205 throw new UnsupportedError("No package named '$packageName' found"); | |
206 } | |
207 } | |
208 | |
209 /// A [PackageResolver] based on a package root. | |
210 class RootResolver extends PackageResolver { | |
211 Uri _root; | |
212 RootResolver(this._root); | |
213 | |
214 Uri resolve(Uri uri) { | |
215 if (uri.scheme != "package") return uri; | |
216 return _root.resolve(uri.path); | |
217 } | |
218 } | |
OLD | NEW |