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 }