From c4298fcf5b6551f44df6bc7339755175cd0cc883 Mon Sep 17 00:00:00 2001
From: "rnystrom@google.com" <rnystrom@google.com>
Date: Fri, 19 Apr 2013 22:29:44 +0000
Subject: [PATCH] Use the cached pubspec if possible for describing hosted
 packages.

BUG=https://code.google.com/p/dart/issues/detail?id=9027

Review URL: https://codereview.chromium.org//14241005

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge@21777 260f80e4-7a28-3924-810f-c04153c831b5
---
 lib/src/hosted_source.dart                   |  9 +++-
 lib/src/package.dart                         |  2 +-
 lib/src/system_cache.dart                    |  7 +++-
 test/install/hosted/cached_pubspec_test.dart | 43 ++++++++++++++++++++
 test/test_pub.dart                           | 18 ++++++++
 test/version_solver_test.dart                |  4 ++
 6 files changed, 78 insertions(+), 5 deletions(-)
 create mode 100644 test/install/hosted/cached_pubspec_test.dart

diff --git a/lib/src/hosted_source.dart b/lib/src/hosted_source.dart
index bd017e66..86693623 100644
--- a/lib/src/hosted_source.dart
+++ b/lib/src/hosted_source.dart
@@ -49,11 +49,16 @@ class HostedSource extends Source {
   /// Downloads and parses the pubspec for a specific version of a package that
   /// is available from the site.
   Future<Pubspec> describe(PackageId id) {
+    // Request it from the server.
     var url = _makeVersionUrl(id, (server, package, version) =>
         "$server/packages/$package/versions/$version.yaml");
 
     log.io("Describe package at $url.");
     return httpClient.read(url).then((yaml) {
+      // TODO(rnystrom): After this is pulled down, we could place it in
+      // a secondary cache of just pubspecs. This would let us have a
+      // persistent cache for pubspecs for packages that haven't actually
+      // been installed.
       return new Pubspec.parse(null, yaml, systemCache.sources);
     }).catchError((ex) {
       var parsed = _parseDescription(id.description);
@@ -122,11 +127,11 @@ class HostedSource extends Source {
     var cacheDir = path.join(systemCacheRoot,
                              _getSourceDirectory(_defaultUrl));
     if (!dirExists(cacheDir)) return [];
-  
+
     return listDir(path.join(cacheDir)).map((entry) =>
         new Package.load(null, entry, systemCache.sources)).toList();
   }
-  
+
   /// 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.
diff --git a/lib/src/package.dart b/lib/src/package.dart
index f1f65d30..ce1ad35d 100644
--- a/lib/src/package.dart
+++ b/lib/src/package.dart
@@ -146,7 +146,7 @@ class PackageId implements Comparable<PackageId> {
   }
 
   /// Returns the pubspec for this package.
-  Future<Pubspec> describe() => source.describe(this);
+  Future<Pubspec> describe() => source.systemCache.describe(this);
 
   /// Returns a future that completes to the resovled [PackageId] for this id.
   Future<PackageId> get resolved => source.resolveId(this);
diff --git a/lib/src/system_cache.dart b/lib/src/system_cache.dart
index 4c0e1760..8a51912a 100644
--- a/lib/src/system_cache.dart
+++ b/lib/src/system_cache.dart
@@ -70,13 +70,16 @@ class SystemCache {
     // Try to get it from the system cache first.
     if (id.source.shouldCache) {
       return id.systemCacheDirectory.then((packageDir) {
-        if (!dirExists(packageDir)) return id.describe();
+        if (!fileExists(path.join(packageDir, "pubspec.yaml"))) {
+          return id.source.describe(id);
+        }
+
         return new Pubspec.load(id.name, packageDir, sources);
       });
     }
 
     // Not cached, so get it from the source.
-    return id.describe();
+    return id.source.describe(id);
   }
 
   /// Ensures that the package identified by [id] is installed to the cache,
diff --git a/test/install/hosted/cached_pubspec_test.dart b/test/install/hosted/cached_pubspec_test.dart
new file mode 100644
index 00000000..c3012060
--- /dev/null
+++ b/test/install/hosted/cached_pubspec_test.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2012, 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 pub_tests;
+
+import 'package:pathos/path.dart' as path;
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../../../pub/io.dart';
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+
+  integration('does not request a pubspec for a cached package', () {
+    servePackages([packageMap("foo", "1.2.3")]);
+
+    d.appDir([dependencyMap("foo", "1.2.3")]).create();
+
+    // Run install once so it gets cached.
+    schedulePub(args: ['install'],
+        output: new RegExp("Dependencies installed!\$"));
+
+    // Clear the cache. We don't care about anything that was served during
+    // the initial install.
+    getRequestedPaths();
+
+    d.cacheDir({"foo": "1.2.3"}).validate();
+    d.packagesDir({"foo": "1.2.3"}).validate();
+
+    // Run the solver again now that it's cached.
+    schedulePub(args: ['install'],
+        output: new RegExp("Dependencies installed!\$"));
+
+    // The update should not have requested the pubspec since it's installed
+    // locally already.
+    getRequestedPaths().then((paths) {
+      expect(paths, isNot(contains("packages/foo/versions/1.2.3.yaml")));
+    });
+  });
+}
diff --git a/test/test_pub.dart b/test/test_pub.dart
index d7fdb1df..f5cb82c3 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -57,6 +57,10 @@ bool get runningOnBuildbot =>
 /// The current [HttpServer] created using [serve].
 var _server;
 
+/// The list of paths that have been requested from the server since the last
+/// call to [getRequestedPaths].
+final _requestedPaths = <String>[];
+
 /// The cached value for [_portCompleter].
 Completer<int> _portCompleterCache;
 
@@ -73,6 +77,16 @@ Completer<int> get _portCompleter {
 /// A future that will complete to the port used for the current server.
 Future<int> get port => _portCompleter.future;
 
+/// Gets the list of paths that have been requested from the server since the
+/// last time this was called (or since the server was first spun up).
+Future<List<String>> getRequestedPaths() {
+  return schedule(() {
+    var paths = _requestedPaths.toList();
+    _requestedPaths.clear();
+    return paths;
+  });
+}
+
 /// Creates an HTTP server to serve [contents] as static files. This server will
 /// exist only for the duration of the pub run.
 ///
@@ -88,6 +102,10 @@ void serve([List<d.Descriptor> contents]) {
           var response = request.response;
           try {
             var path = request.uri.path.replaceFirst("/", "");
+
+            if (_requestedPaths == null) _requestedPaths = <String>[];
+            _requestedPaths.add(path);
+
             response.persistentConnection = false;
             var stream = baseDir.load(path);
 
diff --git a/test/version_solver_test.dart b/test/version_solver_test.dart
index 6f24b414..b338e11e 100644
--- a/test/version_solver_test.dart
+++ b/test/version_solver_test.dart
@@ -888,6 +888,10 @@ class MockSource extends Source {
 
   MockSource(this.name);
 
+  Future<String> systemCacheDirectory(PackageId id) {
+    return new Future.value('${id.name}-${id.version}');
+  }
+
   Future<List<Version>> getVersions(String name, String description) {
     return new Future.sync(() {
       // Make sure the solver doesn't request the same thing twice.
-- 
GitLab