11# Copyright The IETF Trust 2023-2026, All Rights Reserved
2+ import os
23import shutil
34from pathlib import Path
45from tempfile import TemporaryDirectory
3536)
3637from ietf .doc .models import Document , DocHistory , RfcAuthor
3738from ietf .doc .serializers import RfcAuthorSerializer
39+ from ietf .doc .storage_utils import remove_from_storage , store_file , exists_in_storage
3840from ietf .person .models import Email , Person
3941
4042
@@ -366,8 +368,8 @@ class RfcPubFilesView(APIView):
366368 api_key_endpoint = "ietf.api.views_rpc"
367369 parser_classes = [parsers .MultiPartParser ]
368370
369- def _destination (self , filename : str | Path ) -> Path :
370- """Destination for an uploaded RFC file
371+ def _fs_destination (self , filename : str | Path ) -> Path :
372+ """Destination for an uploaded RFC file in the filesystem
371373
372374 Strips any path components in filename and returns an absolute Path.
373375 """
@@ -378,6 +380,23 @@ def _destination(self, filename: str | Path) -> Path:
378380 return rfc_path / "prerelease" / filename .name
379381 return rfc_path / filename .name
380382
383+ def _blob_destination (self , filename : str | Path ) -> str :
384+ """Destination name for an uploaded RFC file in the blob store
385+
386+ Strips any path components in filename and returns an absolute Path.
387+ """
388+ filename = Path (filename ) # could potentially have directory components
389+ extension = "" .join (filename .suffixes )
390+ if extension == ".notprepped.xml" :
391+ file_type = "notprepped"
392+ elif extension [0 ] == "." :
393+ file_type = extension [1 :]
394+ else :
395+ raise serializers .ValidationError (
396+ f"Extension does not begin with '.'!? ({ filename } )" ,
397+ )
398+ return f"{ file_type } /{ filename .name } "
399+
381400 @extend_schema (
382401 operation_id = "upload_rfc_files" ,
383402 summary = "Upload files for a published RFC" ,
@@ -394,10 +413,17 @@ def post(self, request):
394413 uploaded_files = serializer .validated_data ["contents" ] # list[UploadedFile]
395414 replace = serializer .validated_data ["replace" ]
396415 dest_stem = f"rfc{ rfc .rfc_number } "
416+ mtime = serializer .validated_data ["mtime" ]
417+ mtimestamp = mtime .timestamp ()
418+ blob_kind = "rfc"
397419
398420 # List of files that might exist for an RFC
399421 possible_rfc_files = [
400- self ._destination (dest_stem + ext )
422+ self ._fs_destination (dest_stem + ext )
423+ for ext in serializer .allowed_extensions
424+ ]
425+ possible_rfc_blobs = [
426+ self ._blob_destination (dest_stem + ext )
401427 for ext in serializer .allowed_extensions
402428 ]
403429 if not replace :
@@ -408,6 +434,14 @@ def post(self, request):
408434 "File(s) already exist for this RFC" ,
409435 code = "files-exist" ,
410436 )
437+ for possible_existing_blob in possible_rfc_blobs :
438+ if exists_in_storage (
439+ kind = blob_kind , name = possible_existing_blob
440+ ):
441+ raise Conflict (
442+ "Blob(s) already exist for this RFC" ,
443+ code = "blobs-exist" ,
444+ )
411445
412446 with TemporaryDirectory () as tempdir :
413447 # Save files in a temporary directory. Use the uploaded filename
@@ -421,14 +455,27 @@ def post(self, request):
421455 with tempfile_path .open ("wb" ) as dest :
422456 for chunk in upfile .chunks ():
423457 dest .write (chunk )
458+ os .utime (tempfile_path , (mtimestamp , mtimestamp ))
424459 files_to_move .append (tempfile_path )
425460 # copy files to final location, removing any existing ones first if the
426461 # remove flag was set
427462 if replace :
428463 for possible_existing_file in possible_rfc_files :
429464 possible_existing_file .unlink (missing_ok = True )
465+ for possible_existing_blob in possible_rfc_blobs :
466+ remove_from_storage (
467+ blob_kind , possible_existing_blob , warn_if_missing = False
468+ )
430469 for ftm in files_to_move :
431- shutil .move (ftm , self ._destination (ftm ))
432- # todo store in blob storage as well (need a bucket for RFCs)
470+ with ftm .open ("rb" ) as f :
471+ store_file (
472+ kind = blob_kind ,
473+ name = self ._blob_destination (ftm ),
474+ file = f ,
475+ doc_name = rfc .name ,
476+ doc_rev = rfc .rev , # expect None, but match whatever it is
477+ mtime = mtime ,
478+ )
479+ shutil .move (ftm , self ._fs_destination (ftm ))
433480
434481 return Response (NotificationAckSerializer ().data )
0 commit comments