@@ -79,6 +79,8 @@ PackageBackend get packageBackend =>
7979class PackageBackend {
8080 final DatastoreDB db;
8181
82+ final Storage _storage;
83+
8284 /// The Cloud Storage bucket to use for incoming package archives.
8385 /// The following files are present:
8486 /// - `tmp/$guid` (incoming package archive that was uploaded, but not yet processed)
@@ -92,12 +94,12 @@ class PackageBackend {
9294
9395 PackageBackend (
9496 this .db,
95- Storage storage ,
97+ this ._storage ,
9698 this ._incomingBucket,
9799 Bucket canonicalBucket,
98100 Bucket publicBucket,
99101 ) : tarballStorage =
100- TarballStorage (db, storage , canonicalBucket, publicBucket);
102+ TarballStorage (db, _storage , canonicalBucket, publicBucket);
101103
102104 /// Whether the package exists and is not blocked or deleted.
103105 Future <bool > isPackageVisible (String package) async {
@@ -891,28 +893,48 @@ class PackageBackend {
891893
892894 /// Finishes the upload of a package and returns the list of messages
893895 /// related to the publishing.
894- Future <List <String >> publishUploadedBlob (String guid ) async {
896+ Future <List <String >> publishUploadedBlob (String uploadGuid ) async {
895897 final restriction = await getUploadRestrictionStatus ();
896898 if (restriction == UploadRestrictionStatus .noUploads) {
897899 throw PackageRejectedException .uploadRestricted ();
898900 }
899901 final agent = await requireAuthenticatedClient ();
900- _logger.info ('Finishing async upload (uuid: $guid )' );
902+ _logger.info ('Finishing async upload (uuid: $uploadGuid )' );
901903 _logger.info ('Reading tarball from cloud storage.' );
902904
903905 return await withTempDirectory ((Directory dir) async {
904- final filename = '${dir .absolute .path }/tarball.tar.gz' ;
905- final info = await _incomingBucket.tryInfo (tmpObjectName (guid));
906+ // Check the existence of the uploaded file
907+ final uploadObjectName = tmpObjectName (uploadGuid);
908+ final info = await _incomingBucket.tryInfo (uploadObjectName);
906909 if (info? .length == null ) {
907910 throw PackageRejectedException .archiveEmpty ();
908911 }
912+
913+ // Create a temporary copy that we will continue working with.
914+ // This will protect us against the unlikely scenario where the
915+ // uploaded blob changes during this processing.
916+ final workGuid = createUuid ();
917+ final workObjectName = '${tmpObjectName (workGuid )}-$uploadGuid ' ;
918+ try {
919+ await _storage.copyObjectWithRetry (
920+ _incomingBucket.absoluteObjectName (uploadObjectName),
921+ _incomingBucket.absoluteObjectName (workObjectName),
922+ );
923+ } catch (e) {
924+ throw InvalidInputException (
925+ 'Failed to copy uploaded file (uuid:$uploadGuid ).' );
926+ }
927+
928+ // Check the file size is within limits.
909929 if (info! .length > UploadSignerService .maxUploadSize) {
910930 throw PackageRejectedException .archiveTooLarge (
911931 UploadSignerService .maxUploadSize);
912932 }
933+
934+ final filename = '${dir .absolute .path }/tarball.tar.gz' ;
913935 await _incomingBucket.readWithRetry (
914- tmpObjectName (guid) , (input) => _saveTarballToFS (input, filename));
915- _logger.info ('Examining tarball content ($guid ).' );
936+ workObjectName , (input) => _saveTarballToFS (input, filename));
937+ _logger.info ('Examining tarball content ($uploadGuid ).' );
916938 final sw = Stopwatch ()..start ();
917939 final file = File (filename);
918940 final sha256Hash = (await file.openRead ().transform (sha256).single).bytes;
@@ -996,15 +1018,16 @@ class PackageBackend {
9961018 entities: entities,
9971019 agent: agent,
9981020 archive: archive,
999- guid : guid ,
1021+ objectName : workObjectName ,
10001022 hasCanonicalArchiveObject:
10011023 canonicalContentMatch == ContentMatchStatus .same,
10021024 );
10031025 _logger.info ('Tarball uploaded in ${sw .elapsed }.' );
1004- _logger.info ('Removing temporary object $guid .' );
1026+ _logger.info ('Removing temporary object $uploadGuid .' );
10051027
10061028 sw.reset ();
1007- await _incomingBucket.deleteWithRetry (tmpObjectName (guid));
1029+ await _incomingBucket.deleteWithRetry (uploadObjectName);
1030+ await _incomingBucket.deleteWithRetry (workObjectName);
10081031 _logger.info ('Temporary object removed in ${sw .elapsed }.' );
10091032 return [
10101033 'Successfully uploaded '
@@ -1074,7 +1097,7 @@ class PackageBackend {
10741097 required _UploadEntities entities,
10751098 required AuthenticatedAgent agent,
10761099 required PackageSummary archive,
1077- required String guid ,
1100+ required String objectName ,
10781101 required bool hasCanonicalArchiveObject,
10791102 }) async {
10801103 final sw = Stopwatch ()..start ();
@@ -1205,7 +1228,7 @@ class PackageBackend {
12051228 // Copy archive to canonical bucket.
12061229 await tarballStorage.copyFromTempToCanonicalBucket (
12071230 sourceAbsoluteObjectName:
1208- _incomingBucket.absoluteObjectName (tmpObjectName (guid) ),
1231+ _incomingBucket.absoluteObjectName (objectName ),
12091232 package: newVersion.package,
12101233 version: newVersion.version! ,
12111234 );
0 commit comments