github.com/qiniu/go-sdk/v7@v7.27.0/storage/resume_uploader_apis.go (about) 1 package storage 2 3 import ( 4 "context" 5 "io" 6 "strings" 7 "time" 8 9 internal_io "github.com/qiniu/go-sdk/v7/internal/io" 10 "github.com/qiniu/go-sdk/v7/storagev2/apis" 11 "github.com/qiniu/go-sdk/v7/storagev2/apis/resumable_upload_v2_complete_multipart_upload" 12 "github.com/qiniu/go-sdk/v7/storagev2/region" 13 "github.com/qiniu/go-sdk/v7/storagev2/uptoken" 14 ) 15 16 type resumeUploaderAPIs struct { 17 cfg *Config 18 storage *apis.Storage 19 } 20 21 // BlkputRet 表示分片上传每个片上传完毕的返回值 22 type BlkputRet struct { 23 Ctx string `json:"ctx"` 24 Checksum string `json:"checksum"` 25 Crc32 uint32 `json:"crc32"` 26 Offset uint32 `json:"offset"` 27 Host string `json:"host"` 28 ExpiredAt int64 `json:"expired_at"` 29 chunkSize int 30 fileOffset int64 31 blkIdx int 32 } 33 34 func (p *resumeUploaderAPIs) mkBlk( 35 ctx context.Context, upToken string, upEndpoints region.EndpointsProvider, 36 ret *BlkputRet, blockSize int64, body io.Reader, size int64, 37 ) error { 38 response, err := p.storage.ResumableUploadV1MakeBlock( 39 ctx, 40 &apis.ResumableUploadV1MakeBlockRequest{ 41 BlockSize: blockSize, 42 UpToken: uptoken.NewParser(upToken), 43 Body: internal_io.MakeReadSeekCloserFromReader(body), 44 }, 45 makeApiOptionsFromUpEndpoints(upEndpoints), 46 ) 47 if err != nil { 48 return err 49 } 50 *ret = BlkputRet{ 51 Ctx: response.Ctx, 52 Checksum: response.Checksum, 53 Crc32: uint32(response.Crc32), 54 Offset: uint32(response.Offset), 55 Host: response.Host, 56 ExpiredAt: response.ExpiredAt, 57 } 58 return nil 59 } 60 61 func (p *resumeUploaderAPIs) bput( 62 ctx context.Context, upToken string, upEndpoints region.EndpointsProvider, 63 ret *BlkputRet, body io.Reader, size int64, 64 ) error { 65 response, err := p.storage.ResumableUploadV1Bput( 66 ctx, 67 &apis.ResumableUploadV1BputRequest{ 68 Ctx: ret.Ctx, 69 ChunkOffset: int64(ret.Offset), 70 UpToken: uptoken.NewParser(upToken), 71 Body: internal_io.MakeReadSeekCloserFromReader(body), 72 }, 73 makeApiOptionsFromUpEndpoints(upEndpoints), 74 ) 75 if err != nil { 76 return err 77 } 78 *ret = BlkputRet{ 79 Ctx: response.Ctx, 80 Checksum: response.Checksum, 81 Crc32: uint32(response.Crc32), 82 Offset: uint32(response.Offset), 83 Host: response.Host, 84 ExpiredAt: response.ExpiredAt, 85 } 86 return nil 87 } 88 89 // RputExtra 表示分片上传额外可以指定的参数 90 type RputExtra struct { 91 Recorder Recorder // 可选。上传进度记录 92 93 // 可选。 94 // 用户自定义参数:key 以"x:"开头,而且 value 不能为空 eg: key为x:qqq 95 // 自定义 meta:key 以"x-qn-meta-"开头,而且 value 不能为空 eg: key为x-qn-meta-aaa 96 Params map[string]string 97 UpHost string 98 MimeType string // 可选。 99 ChunkSize int // 可选。每次上传的Chunk大小 100 TryTimes int // 可选。尝试次数 101 HostFreezeDuration time.Duration // 可选。主备域名冻结时间(默认:600s),当一个域名请求失败(单个域名会被重试 TryTimes 次),会被冻结一段时间,使用备用域名进行重试,在冻结时间内,域名不能被使用,当一个操作中所有域名竣备冻结操作不在进行重试,返回最后一次操作的错误。 102 Progresses []BlkputRet // 可选。上传进度 103 Notify func(blkIdx int, blkSize int, ret *BlkputRet) // 可选。进度提示(注意多个block是并行传输的) 104 NotifyErr func(blkIdx int, blkSize int, err error) 105 } 106 107 func (extra *RputExtra) init() { 108 if extra.ChunkSize == 0 { 109 extra.ChunkSize = settings.ChunkSize 110 } 111 if extra.TryTimes == 0 { 112 extra.TryTimes = settings.TryTimes 113 } 114 if extra.HostFreezeDuration <= 0 { 115 extra.HostFreezeDuration = 10 * 60 * time.Second 116 } 117 if extra.Notify == nil { 118 extra.Notify = func(blkIdx, blkSize int, ret *BlkputRet) {} 119 } 120 if extra.NotifyErr == nil { 121 extra.NotifyErr = func(blkIdx, blkSize int, err error) {} 122 } 123 } 124 125 func (p *resumeUploaderAPIs) mkfile( 126 ctx context.Context, upToken string, upEndpoints region.EndpointsProvider, 127 ret interface{}, key string, hasKey bool, fsize int64, extra *RputExtra, 128 ) error { 129 if extra == nil { 130 extra = &RputExtra{} 131 } 132 ctxs := make([]string, len(extra.Progresses)) 133 for i, progress := range extra.Progresses { 134 ctxs[i] = progress.Ctx 135 } 136 _, err := p.storage.ResumableUploadV1MakeFile( 137 ctx, 138 &apis.ResumableUploadV1MakeFileRequest{ 139 Size: fsize, 140 ObjectName: makeKeyForUploading(key, hasKey), 141 MimeType: extra.MimeType, 142 CustomData: makeCustomData(extra.Params), 143 UpToken: uptoken.NewParser(upToken), 144 Body: internal_io.MakeReadSeekCloserFromReader(strings.NewReader(strings.Join(ctxs, ","))), 145 ResponseBody: ret, 146 }, 147 makeApiOptionsFromUpEndpoints(upEndpoints), 148 ) 149 return err 150 } 151 152 // InitPartsRet 表示分片上传 v2 初始化完毕的返回值 153 type InitPartsRet struct { 154 UploadID string `json:"uploadId"` 155 ExpireAt int64 `json:"expireAt"` 156 } 157 158 func (p *resumeUploaderAPIs) initParts( 159 ctx context.Context, upToken string, upEndpoints region.EndpointsProvider, 160 bucket, key string, hasKey bool, ret *InitPartsRet, 161 ) error { 162 response, err := p.storage.ResumableUploadV2InitiateMultipartUpload( 163 ctx, 164 &apis.ResumableUploadV2InitiateMultipartUploadRequest{ 165 BucketName: bucket, 166 ObjectName: makeKeyForUploading(key, hasKey), 167 UpToken: uptoken.NewParser(upToken), 168 }, 169 makeApiOptionsFromUpEndpoints(upEndpoints), 170 ) 171 if err != nil { 172 return err 173 } 174 *ret = InitPartsRet{ 175 UploadID: response.UploadId, 176 ExpireAt: response.ExpiredAt, 177 } 178 return nil 179 } 180 181 // UploadPartsRet 表示分片上传 v2 每个片上传完毕的返回值 182 type UploadPartsRet struct { 183 Etag string `json:"etag"` 184 MD5 string `json:"md5"` 185 } 186 187 func (p *resumeUploaderAPIs) uploadParts( 188 ctx context.Context, upToken string, upEndpoints region.EndpointsProvider, 189 bucket, key string, hasKey bool, uploadId string, partNumber int64, partMD5 string, ret *UploadPartsRet, body io.Reader, size int64, 190 ) error { 191 response, err := p.storage.ResumableUploadV2UploadPart( 192 ctx, 193 &apis.ResumableUploadV2UploadPartRequest{ 194 BucketName: bucket, 195 ObjectName: makeKeyForUploading(key, hasKey), 196 UploadId: uploadId, 197 PartNumber: partNumber, 198 Md5: partMD5, 199 UpToken: uptoken.NewParser(upToken), 200 Body: internal_io.MakeReadSeekCloserFromLimitedReader(body, size), 201 }, 202 makeApiOptionsFromUpEndpoints(upEndpoints), 203 ) 204 if err != nil { 205 return err 206 } 207 *ret = UploadPartsRet{ 208 Etag: response.Etag, 209 MD5: response.Md5, 210 } 211 return nil 212 } 213 214 type UploadPartInfo struct { 215 Etag string `json:"etag"` 216 PartNumber int64 `json:"partNumber"` 217 partSize int 218 fileOffset int64 219 } 220 221 // RputV2Extra 表示分片上传 v2 额外可以指定的参数 222 type RputV2Extra struct { 223 Recorder Recorder // 可选。上传进度记录 224 Metadata map[string]string // 可选。用户自定义文件 metadata 信息 225 CustomVars map[string]string // 可选。用户自定义参数,以"x:"开头,而且值不能为空,否则忽略 226 UpHost string 227 MimeType string // 可选。 228 PartSize int64 // 可选。每次上传的块大小 229 TryTimes int // 可选。尝试次数 230 HostFreezeDuration time.Duration // 可选。主备域名冻结时间(默认:600s),当一个域名请求失败(单个域名会被重试 TryTimes 次),会被冻结一段时间,使用备用域名进行重试,在冻结时间内,域名不能被使用,当一个操作中所有域名竣备冻结操作不在进行重试,返回最后一次操作的错误。 231 Progresses []UploadPartInfo // 上传进度 232 Notify func(partNumber int64, ret *UploadPartsRet) // 可选。进度提示(注意多个block是并行传输的) 233 NotifyErr func(partNumber int64, err error) 234 } 235 236 func (extra *RputV2Extra) init() { 237 if extra.PartSize == 0 { 238 extra.PartSize = settings.PartSize 239 } 240 if extra.TryTimes == 0 { 241 extra.TryTimes = settings.TryTimes 242 } 243 if extra.HostFreezeDuration <= 0 { 244 extra.HostFreezeDuration = 10 * 60 * time.Second 245 } 246 if extra.Notify == nil { 247 extra.Notify = func(partNumber int64, ret *UploadPartsRet) {} 248 } 249 if extra.NotifyErr == nil { 250 extra.NotifyErr = func(partNumber int64, err error) {} 251 } 252 } 253 254 func (p *resumeUploaderAPIs) completeParts( 255 ctx context.Context, upToken string, upEndpoints region.EndpointsProvider, ret interface{}, 256 bucket, key string, hasKey bool, uploadId string, extra *RputV2Extra, 257 ) error { 258 parts := make([]resumable_upload_v2_complete_multipart_upload.PartInfo, 0, len(extra.Progresses)) 259 for i := range extra.Progresses { 260 parts = append(parts, resumable_upload_v2_complete_multipart_upload.PartInfo{ 261 PartNumber: extra.Progresses[i].PartNumber, 262 Etag: extra.Progresses[i].Etag, 263 }) 264 } 265 customVars := make(map[string]string, len(extra.CustomVars)) 266 for k, v := range extra.CustomVars { 267 if strings.HasPrefix(k, "x:") && v != "" { 268 customVars[k] = v 269 } 270 } 271 _, err := p.storage.ResumableUploadV2CompleteMultipartUpload( 272 ctx, 273 &apis.ResumableUploadV2CompleteMultipartUploadRequest{ 274 BucketName: bucket, 275 ObjectName: makeKeyForUploading(key, hasKey), 276 UploadId: uploadId, 277 UpToken: uptoken.NewParser(upToken), 278 Parts: parts, 279 MimeType: extra.MimeType, 280 Metadata: extra.Metadata, 281 CustomVars: customVars, 282 ResponseBody: ret, 283 }, 284 makeApiOptionsFromUpEndpoints(upEndpoints), 285 ) 286 return err 287 } 288 289 func (p *resumeUploaderAPIs) upHost(ak, bucket string) (upHost string, err error) { 290 return getUpHost(p.cfg, 0, 0, ak, bucket) 291 } 292 293 func makeEndpointsFromUpHost(upHost string) region.EndpointsProvider { 294 if upHost != "" { 295 return ®ion.Endpoints{Preferred: []string{upHost}} 296 } 297 return nil 298 } 299 300 func makeApiOptionsFromUpEndpoints(upEndpoints region.EndpointsProvider) *apis.Options { 301 if upEndpoints != nil { 302 return &apis.Options{ 303 OverwrittenEndpoints: upEndpoints, 304 } 305 } 306 return nil 307 } 308 309 func makeKeyForUploading(key string, hasKey bool) *string { 310 if hasKey { 311 return &key 312 } else { 313 return nil 314 } 315 } 316 317 type blkputRets []BlkputRet 318 319 func (rets blkputRets) Len() int { 320 return len(rets) 321 } 322 323 func (rets blkputRets) Less(i, j int) bool { 324 return rets[i].blkIdx < rets[j].blkIdx 325 } 326 327 func (rets blkputRets) Swap(i, j int) { 328 rets[i], rets[j] = rets[j], rets[i] 329 } 330 331 type uploadPartInfos []UploadPartInfo 332 333 func (infos uploadPartInfos) Len() int { 334 return len(infos) 335 } 336 337 func (infos uploadPartInfos) Less(i, j int) bool { 338 return infos[i].PartNumber < infos[j].PartNumber 339 } 340 341 func (infos uploadPartInfos) Swap(i, j int) { 342 infos[i], infos[j] = infos[j], infos[i] 343 }