Index: sdk/lib/_internal/pub/lib/src/source/hosted.dart |
diff --git a/sdk/lib/_internal/pub/lib/src/source/hosted.dart b/sdk/lib/_internal/pub/lib/src/source/hosted.dart |
index 781ab0c9084fa47aa168413070edbb606cd75a9a..470f28ad090dbfb9054fcecf91af1dfc387bbeae 100644 |
--- a/sdk/lib/_internal/pub/lib/src/source/hosted.dart |
+++ b/sdk/lib/_internal/pub/lib/src/source/hosted.dart |
@@ -78,28 +78,8 @@ class HostedSource extends Source { |
/// Downloads a package from the site and unpacks it. |
Future<bool> get(PackageId id, String destPath) { |
- return syncFuture(() { |
- var url = _makeVersionUrl(id, (server, package, version) => |
- "$server/packages/$package/versions/$version.tar.gz"); |
- log.io("Get package from $url."); |
- |
- log.message('Downloading ${id.name} ${id.version}...'); |
- |
- // Download and extract the archive to a temp directory. |
- var tempDir = systemCache.createTempDir(); |
- return httpClient.send(new http.Request("GET", url)) |
- .then((response) => response.stream) |
- .then((stream) { |
- return timeout(extractTarGz(stream, tempDir), HTTP_TIMEOUT, |
- 'fetching URL "$url"'); |
- }).then((_) { |
- // Now that the get has succeeded, move it to the real location in the |
- // cache. This ensures that we don't leave half-busted ghost |
- // directories in the user's pub cache if a get fails. |
- renameDir(tempDir, destPath); |
- return true; |
- }); |
- }); |
+ var parsed = _parseDescription(id.description); |
+ return _download(parsed.last, parsed.first, id.version, destPath); |
} |
/// The system cache directory for the hosted source contains subdirectories |
@@ -108,7 +88,7 @@ class HostedSource extends Source { |
/// from that site. |
Future<String> systemCacheDirectory(PackageId id) { |
var parsed = _parseDescription(id.description); |
- var dir = _getSourceDirectory(parsed.last); |
+ var dir = _urlToDirectory(parsed.last); |
return new Future.value( |
path.join(systemCacheRoot, dir, "${parsed.first}-${id.version}")); |
@@ -130,25 +110,77 @@ class HostedSource extends Source { |
return description; |
} |
- List<Package> getCachedPackages([String url]) { |
- if (url == null) url = defaultUrl; |
+ /// Re-downloads all packages that have been previously downloaded into the |
+ /// system cache from any server. |
+ Future<Pair<int, int>> repairCachedPackages() { |
+ if (!dirExists(systemCacheRoot)) return new Future.value(new Pair(0, 0)); |
+ |
+ var successes = 0; |
+ var failures = 0; |
+ |
+ return Future.wait(listDir(systemCacheRoot).map((serverDir) { |
+ var url = _directoryToUrl(path.basename(serverDir)); |
+ var packages = _getCachedPackagesInDirectory(path.basename(serverDir)); |
+ packages.sort(Package.orderByNameAndVersion); |
+ return Future.wait(packages.map((package) { |
+ return _download(url, package.name, package.version, package.dir).then((_) { |
nweiz
2014/04/15 00:59:26
Long line.
Bob Nystrom
2014/04/16 17:07:44
Done.
|
+ successes++; |
+ }).catchError((error, stackTrace) { |
+ failures++; |
+ log.error("Failed to repair ${log.bold(package.name)} " |
+ "${package.version} from $url. Error:\n$error"); |
nweiz
2014/04/15 00:59:26
I'd omit "from $url" if it's pub.dartlang.org.
Bob Nystrom
2014/04/16 17:07:44
Done.
|
+ log.fine(stackTrace); |
+ }); |
+ })); |
+ })).then((_) => new Pair(successes, failures)); |
+ } |
- var cacheDir = path.join(systemCacheRoot, |
- _getSourceDirectory(url)); |
+ /// Gets all of the packages that have been downloaded into the system cache |
+ /// from the default server. |
+ List<Package> getCachedPackages() { |
+ return _getCachedPackagesInDirectory(_urlToDirectory(defaultUrl)); |
+ } |
+ |
+ /// Gets all of the packages that have been downloaded into the system cache |
+ /// into [dir]. |
+ List<Package> _getCachedPackagesInDirectory(String dir) { |
+ var cacheDir = path.join(systemCacheRoot, dir); |
if (!dirExists(cacheDir)) return []; |
- return listDir(path.join(cacheDir)).map((entry) { |
- // TODO(keertip): instead of catching exception in pubspec parse with |
- // sdk dependency, fix to parse and report usage of sdk dependency. |
- // dartbug.com/10190 |
- try { |
- return new Package.load(null, entry, systemCache.sources); |
- } on ArgumentError catch (e) { |
- log.error(e); |
- } |
- }).where((package) => package != null).toList(); |
+ return listDir(cacheDir) |
+ .map((entry) => new Package.load(null, entry, systemCache.sources)) |
+ .toList(); |
} |
+ /// Downloads package [package] at [version] from [server], and unpacks it |
+ /// into [destPath]. |
+ Future<bool> _download(String server, String package, Version version, |
+ String destPath) { |
+ return syncFuture(() { |
+ var url = Uri.parse("$server/packages/$package/versions/$version.tar.gz"); |
+ log.io("Get package from $url."); |
+ log.message('Downloading ${log.bold(package)} ${version}...'); |
+ |
+ // Download and extract the archive to a temp directory. |
+ var tempDir = systemCache.createTempDir(); |
+ return httpClient.send(new http.Request("GET", url)) |
+ .then((response) => response.stream) |
+ .then((stream) { |
+ return timeout(extractTarGz(stream, tempDir), HTTP_TIMEOUT, |
+ 'fetching URL "$url"'); |
+ }).then((_) { |
+ // Remove the existing directory if it exists. This will happen if |
+ // we're forcing a download to repair the cache. |
+ if (dirExists(destPath)) deleteEntry(destPath); |
+ |
+ // Now that the get has succeeded, move it to the real location in the |
+ // cache. This ensures that we don't leave half-busted ghost |
+ // directories in the user's pub cache if a get fails. |
+ renameDir(tempDir, destPath); |
+ return true; |
+ }); |
+ }); |
+ } |
/// When an error occurs trying to read something about [package] from [url], |
/// this tries to translate into a more user friendly error message. Always |
/// throws an error, either the original one or a better one. |
@@ -185,8 +217,8 @@ class OfflineHostedSource extends HostedSource { |
var parsed = _parseDescription(description); |
var server = parsed.last; |
log.io("Finding versions of $name in " |
- "${systemCache.rootDir}/${_getSourceDirectory(server)}"); |
- return getCachedPackages(server) |
+ "$systemCacheRoot/${_urlToDirectory(server)}"); |
+ return _getCachedPackagesInDirectory(_urlToDirectory(server)) |
.where((package) => package.name == name) |
.map((package) => package.version) |
.toList(); |
@@ -212,12 +244,58 @@ class OfflineHostedSource extends HostedSource { |
} |
} |
-String _getSourceDirectory(String url) { |
+/// Given a URL, returns a "normalized" string to be used as a directory name |
+/// for packages downloaded from the server at that URL. |
+/// |
+/// This normalization strips off the scheme (which is presumed to be HTTP or |
+/// HTTPS) and *sort of* URL-encodes it. I say "sort of" because it does it |
+/// incorrectly: it uses the character's *decimal* ASCII value instead of hex. |
+/// |
+/// This could cause an ambiguity since some characters get encoded as three |
+/// digits and others two. It's possible for one to be a prefix of the other. |
+/// In practice, the set of characters that are encoded don't happen to have |
+/// any collisions, so the encoding is reversible. |
+/// |
+/// This behavior is a bug, but is being preserved for compatibility. |
+String _urlToDirectory(String url) { |
url = url.replaceAll(new RegExp(r"^https?://"), ""); |
return replace(url, new RegExp(r'[<>:"\\/|?*%]'), |
(match) => '%${match[0].codeUnitAt(0)}'); |
} |
+/// Given a directory name in the system cache, returns the URL of the server |
+/// whose packages it contains. |
+/// |
+/// See [_urlToDirectory] for details on the mapping. Note that because the |
+/// directory name does not preserve the scheme, this has to guess at it. It |
+/// chooses "http" for loopback URLs (mainly to support the pub tests) and |
+/// "https" for all others. |
+String _directoryToUrl(String url) { |
+ // Decode the pseudo-URL-encoded characters. |
+ var chars = '<>:"\\/|?*%'; |
+ for (var i = 0; i < chars.length; i++) { |
+ var c = chars.substring(i, i + 1); |
+ url = url.replaceAll("%${c.codeUnitAt(0)}", c); |
+ } |
+ |
+ // Figure out the scheme. |
+ var scheme = "https"; |
+ |
+ // See if it's a loopback IP address. |
+ try { |
+ var urlWithoutPort = url.replaceAll(new RegExp(":.*"), ""); |
+ var address = new io.InternetAddress(urlWithoutPort); |
+ if (address.isLoopback) scheme = "http"; |
+ } on ArgumentError catch(error) { |
+ // If we got here, it's not a raw IP address, so it's probably a regular |
+ // URL. |
+ } |
+ |
+ if (url == "localhost") scheme = "http"; |
+ |
+ return "$scheme://$url"; |
+} |
+ |
/// Parses [description] into its server and package name components, then |
/// converts that to a Uri given [pattern]. Ensures the package name is |
/// properly URL encoded. |