Don't download via the cache in `pub unpack` (#4785)
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index b149d68..d13db32 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -1221,7 +1221,7 @@
           'Try again without --offline.',
         );
       }
-      contentHash = await _download(id, packageDir, cache);
+      contentHash = await _downloadAtomically(id, packageDir, cache);
     }
     return DownloadPackageResult(
       PackageId(
@@ -1355,7 +1355,7 @@
                 );
                 try {
                   deleteEntry(package.dir);
-                  await _download(id, package.dir, cache);
+                  await _downloadAtomically(id, package.dir, cache);
                   return RepairResult(id.name, id.version, this, success: true);
                 } catch (error, stackTrace) {
                   var message =
@@ -1444,18 +1444,53 @@
         .toList();
   }
 
-  Future<void> downloadInto(PackageId id, String destPath, SystemCache cache) =>
-      _download(id, destPath, cache);
+  Future<void> downloadInto(
+    PackageId id,
+    String destPath,
+    SystemCache cache,
+  ) async {
+    try {
+      // For the functionality of `unpack` it is important that the unpack
+      // doesn't go via the cache, as that would not allow unpacking into a
+      // directory on a different device than the cache. So we don't use
+      // `_downloadAtomically` here.
+      await _downloadAndExtract(id, destPath, cache);
+    } catch (e) {
+      tryDeleteEntry(destPath);
+      rethrow;
+    }
+  }
 
   /// Downloads package [id] from the archive_url and unpacks it into
-  /// [destPath].
+  /// [destPath]. The unpack is done to a temporary directory and then moved
+  /// into [destPath] atomically.
   ///
   /// If there is no archive_url, try to fetch it from
   /// `$server/packages/$package/versions/$version.tar.gz` where server comes
   /// from `id.description`.
   ///
   /// Returns the content-hash of the downloaded archive.
-  Future<Uint8List> _download(
+  Future<Uint8List> _downloadAtomically(
+    PackageId id,
+    String destPath,
+    SystemCache cache,
+  ) async {
+    final tempDir = cache.createTempDir();
+    try {
+      final contentHash = await _downloadAndExtract(id, tempDir, cache);
+      ensureDir(p.dirname(destPath));
+      tryRenameDir(tempDir, destPath);
+      return contentHash;
+    } catch (e) {
+      deleteEntry(tempDir);
+      rethrow;
+    }
+  }
+
+  /// Downloads the archive for [id] and extracts it to [destPath].
+  ///
+  /// Returns the content-hash of the downloaded archive.
+  Future<Uint8List> _downloadAndExtract(
     PackageId id,
     String destPath,
     SystemCache cache,
@@ -1568,25 +1603,11 @@
         _throwFriendlyError(error, stackTrace, id.name, description.url);
       }
 
-      final tempDir = cache.createTempDir();
       try {
-        try {
-          await extractTarGz(readBinaryFileAsStream(archivePath), tempDir);
-        } on FormatException catch (e) {
-          dataError('Failed to extract `$archivePath`: ${e.message}.');
-        }
-        ensureDir(p.dirname(destPath));
-      } catch (e) {
-        deleteEntry(tempDir);
-        rethrow;
+        await extractTarGz(readBinaryFileAsStream(archivePath), destPath);
+      } on FormatException catch (e) {
+        dataError('Failed to extract `$archivePath`: ${e.message}.');
       }
-      // Now that the get has succeeded, move it to the real location in the
-      // cache.
-      //
-      // If this fails with a "directory not empty" exception we assume that
-      // another pub process has installed the same package version while we
-      // downloaded.
-      tryRenameDir(tempDir, destPath);
       return contentHash;
     });
   }
diff --git a/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
index cf58928..29480b5 100644
--- a/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
+++ b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
@@ -32,6 +32,7 @@
 [E] SLVR: Version solving took: $TIME
 [E]    | Tried 1 solutions.
 [E] FINE: Resolving dependencies finished ($TIME)
+[E] IO  : Created temp directory $DIR
 [E] IO  : Get package from http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz.
 [E] FINE: Downloading foo 1.0.0...
 [E] IO  : Created temp directory $DIR
@@ -53,7 +54,6 @@
 [E] FINE: Contents:
 [E]    | c35f6caa60870d3b6c5c6aebe5a5d4b56ca4733a52a80ded3445c0c5fa4b62df
 [E] FINE: Created $FILE from stream
-[E] IO  : Created temp directory $DIR
 [E] IO  : Reading binary file $FILE.
 [E] FINE: Extracting .tar.gz stream to $DIR
 [E] IO  : Creating $FILE from stream
@@ -61,8 +61,8 @@
 [E] IO  : Creating $FILE from stream
 [E] FINE: Created $FILE from stream
 [E] FINE: Extracted .tar.gz to $DIR
-[E] IO  : Renaming directory $A to $B
 [E] IO  : Deleting directory $DIR
+[E] IO  : Renaming directory $A to $B
 [E] IO  : Writing $N characters to text file $SANDBOX/cache/README.md.
 [E] FINE: Contents:
 [E]    | Pub Package Cache
@@ -209,6 +209,7 @@
    | Tried 1 solutions.
 FINE: Resolving dependencies finished ($TIME)
 MSG : Downloading packages...
+IO  : Created temp directory $DIR
 IO  : Get package from http://localhost:$PORT/packages/foo/versions/1.0.0.tar.gz.
 FINE: Downloading foo 1.0.0...
 IO  : Created temp directory $DIR
@@ -230,7 +231,6 @@
 FINE: Contents:
    | c35f6caa60870d3b6c5c6aebe5a5d4b56ca4733a52a80ded3445c0c5fa4b62df
 FINE: Created $FILE from stream
-IO  : Created temp directory $DIR
 IO  : Reading binary file $FILE.
 FINE: Extracting .tar.gz stream to $DIR
 IO  : Creating $FILE from stream
@@ -238,8 +238,8 @@
 IO  : Creating $FILE from stream
 FINE: Created $FILE from stream
 FINE: Extracted .tar.gz to $DIR
-IO  : Renaming directory $A to $B
 IO  : Deleting directory $DIR
+IO  : Renaming directory $A to $B
 IO  : Writing $N characters to text file $SANDBOX/cache/README.md.
 FINE: Contents:
    | Pub Package Cache