@@ -275,14 +275,12 @@ final class ExportedPackage {
275275 ) async {
276276 await Future .wait ([
277277 ..._owner._prefixes.map ((prefix) async {
278- final pfx = prefix + ' /api/archives/$_package -' ;
278+ final pfx = '$ prefix /api/archives/$_package -' ;
279279
280280 final versionsForUpload = versions.keys.toSet ();
281281 await _owner._listBucket (prefix: pfx, delimiter: '' , (entry) async {
282282 final item = switch (entry) {
283- final BucketDirectoryEntry _ => throw AssertionError (
284- 'directory entries should not be possible' ,
285- ),
283+ final BucketDirectoryEntry _ => throw AssertionError ('unreachable' ),
286284 final BucketObjectEntry item => item,
287285 };
288286 if (! item.name.endsWith ('.tar.gz' )) {
@@ -561,6 +559,18 @@ final class ExportedBlob extends ExportedObject {
561559 }));
562560 }
563561
562+ /// Copy from [source] to [prefix] if required by [destinationInfo] .
563+ ///
564+ /// This will skip copying if [destinationInfo] indicates that the file
565+ /// already exists, and the [ObjectInfo.length] and [ObjectInfo.md5Hash]
566+ /// indicates that the contents is the same as [source] .
567+ ///
568+ /// Even if the copy is skipped, this will update the [_validatedCustomHeader]
569+ /// header, if it's older than [_updateValidatedAfter] . This ensures that we
570+ /// can detect stray files that are not being updated (but also not deleted).
571+ ///
572+ /// Throws, if [destinationInfo] is not `null` and its [ObjectInfo.name]
573+ /// doesn't match the intended target object in [prefix] .
564574 Future <void > _copyToPrefixFromIfNotContentEquals (
565575 String prefix,
566576 SourceObjectInfo source,
@@ -569,14 +579,22 @@ final class ExportedBlob extends ExportedObject {
569579 final dst = prefix + _objectName;
570580
571581 // Check if the dst already exists
572- if (destinationInfo case final dstInfo? ) {
573- if (dstInfo.contentEquals (source)) {
582+ if (destinationInfo != null ) {
583+ if (destinationInfo.name != dst) {
584+ throw ArgumentError .value (
585+ destinationInfo,
586+ 'destinationInfo' ,
587+ 'should have name "$dst " not "${destinationInfo .name }"' ,
588+ );
589+ }
590+
591+ if (destinationInfo.contentEquals (source)) {
574592 // If both source and dst exists, and their content matches, then
575593 // we only need to update the "validated" metadata. And we only
576594 // need to update the "validated" timestamp if it's older than
577595 // _retouchDeadline
578596 final retouchDeadline = clock.agoBy (_updateValidatedAfter);
579- if (dstInfo .metadata.validated.isBefore (retouchDeadline)) {
597+ if (destinationInfo .metadata.validated.isBefore (retouchDeadline)) {
580598 await _owner._bucket.updateMetadata (dst, _metadata ());
581599 }
582600 return ;
0 commit comments