Download packages from the archive link instead of hardcoded uri (#2366)

diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index de32b21..146f0f8 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -151,6 +151,14 @@
   }
 }
 
+/// Information about a package version retrieved from /api/packages/$package
+class _VersionInfo {
+  final Pubspec pubspec;
+  final Uri archiveUrl;
+
+  _VersionInfo(this.pubspec, this.archiveUrl);
+}
+
 /// The [BoundSource] for [HostedSource].
 class BoundHostedSource extends CachedSource {
   @override
@@ -158,14 +166,14 @@
 
   @override
   final SystemCache systemCache;
-  RateLimitedScheduler<PackageRef, Map<PackageId, Pubspec>> _scheduler;
+  RateLimitedScheduler<PackageRef, Map<PackageId, _VersionInfo>> _scheduler;
 
   BoundHostedSource(this.source, this.systemCache) {
     _scheduler =
         RateLimitedScheduler(_fetchVersions, maxConcurrentOperations: 10);
   }
 
-  Future<Map<PackageId, Pubspec>> _fetchVersions(PackageRef ref) async {
+  Future<Map<PackageId, _VersionInfo>> _fetchVersions(PackageRef ref) async {
     var url = _makeUrl(
         ref.description, (server, package) => '$server/api/packages/$package');
     log.io('Get versions from $url.');
@@ -186,7 +194,10 @@
           expectedName: ref.name, location: url);
       var id = source.idFor(ref.name, pubspec.version,
           url: _serverFor(ref.description));
-      return MapEntry(id, pubspec);
+      final archiveUrlValue = map['archive_url'];
+      final archiveUrl =
+          archiveUrlValue is String ? Uri.tryParse(archiveUrlValue) : null;
+      return MapEntry(id, _VersionInfo(pubspec, archiveUrl));
     }));
 
     // Prefetch the dependencies of the latest version, we are likely to need
@@ -200,7 +211,8 @@
       final latestVersionId =
           PackageId(ref.name, source, latestVersion, ref.description);
 
