github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/uploader/multi_parts_uploader_v2.go (about)

     1  package uploader
     2  
     3  import (
     4  	"context"
     5  	"crypto/md5"
     6  	"encoding/hex"
     7  	"errors"
     8  	"io"
     9  	"time"
    10  
    11  	internal_io "github.com/qiniu/go-sdk/v7/internal/io"
    12  	"github.com/qiniu/go-sdk/v7/storagev2/apis"
    13  	"github.com/qiniu/go-sdk/v7/storagev2/apis/resumable_upload_v2_complete_multipart_upload"
    14  	"github.com/qiniu/go-sdk/v7/storagev2/retrier"
    15  	resumablerecorder "github.com/qiniu/go-sdk/v7/storagev2/uploader/resumable_recorder"
    16  	"github.com/qiniu/go-sdk/v7/storagev2/uploader/source"
    17  )
    18  
    19  type (
    20  	multiPartsUploaderV2 struct {
    21  		storage *apis.Storage
    22  		options *MultiPartsUploaderOptions
    23  	}
    24  
    25  	multiPartsUploaderV2InitializedParts struct {
    26  		bucketName, uploadID    string
    27  		multiPartsObjectOptions *MultiPartsObjectOptions
    28  		expiredAt               time.Time
    29  		records                 map[uint64]resumedMultiPartsUploaderV2Record
    30  		medium                  resumablerecorder.WriteableResumableRecorderMedium
    31  		src                     source.Source
    32  	}
    33  
    34  	multiPartsUploaderV2UploadedPart struct {
    35  		partNumber, offset, size uint64
    36  		etag                     string
    37  		md5                      [md5.Size]byte
    38  	}
    39  
    40  	resumedMultiPartsUploaderV2Record struct {
    41  		uploadID, etag           string
    42  		md5                      [md5.Size]byte
    43  		partNumber, offset, size uint64
    44  		expiredAt                time.Time
    45  	}
    46  )
    47  
    48  // 创建分片上传器 V2
    49  func NewMultiPartsUploaderV2(options *MultiPartsUploaderOptions) MultiPartsUploader {
    50  	if options == nil {
    51  		options = &MultiPartsUploaderOptions{}
    52  	}
    53  	return &multiPartsUploaderV2{apis.NewStorage(&options.Options), options}
    54  }
    55  
    56  func (uploader *multiPartsUploaderV2) InitializeParts(ctx context.Context, src source.Source, multiPartsObjectOptions *MultiPartsObjectOptions) (InitializedParts, error) {
    57  	if multiPartsObjectOptions == nil {
    58  		multiPartsObjectOptions = &MultiPartsObjectOptions{}
    59  	}
    60  	if multiPartsObjectOptions.PartSize == 0 {
    61  		multiPartsObjectOptions.PartSize = 1 << 22
    62  	}
    63  
    64  	upToken, err := getUpToken(uploader.options.Credentials, &multiPartsObjectOptions.ObjectOptions, uploader.options.UpTokenProvider)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	bucketName, err := guessBucketName(ctx, multiPartsObjectOptions.BucketName, upToken)
    70  	if err != nil {
    71  		return nil, err
    72  	} else if multiPartsObjectOptions.BucketName == "" {
    73  		multiPartsObjectOptions.BucketName = bucketName
    74  	}
    75  
    76  	response, err := uploader.storage.ResumableUploadV2InitiateMultipartUpload(ctx, &apis.ResumableUploadV2InitiateMultipartUploadRequest{
    77  		BucketName: bucketName,
    78  		ObjectName: multiPartsObjectOptions.ObjectName,
    79  		UpToken:    upToken,
    80  	}, &apis.Options{
    81  		OverwrittenRegion: multiPartsObjectOptions.RegionsProvider,
    82  	})
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	medium := tryToOpenResumableRecorderForAppending(ctx, src, multiPartsObjectOptions, uploader.options)
    88  	return &multiPartsUploaderV2InitializedParts{
    89  		bucketName:              bucketName,
    90  		uploadID:                response.UploadId,
    91  		multiPartsObjectOptions: multiPartsObjectOptions,
    92  		expiredAt:               time.Unix(response.ExpiredAt, 0),
    93  		medium:                  medium,
    94  		src:                     src,
    95  	}, nil
    96  }
    97  
    98  func (uploader *multiPartsUploaderV2) TryToResume(ctx context.Context, src source.Source, multiPartsObjectOptions *MultiPartsObjectOptions) InitializedParts {
    99  	if multiPartsObjectOptions == nil {
   100  		multiPartsObjectOptions = &MultiPartsObjectOptions{}
   101  	}
   102  	if multiPartsObjectOptions.PartSize == 0 {
   103  		multiPartsObjectOptions.PartSize = 1 << 22
   104  	}
   105  
   106  	upToken, err := getUpToken(uploader.options.Credentials, &multiPartsObjectOptions.ObjectOptions, uploader.options.UpTokenProvider)
   107  	if err != nil {
   108  		return nil
   109  	}
   110  
   111  	bucketName, err := guessBucketName(ctx, multiPartsObjectOptions.BucketName, upToken)
   112  	if err != nil {
   113  		return nil
   114  	} else if multiPartsObjectOptions.BucketName == "" {
   115  		multiPartsObjectOptions.BucketName = bucketName
   116  	}
   117  
   118  	readableMedium := tryToOpenResumableRecorderForReading(ctx, src, multiPartsObjectOptions, uploader.options)
   119  	if readableMedium == nil {
   120  		return nil
   121  	}
   122  	defer readableMedium.Close()
   123  
   124  	var (
   125  		records   = make(map[uint64]resumedMultiPartsUploaderV2Record)
   126  		uploadID  string
   127  		expiredAt time.Time
   128  	)
   129  	for {
   130  		var record resumablerecorder.ResumableRecord
   131  		if err := readableMedium.Next(&record); err != nil {
   132  			break
   133  		}
   134  		records[record.PartNumber] = resumedMultiPartsUploaderV2Record{
   135  			uploadID:   record.UploadID,
   136  			etag:       record.PartID,
   137  			md5:        record.MD5,
   138  			partNumber: record.PartNumber,
   139  			offset:     record.Offset,
   140  			size:       record.PartSize,
   141  			expiredAt:  record.ExpiredAt,
   142  		}
   143  		if uploadID == "" {
   144  			uploadID = record.UploadID
   145  			expiredAt = record.ExpiredAt
   146  		}
   147  	}
   148  	readableMedium.Close()
   149  	if uploadID == "" {
   150  		return nil
   151  	}
   152  
   153  	medium := tryToOpenResumableRecorderForAppending(ctx, src, multiPartsObjectOptions, uploader.options)
   154  	return &multiPartsUploaderV2InitializedParts{
   155  		bucketName:              bucketName,
   156  		uploadID:                uploadID,
   157  		multiPartsObjectOptions: multiPartsObjectOptions,
   158  		expiredAt:               expiredAt,
   159  		records:                 records,
   160  		medium:                  medium,
   161  		src:                     src,
   162  	}
   163  }
   164  
   165  func (uploader *multiPartsUploaderV2) UploadPart(ctx context.Context, initialized InitializedParts, part source.Part, options *UploadPartOptions) (UploadedPart, error) {
   166  	initializedParts, ok := initialized.(*multiPartsUploaderV2InitializedParts)
   167  	if !ok {
   168  		return nil, errors.New("unrecognized initialized parts")
   169  	}
   170  	if len(initializedParts.records) > 0 {
   171  		if record, ok := initializedParts.records[part.PartNumber()]; ok {
   172  			if record.offset == part.Offset() && record.size == part.Size() {
   173  				if options != nil && options.OnUploadingProgress != nil {
   174  					options.OnUploadingProgress(&UploadingPartProgress{Uploaded: record.size, PartSize: record.size})
   175  				}
   176  				return multiPartsUploaderV2UploadedPart{
   177  					partNumber: record.partNumber,
   178  					offset:     record.offset,
   179  					size:       record.size,
   180  					etag:       record.etag,
   181  					md5:        record.md5,
   182  				}, nil
   183  			}
   184  		}
   185  	}
   186  	return uploader.uploadPart(ctx, initializedParts, part, options)
   187  }
   188  
   189  func (uploader *multiPartsUploaderV2) uploadPart(ctx context.Context, initialized *multiPartsUploaderV2InitializedParts, part source.Part, options *UploadPartOptions) (UploadedPart, error) {
   190  	apisOptions := apis.Options{
   191  		OverwrittenRegion: initialized.multiPartsObjectOptions.RegionsProvider,
   192  	}
   193  	if options != nil && options.OnUploadingProgress != nil {
   194  		apisOptions.OnRequestProgress = func(uploaded, partSize uint64) {
   195  			options.OnUploadingProgress(&UploadingPartProgress{Uploaded: uploaded, PartSize: partSize})
   196  		}
   197  	}
   198  	upToken, err := getUpToken(uploader.options.Credentials, &initialized.multiPartsObjectOptions.ObjectOptions, uploader.options.UpTokenProvider)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	md5, err := md5FromReadSeeker(part)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	response, err := uploader.storage.ResumableUploadV2UploadPart(ctx, &apis.ResumableUploadV2UploadPartRequest{
   209  		BucketName: initialized.bucketName,
   210  		ObjectName: initialized.multiPartsObjectOptions.ObjectName,
   211  		UploadId:   initialized.uploadID,
   212  		PartNumber: int64(part.PartNumber()),
   213  		Md5:        hex.EncodeToString(md5[:]),
   214  		UpToken:    upToken,
   215  		Body:       internal_io.MakeReadSeekCloserFromReader(part),
   216  	}, &apisOptions)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	if medium := initialized.medium; medium != nil {
   222  		medium.Write(&resumablerecorder.ResumableRecord{
   223  			UploadID:   initialized.uploadID,
   224  			PartID:     response.Etag,
   225  			Offset:     part.Offset(),
   226  			PartSize:   part.Size(),
   227  			PartNumber: part.PartNumber(),
   228  			ExpiredAt:  initialized.expiredAt,
   229  			MD5:        md5,
   230  		})
   231  	}
   232  
   233  	return multiPartsUploaderV2UploadedPart{
   234  		partNumber: part.PartNumber(),
   235  		offset:     part.Offset(),
   236  		size:       part.Size(),
   237  		etag:       response.Etag,
   238  		md5:        md5,
   239  	}, nil
   240  }
   241  
   242  func (uploader *multiPartsUploaderV2) CompleteParts(ctx context.Context, initialized InitializedParts, parts []UploadedPart, returnValue interface{}) error {
   243  	initializedParts, ok := initialized.(*multiPartsUploaderV2InitializedParts)
   244  	if !ok {
   245  		return errors.New("unrecognized initialized parts")
   246  	}
   247  	options := apis.Options{
   248  		OverwrittenRegion: initializedParts.multiPartsObjectOptions.RegionsProvider,
   249  	}
   250  	upToken, err := getUpToken(uploader.options.Credentials, &initializedParts.multiPartsObjectOptions.ObjectOptions, uploader.options.UpTokenProvider)
   251  	if err != nil {
   252  		return err
   253  	}
   254  
   255  	completedParts := make(resumable_upload_v2_complete_multipart_upload.Parts, 0, len(parts))
   256  	for _, part := range parts {
   257  		uploadedPart, ok := part.(multiPartsUploaderV2UploadedPart)
   258  		if !ok {
   259  			return errors.New("unrecognized uploaded part")
   260  		}
   261  		completedParts = append(completedParts, resumable_upload_v2_complete_multipart_upload.PartInfo{
   262  			PartNumber: int64(uploadedPart.partNumber),
   263  			Etag:       uploadedPart.etag,
   264  		})
   265  	}
   266  
   267  	metadata := make(map[string]string)
   268  	for k, v := range initializedParts.multiPartsObjectOptions.Metadata {
   269  		metadata[normalizeMetadataKey(k)] = v
   270  	}
   271  
   272  	customVars := make(map[string]string)
   273  	for k, v := range initializedParts.multiPartsObjectOptions.CustomVars {
   274  		customVars[normalizeCustomVarKey(k)] = v
   275  	}
   276  
   277  	_, err = uploader.storage.ResumableUploadV2CompleteMultipartUpload(ctx, &apis.ResumableUploadV2CompleteMultipartUploadRequest{
   278  		BucketName:   initializedParts.bucketName,
   279  		ObjectName:   initializedParts.multiPartsObjectOptions.ObjectName,
   280  		UploadId:     initializedParts.uploadID,
   281  		UpToken:      upToken,
   282  		Parts:        completedParts,
   283  		FileName:     initializedParts.multiPartsObjectOptions.FileName,
   284  		MimeType:     initializedParts.multiPartsObjectOptions.ContentType,
   285  		Metadata:     metadata,
   286  		CustomVars:   customVars,
   287  		ResponseBody: returnValue,
   288  	}, &options)
   289  	if err == nil || !retrier.IsErrorRetryable(err) {
   290  		if medium := initializedParts.medium; medium != nil {
   291  			medium.Close()
   292  		}
   293  		initializedParts.medium = nil
   294  		tryToDeleteResumableRecorderMedium(ctx, initializedParts.src, initializedParts.multiPartsObjectOptions, uploader.options)
   295  	}
   296  	return err
   297  }
   298  
   299  func (uploader *multiPartsUploaderV2) MultiPartsUploaderOptions() *MultiPartsUploaderOptions {
   300  	return uploader.options
   301  }
   302  
   303  func (initialized *multiPartsUploaderV2InitializedParts) Close() error {
   304  	if initialized.medium != nil {
   305  		return initialized.medium.Close()
   306  	}
   307  	return nil
   308  }
   309  
   310  func (uploadedPart multiPartsUploaderV2UploadedPart) Offset() uint64 {
   311  	return uploadedPart.offset
   312  }
   313  
   314  func (uploadedPart multiPartsUploaderV2UploadedPart) PartNumber() uint64 {
   315  	return uploadedPart.partNumber
   316  }
   317  
   318  func (uploadedPart multiPartsUploaderV2UploadedPart) PartSize() uint64 {
   319  	return uploadedPart.size
   320  }
   321  
   322  func (uploadedPart multiPartsUploaderV2UploadedPart) Etag() string {
   323  	return uploadedPart.etag
   324  }
   325  
   326  func (uploadedPart multiPartsUploaderV2UploadedPart) MD5() [md5.Size]byte {
   327  	return uploadedPart.md5
   328  }
   329  
   330  func md5FromReadSeeker(r io.ReadSeeker) (md5Result [md5.Size]byte, err error) {
   331  	var offset int64
   332  
   333  	offset, err = r.Seek(0, io.SeekCurrent)
   334  	if err != nil {
   335  		return
   336  	}
   337  	hasher := md5.New()
   338  	if _, err = io.Copy(hasher, r); err != nil {
   339  		return
   340  	}
   341  	if _, err = r.Seek(offset, io.SeekStart); err != nil {
   342  		return
   343  	}
   344  	copy(md5Result[:], hasher.Sum(nil))
   345  	return
   346  }