@@ -451,6 +451,44 @@ impl BlobService {
451451 Ok ( output)
452452 }
453453
454+ // Prune operations
455+
456+ /// Deletes all expired pending blobs from the database and S3.
457+ pub async fn prune ( ctx : & ServiceContext < ' _ > ) -> Result < ( ) > {
458+ let txn = ctx. transaction ( ) ;
459+ let bucket = ctx. s3_files_bucket ( ) ;
460+ info ! ( "Pruning expired pending blobs from database and S3" ) ;
461+
462+ // Fetch all expired pending blobs
463+ let pending_blobs = BlobPending :: find ( )
464+ . select_only ( )
465+ . column ( blob_pending:: Column :: ExternalId )
466+ . column ( blob_pending:: Column :: S3Path )
467+ . filter ( blob_pending:: Column :: ExpiresAt . lte ( now ( ) ) )
468+ . into_tuple :: < ( String , String ) > ( )
469+ . all ( txn)
470+ . await ?;
471+
472+ // Delete from the S3 bucket
473+ for ( _, s3_path) in & pending_blobs {
474+ // Only try to delete if the object exists,
475+ // ignore missing objects.
476+ if Self :: exists ( ctx, s3_path) . await ? {
477+ bucket. delete_object ( & s3_path) . await ?;
478+ }
479+ }
480+
481+ // Delete from the database
482+ let blob_ids = pending_blobs. into_iter ( ) . map ( |( id, _) | id) ;
483+
484+ BlobPending :: delete_many ( )
485+ . filter ( blob_pending:: Column :: ExternalId . is_in ( blob_ids) )
486+ . exec ( txn)
487+ . await ?;
488+
489+ Ok ( ( ) )
490+ }
491+
454492 // Hard-deletion operations
455493
456494 /// Does a dry run on a blob hard deletion, showing what would have been changed.
@@ -872,20 +910,6 @@ impl BlobService {
872910 find_or_error ! ( Self :: get_metadata_optional( ctx, hash) , Blob )
873911 }
874912
875- #[ allow( dead_code) ] // TEMP
876- pub async fn exists ( ctx : & ServiceContext < ' _ > , hash : & [ u8 ] ) -> Result < bool > {
877- // Special handling for the empty blob
878- if hash == EMPTY_BLOB_HASH {
879- debug ! ( "Checking existence of the empty blob" ) ;
880- return Ok ( true ) ;
881- }
882-
883- // Fetch existence from S3
884- let hex_hash = blob_hash_to_hex ( hash) ;
885- let result = Self :: head ( ctx, & hex_hash) . await ?;
886- Ok ( result. is_some ( ) )
887- }
888-
889913 /// Possibly retrieve blob contents, if a flag is set.
890914 ///
891915 /// This utility conditionally retrieves the
@@ -922,6 +946,11 @@ impl BlobService {
922946 }
923947 }
924948
949+ async fn exists ( ctx : & ServiceContext < ' _ > , path : & str ) -> Result < bool > {
950+ let head = Self :: head ( ctx, path) . await ?;
951+ Ok ( head. is_some ( ) )
952+ }
953+
925954 pub async fn hard_delete ( ctx : & ServiceContext < ' _ > , hash : & [ u8 ] ) -> Result < ( ) > {
926955 // Special handling for empty blobs
927956 //
0 commit comments