-      final dependencies = result[latestVersionId]?.dependencies?.values ?? [];
+      final dependencies =
+          result[latestVersionId]?.pubspec?.dependencies?.values ?? [];
       unawaited(withDependencyType(DependencyType.none, () async {
         for (final packageRange in dependencies) {
           if (packageRange.source is HostedSource) {
@@ -239,7 +251,7 @@
     final versions = await _scheduler.schedule(id.toRef());
     final url = _makeUrl(
         id.description, (server, package) => '$server/api/packages/$package');
-    return versions[id] ??
+    return versions[id]?.pubspec ??
         (throw PackageNotFoundException('Could not find package $id at $url'));
   }
 
@@ -249,8 +261,7 @@
     if (!isInSystemCache(id)) {
       var packageDir = getDirectory(id);
       ensureDir(p.dirname(packageDir));
-      var parsed = source._parseDescription(id.description);
-      await _download(parsed.last, parsed.first, id.version, packageDir);
+      await _download(id, packageDir);
     }
 
     return Package.load(id.name, getDirectory(id), systemCache.sources);
@@ -295,9 +306,8 @@
 
       for (var package in packages) {
         var id = source.idFor(package.name, package.version, url: url);
-
         try {
-          await _download(url, package.name, package.version, package.dir);
+          await _download(id, package.dir);
           successes.add(id);
         } catch (error, stackTrace) {
           failures.add(id);
@@ -352,13 +362,31 @@
         .toList();
   }
 
-  /// Downloads package [package] at [version] from [server], and unpacks it
-  /// into [destPath].
-  Future _download(
-      String server, String package, Version version, String destPath) async {
-    var url = Uri.parse('$server/packages/$package/versions/$version.tar.gz');
+  /// Downloads package [package] at [version] from the archive_url and unpacks
+  /// it into [destPath].
+  ///
+  /// If there is no archive_url, try to fetch it from
+  /// `$server/packages/$package/versions/$version.tar.gz` where server comes
+  /// from `id.description`.
+  Future _download(PackageId id, String destPath) async {
+    final versions = await _scheduler.schedule(id.toRef());
+    final versionInfo = versions[id];
+    final packageName = id.name;
+    final version = id.version;
+    if (versionInfo == null) {
+      throw PackageNotFoundException(
+          'Package $packageName has no version $version');
+    }
+    var url = versionInfo.archiveUrl;
+    if (url == null) {
+      // To support old servers that has no archive_url we fall back to the
+      // hard-coded path.
+      final parsedDescription = source._parseDescription(id.description);
+      final server = parsedDescription.last;
+      url = Uri.parse('$server/packages/$packageName/versions/$version.tar.gz');
+    }
     log.io('Get package from $url.');
-    log.message('Downloading ${log.bold(package)} $version...');
+    log.message('Downloading ${log.bold(id.name)} ${id.version}...');
 
     // Download and extract the archive to a temp directory.
     var tempDir = systemCache.createTempDir();
@@ -521,8 +549,7 @@
   }
 
   @override
-  Future _download(
-      String server, String package, Version version, String destPath) {
+  Future _download(PackageId id, String destPath) {
     // Since HostedSource is cached, this will only be called for uncached
     // packages.
     throw UnsupportedError('Cannot download packages when offline.');
diff --git a/test/cache/repair/handles_failure_test.dart b/test/cache/repair/handles_failure_test.dart
index 653ae2e..3759d76 100644
--- a/test/cache/repair/handles_failure_test.dart
+++ b/test/cache/repair/handles_failure_test.dart
@@ -35,11 +35,13 @@
     var pub = await startPub(args: ['cache', 'repair']);
 
     expect(pub.stdout, emits('Downloading foo 1.2.3...'));
-    expect(pub.stdout, emits('Downloading foo 1.2.4...'));
     expect(pub.stdout, emits('Downloading foo 1.2.5...'));
 
     expect(pub.stderr, emits(startsWith('Failed to repair foo 1.2.4. Error:')));
-    expect(pub.stderr, emits('HTTP error 404: Not Found'));
+    expect(
+        pub.stderr,
+        emits('Package doesn\'t exist '
+            '(Package foo has no version 1.2.4).'));
 
     expect(pub.stdout, emits('Reinstalled 2 packages.'));
     expect(pub.stdout, emits('Failed to reinstall 1 package:'));
diff --git a/test/package_server.dart b/test/package_server.dart
index 9f261bf..493bcba 100644
--- a/test/package_server.dart
+++ b/test/package_server.dart
@@ -100,13 +100,15 @@
               'name': name,
               'uploaders': ['nweiz@google.com'],
               'versions': versions
-                  .map((version) => packageVersionApiMap(version.pubspec))
+                  .map((version) => packageVersionApiMap(url, version.pubspec))
                   .toList()
             })),
         d.dir(name, [
           d.dir('versions', versions.map((version) {
-            return d.file(version.version.toString(),
-                jsonEncode(packageVersionApiMap(version.pubspec, full: true)));
+            return d.file(
+                version.version.toString(),
+                jsonEncode(
+                    packageVersionApiMap(url, version.pubspec, full: true)));
           }))
         ])
       ]);
diff --git a/test/test_pub.dart b/test/test_pub.dart
index 27fd919..94e81f8 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -614,17 +614,13 @@
 /// [pubspec] is the parsed pubspec of the package version. If [full] is true,
 /// this returns the complete map, including metadata that's only included when
 /// requesting the package version directly.
-Map packageVersionApiMap(Map pubspec, {bool full = false}) {
+Map packageVersionApiMap(String hostedUrl, Map pubspec, {bool full = false}) {
   var name = pubspec['name'];
   var version = pubspec['version'];
   var map = {
     'pubspec': pubspec,
     'version': version,
-    'url': '/api/packages/$name/versions/$version',
-    'archive_url': '/packages/$name/versions/$version.tar.gz',
-    'new_dartdoc_url': '/api/packages/$name/versions/$version'
-        '/new_dartdoc',
-    'package_url': '/api/packages/$name'
+    'archive_url': '$hostedUrl/packages/$name/versions/$version.tar.gz',
   };
 
   if (full) {
diff --git a/test/validator/dependency_test.dart b/test/validator/dependency_test.dart
index 6ffd61f..31ed937 100644
--- a/test/validator/dependency_test.dart
+++ b/test/validator/dependency_test.dart
@@ -45,8 +45,8 @@
             'name': 'foo',
             'uploaders': ['nweiz@google.com'],
             'versions': hostedVersions
-                .map((version) =>
-                    packageVersionApiMap(packageMap('foo', version)))
+                .map((version) => packageVersionApiMap(
+                    'https://pub.dartlang.org', packageMap('foo', version)))
                 .toList()
           }),
           200));