github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/openstack/obs/transfer.go (about) 1 // Copyright 2019 Huawei Technologies Co.,Ltd. 2 // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 // this file except in compliance with the License. You may obtain a copy of the 4 // License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software distributed 9 // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 // specific language governing permissions and limitations under the License. 12 13 package obs 14 15 import ( 16 "bufio" 17 "encoding/xml" 18 "errors" 19 "fmt" 20 "io" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "sync" 25 "sync/atomic" 26 "syscall" 27 ) 28 29 var errAbort = errors.New("AbortError") 30 31 // FileStatus defines the upload file properties 32 type FileStatus struct { 33 XMLName xml.Name `xml:"FileInfo"` 34 LastModified int64 `xml:"LastModified"` 35 Size int64 `xml:"Size"` 36 } 37 38 // UploadPartInfo defines the upload part properties 39 type UploadPartInfo struct { 40 XMLName xml.Name `xml:"UploadPart"` 41 PartNumber int `xml:"PartNumber"` 42 Etag string `xml:"Etag"` 43 PartSize int64 `xml:"PartSize"` 44 Offset int64 `xml:"Offset"` 45 IsCompleted bool `xml:"IsCompleted"` 46 } 47 48 // UploadCheckpoint defines the upload checkpoint file properties 49 type UploadCheckpoint struct { 50 XMLName xml.Name `xml:"UploadFileCheckpoint"` 51 Bucket string `xml:"Bucket"` 52 Key string `xml:"Key"` 53 UploadId string `xml:"UploadId,omitempty"` 54 UploadFile string `xml:"FileUrl"` 55 FileInfo FileStatus `xml:"FileInfo"` 56 UploadParts []UploadPartInfo `xml:"UploadParts>UploadPart"` 57 } 58 59 func (ufc *UploadCheckpoint) isValid(bucket, key, uploadFile string, fileStat os.FileInfo) bool { 60 if ufc.Bucket != bucket || ufc.Key != key || ufc.UploadFile != uploadFile { 61 doLog(LEVEL_INFO, "Checkpoint file is invalid, the bucketName or objectKey or uploadFile was changed. clear the record.") 62 return false 63 } 64 65 if ufc.FileInfo.Size != fileStat.Size() || ufc.FileInfo.LastModified != fileStat.ModTime().Unix() { 66 doLog(LEVEL_INFO, "Checkpoint file is invalid, the uploadFile was changed. clear the record.") 67 return false 68 } 69 70 if ufc.UploadId == "" { 71 doLog(LEVEL_INFO, "UploadId is invalid. clear the record.") 72 return false 73 } 74 75 return true 76 } 77 78 type uploadPartTask struct { 79 UploadPartInput 80 obsClient *ObsClient 81 abort *int32 82 extensions []extensionOptions 83 enableCheckpoint bool 84 } 85 86 func (task *uploadPartTask) Run() interface{} { 87 if atomic.LoadInt32(task.abort) == 1 { 88 return errAbort 89 } 90 91 input := &UploadPartInput{} 92 input.Bucket = task.Bucket 93 input.Key = task.Key 94 input.PartNumber = task.PartNumber 95 input.UploadId = task.UploadId 96 input.SseHeader = task.SseHeader 97 input.SourceFile = task.SourceFile 98 input.Offset = task.Offset 99 input.PartSize = task.PartSize 100 extensions := task.extensions 101 102 var output *UploadPartOutput 103 var err error 104 if extensions != nil { 105 output, err = task.obsClient.UploadPart(input, extensions...) 106 } else { 107 output, err = task.obsClient.UploadPart(input) 108 } 109 110 if err == nil { 111 if output.ETag == "" { 112 doLog(LEVEL_WARN, "Get invalid etag value after uploading part [%d].", task.PartNumber) 113 if !task.enableCheckpoint { 114 atomic.CompareAndSwapInt32(task.abort, 0, 1) 115 doLog(LEVEL_WARN, "Task is aborted, part number is [%d]", task.PartNumber) 116 } 117 return fmt.Errorf("get invalid etag value after uploading part [%d]", task.PartNumber) 118 } 119 return output 120 } else if obsError, ok := err.(ObsError); ok && obsError.StatusCode >= 400 && obsError.StatusCode < 500 { 121 atomic.CompareAndSwapInt32(task.abort, 0, 1) 122 doLog(LEVEL_WARN, "Task is aborted, part number is [%d]", task.PartNumber) 123 } 124 return err 125 } 126 127 func loadCheckpointFile(checkpointFile string, result interface{}) error { 128 ret, err := ioutil.ReadFile(checkpointFile) 129 if err != nil { 130 return err 131 } 132 if len(ret) == 0 { 133 return nil 134 } 135 return xml.Unmarshal(ret, result) 136 } 137 138 func updateCheckpointFile(fc interface{}, checkpointFilePath string) error { 139 result, err := xml.Marshal(fc) 140 if err != nil { 141 return err 142 } 143 err = ioutil.WriteFile(checkpointFilePath, result, 0666) 144 return err 145 } 146 147 func getCheckpointFile(ufc *UploadCheckpoint, uploadFileStat os.FileInfo, input *UploadFileInput, obsClient *ObsClient, extensions []extensionOptions) (needCheckpoint bool, err error) { 148 checkpointFilePath := input.CheckpointFile 149 checkpointFileStat, err := os.Stat(checkpointFilePath) 150 if err != nil { 151 doLog(LEVEL_DEBUG, fmt.Sprintf("Stat checkpoint file failed with error: [%v].", err)) 152 return true, nil 153 } 154 if checkpointFileStat.IsDir() { 155 doLog(LEVEL_ERROR, "Checkpoint file can not be a folder.") 156 return false, errors.New("checkpoint file can not be a folder") 157 } 158 err = loadCheckpointFile(checkpointFilePath, ufc) 159 if err != nil { 160 doLog(LEVEL_WARN, fmt.Sprintf("Load checkpoint file failed with error: [%v].", err)) 161 return true, nil 162 } else if !ufc.isValid(input.Bucket, input.Key, input.UploadFile, uploadFileStat) { 163 if ufc.Bucket != "" && ufc.Key != "" && ufc.UploadId != "" { 164 _err := abortTask(ufc.Bucket, ufc.Key, ufc.UploadId, obsClient, extensions) 165 if _err != nil { 166 doLog(LEVEL_WARN, "Failed to abort upload task [%s].", ufc.UploadId) 167 } 168 } 169 _err := os.Remove(checkpointFilePath) 170 if _err != nil { 171 doLog(LEVEL_WARN, fmt.Sprintf("Failed to remove checkpoint file with error: [%v].", _err)) 172 } 173 } else { 174 return false, nil 175 } 176 177 return true, nil 178 } 179 180 func prepareUpload(ufc *UploadCheckpoint, uploadFileStat os.FileInfo, input *UploadFileInput, obsClient *ObsClient, extensions []extensionOptions) error { 181 initiateInput := &InitiateMultipartUploadInput{} 182 initiateInput.ObjectOperationInput = input.ObjectOperationInput 183 initiateInput.ContentType = input.ContentType 184 initiateInput.EncodingType = input.EncodingType 185 var output *InitiateMultipartUploadOutput 186 var err error 187 if extensions != nil { 188 output, err = obsClient.InitiateMultipartUpload(initiateInput, extensions...) 189 } else { 190 output, err = obsClient.InitiateMultipartUpload(initiateInput) 191 } 192 if err != nil { 193 return err 194 } 195 196 ufc.Bucket = input.Bucket 197 ufc.Key = input.Key 198 ufc.UploadFile = input.UploadFile 199 ufc.FileInfo = FileStatus{} 200 ufc.FileInfo.Size = uploadFileStat.Size() 201 ufc.FileInfo.LastModified = uploadFileStat.ModTime().Unix() 202 ufc.UploadId = output.UploadId 203 204 err = sliceFile(input.PartSize, ufc) 205 return err 206 } 207 208 func sliceFile(partSize int64, ufc *UploadCheckpoint) error { 209 fileSize := ufc.FileInfo.Size 210 cnt := fileSize / partSize 211 if cnt >= 10000 { 212 partSize = fileSize / 10000 213 if fileSize%10000 != 0 { 214 partSize++ 215 } 216 cnt = fileSize / partSize 217 } 218 if fileSize%partSize != 0 { 219 cnt++ 220 } 221 222 if partSize > MAX_PART_SIZE { 223 doLog(LEVEL_ERROR, "The source upload file is too large") 224 return fmt.Errorf("The source upload file is too large") 225 } 226 227 if cnt == 0 { 228 uploadPart := UploadPartInfo{} 229 uploadPart.PartNumber = 1 230 ufc.UploadParts = []UploadPartInfo{uploadPart} 231 } else { 232 uploadParts := make([]UploadPartInfo, 0, cnt) 233 var i int64 234 for i = 0; i < cnt; i++ { 235 uploadPart := UploadPartInfo{} 236 uploadPart.PartNumber = int(i) + 1 237 uploadPart.PartSize = partSize 238 uploadPart.Offset = i * partSize 239 uploadParts = append(uploadParts, uploadPart) 240 } 241 if value := fileSize % partSize; value != 0 { 242 uploadParts[cnt-1].PartSize = value 243 } 244 ufc.UploadParts = uploadParts 245 } 246 return nil 247 } 248 249 func abortTask(bucket, key, uploadID string, obsClient *ObsClient, extensions []extensionOptions) error { 250 input := &AbortMultipartUploadInput{} 251 input.Bucket = bucket 252 input.Key = key 253 input.UploadId = uploadID 254 if extensions != nil { 255 _, err := obsClient.AbortMultipartUpload(input, extensions...) 256 return err 257 } 258 _, err := obsClient.AbortMultipartUpload(input) 259 return err 260 } 261 262 func handleUploadFileResult(uploadPartError error, ufc *UploadCheckpoint, enableCheckpoint bool, obsClient *ObsClient, extensions []extensionOptions) error { 263 if uploadPartError != nil { 264 if enableCheckpoint { 265 return uploadPartError 266 } 267 _err := abortTask(ufc.Bucket, ufc.Key, ufc.UploadId, obsClient, extensions) 268 if _err != nil { 269 doLog(LEVEL_WARN, "Failed to abort task [%s].", ufc.UploadId) 270 } 271 return uploadPartError 272 } 273 return nil 274 } 275 276 func completeParts(ufc *UploadCheckpoint, enableCheckpoint bool, checkpointFilePath string, obsClient *ObsClient, encodingType string, extensions []extensionOptions) (output *CompleteMultipartUploadOutput, err error) { 277 completeInput := &CompleteMultipartUploadInput{} 278 completeInput.Bucket = ufc.Bucket 279 completeInput.Key = ufc.Key 280 completeInput.UploadId = ufc.UploadId 281 completeInput.EncodingType = encodingType 282 parts := make([]Part, 0, len(ufc.UploadParts)) 283 for _, uploadPart := range ufc.UploadParts { 284 part := Part{} 285 part.PartNumber = uploadPart.PartNumber 286 part.ETag = uploadPart.Etag 287 parts = append(parts, part) 288 } 289 completeInput.Parts = parts 290 var completeOutput *CompleteMultipartUploadOutput 291 if extensions != nil { 292 completeOutput, err = obsClient.CompleteMultipartUpload(completeInput, extensions...) 293 } else { 294 completeOutput, err = obsClient.CompleteMultipartUpload(completeInput) 295 } 296 297 if err == nil { 298 if enableCheckpoint { 299 _err := os.Remove(checkpointFilePath) 300 if _err != nil { 301 doLog(LEVEL_WARN, "Upload file successfully, but remove checkpoint file failed with error [%v].", _err) 302 } 303 } 304 return completeOutput, err 305 } 306 if !enableCheckpoint { 307 _err := abortTask(ufc.Bucket, ufc.Key, ufc.UploadId, obsClient, extensions) 308 if _err != nil { 309 doLog(LEVEL_WARN, "Failed to abort task [%s].", ufc.UploadId) 310 } 311 } 312 return completeOutput, err 313 } 314 315 func (obsClient ObsClient) resumeUpload(input *UploadFileInput, extensions []extensionOptions) (output *CompleteMultipartUploadOutput, err error) { 316 uploadFileStat, err := os.Stat(input.UploadFile) 317 if err != nil { 318 doLog(LEVEL_ERROR, fmt.Sprintf("Failed to stat uploadFile with error: [%v].", err)) 319 return nil, err 320 } 321 if uploadFileStat.IsDir() { 322 doLog(LEVEL_ERROR, "UploadFile can not be a folder.") 323 return nil, errors.New("uploadFile can not be a folder") 324 } 325 326 ufc := &UploadCheckpoint{} 327 328 var needCheckpoint = true 329 var checkpointFilePath = input.CheckpointFile 330 var enableCheckpoint = input.EnableCheckpoint 331 if enableCheckpoint { 332 needCheckpoint, err = getCheckpointFile(ufc, uploadFileStat, input, &obsClient, extensions) 333 if err != nil { 334 return nil, err 335 } 336 } 337 if needCheckpoint { 338 err = prepareUpload(ufc, uploadFileStat, input, &obsClient, extensions) 339 if err != nil { 340 return nil, err 341 } 342 343 if enableCheckpoint { 344 err = updateCheckpointFile(ufc, checkpointFilePath) 345 if err != nil { 346 doLog(LEVEL_ERROR, "Failed to update checkpoint file with error [%v].", err) 347 _err := abortTask(ufc.Bucket, ufc.Key, ufc.UploadId, &obsClient, extensions) 348 if _err != nil { 349 doLog(LEVEL_WARN, "Failed to abort task [%s].", ufc.UploadId) 350 } 351 return nil, err 352 } 353 } 354 } 355 356 uploadPartError := obsClient.uploadPartConcurrent(ufc, checkpointFilePath, input, extensions) 357 err = handleUploadFileResult(uploadPartError, ufc, enableCheckpoint, &obsClient, extensions) 358 if err != nil { 359 return nil, err 360 } 361 362 completeOutput, err := completeParts(ufc, enableCheckpoint, checkpointFilePath, &obsClient, input.EncodingType, extensions) 363 364 return completeOutput, err 365 } 366 367 func handleUploadTaskResult(result interface{}, ufc *UploadCheckpoint, partNum int, enableCheckpoint bool, checkpointFilePath string, lock *sync.Mutex) (err error) { 368 if uploadPartOutput, ok := result.(*UploadPartOutput); ok { 369 lock.Lock() 370 defer lock.Unlock() 371 ufc.UploadParts[partNum-1].Etag = uploadPartOutput.ETag 372 ufc.UploadParts[partNum-1].IsCompleted = true 373 if enableCheckpoint { 374 _err := updateCheckpointFile(ufc, checkpointFilePath) 375 if _err != nil { 376 doLog(LEVEL_WARN, "Failed to update checkpoint file with error [%v].", _err) 377 } 378 } 379 } else if result != errAbort { 380 if _err, ok := result.(error); ok { 381 err = _err 382 } 383 } 384 return 385 } 386 387 func (obsClient ObsClient) uploadPartConcurrent(ufc *UploadCheckpoint, checkpointFilePath string, input *UploadFileInput, extensions []extensionOptions) error { 388 pool := NewRoutinePool(input.TaskNum, MAX_PART_NUM) 389 var uploadPartError atomic.Value 390 var errFlag int32 391 var abort int32 392 lock := new(sync.Mutex) 393 for _, uploadPart := range ufc.UploadParts { 394 if atomic.LoadInt32(&abort) == 1 { 395 break 396 } 397 if uploadPart.IsCompleted { 398 continue 399 } 400 task := uploadPartTask{ 401 UploadPartInput: UploadPartInput{ 402 Bucket: ufc.Bucket, 403 Key: ufc.Key, 404 PartNumber: uploadPart.PartNumber, 405 UploadId: ufc.UploadId, 406 SseHeader: input.SseHeader, 407 SourceFile: input.UploadFile, 408 Offset: uploadPart.Offset, 409 PartSize: uploadPart.PartSize, 410 }, 411 obsClient: &obsClient, 412 abort: &abort, 413 extensions: extensions, 414 enableCheckpoint: input.EnableCheckpoint, 415 } 416 pool.ExecuteFunc(func() interface{} { 417 result := task.Run() 418 err := handleUploadTaskResult(result, ufc, task.PartNumber, input.EnableCheckpoint, input.CheckpointFile, lock) 419 if err != nil && atomic.CompareAndSwapInt32(&errFlag, 0, 1) { 420 uploadPartError.Store(err) 421 } 422 return nil 423 }) 424 } 425 pool.ShutDown() 426 if err, ok := uploadPartError.Load().(error); ok { 427 return err 428 } 429 return nil 430 } 431 432 // ObjectInfo defines download object info 433 type ObjectInfo struct { 434 XMLName xml.Name `xml:"ObjectInfo"` 435 LastModified int64 `xml:"LastModified"` 436 Size int64 `xml:"Size"` 437 ETag string `xml:"ETag"` 438 } 439 440 // TempFileInfo defines temp download file properties 441 type TempFileInfo struct { 442 XMLName xml.Name `xml:"TempFileInfo"` 443 TempFileUrl string `xml:"TempFileUrl"` 444 Size int64 `xml:"Size"` 445 } 446 447 // DownloadPartInfo defines download part properties 448 type DownloadPartInfo struct { 449 XMLName xml.Name `xml:"DownloadPart"` 450 PartNumber int64 `xml:"PartNumber"` 451 RangeEnd int64 `xml:"RangeEnd"` 452 Offset int64 `xml:"Offset"` 453 IsCompleted bool `xml:"IsCompleted"` 454 } 455 456 // DownloadCheckpoint defines download checkpoint file properties 457 type DownloadCheckpoint struct { 458 XMLName xml.Name `xml:"DownloadFileCheckpoint"` 459 Bucket string `xml:"Bucket"` 460 Key string `xml:"Key"` 461 VersionId string `xml:"VersionId,omitempty"` 462 DownloadFile string `xml:"FileUrl"` 463 ObjectInfo ObjectInfo `xml:"ObjectInfo"` 464 TempFileInfo TempFileInfo `xml:"TempFileInfo"` 465 DownloadParts []DownloadPartInfo `xml:"DownloadParts>DownloadPart"` 466 } 467 468 func (dfc *DownloadCheckpoint) isValid(input *DownloadFileInput, output *GetObjectMetadataOutput) bool { 469 if dfc.Bucket != input.Bucket || dfc.Key != input.Key || dfc.VersionId != input.VersionId || dfc.DownloadFile != input.DownloadFile { 470 doLog(LEVEL_INFO, "Checkpoint file is invalid, the bucketName or objectKey or downloadFile was changed. clear the record.") 471 return false 472 } 473 if dfc.ObjectInfo.LastModified != output.LastModified.Unix() || dfc.ObjectInfo.ETag != output.ETag || dfc.ObjectInfo.Size != output.ContentLength { 474 doLog(LEVEL_INFO, "Checkpoint file is invalid, the object info was changed. clear the record.") 475 return false 476 } 477 if dfc.TempFileInfo.Size != output.ContentLength { 478 doLog(LEVEL_INFO, "Checkpoint file is invalid, size was changed. clear the record.") 479 return false 480 } 481 stat, err := os.Stat(dfc.TempFileInfo.TempFileUrl) 482 if err != nil || stat.Size() != dfc.ObjectInfo.Size { 483 doLog(LEVEL_INFO, "Checkpoint file is invalid, the temp download file was changed. clear the record.") 484 return false 485 } 486 487 return true 488 } 489 490 type downloadPartTask struct { 491 GetObjectInput 492 obsClient *ObsClient 493 extensions []extensionOptions 494 abort *int32 495 partNumber int64 496 tempFileURL string 497 enableCheckpoint bool 498 } 499 500 func (task *downloadPartTask) Run() interface{} { 501 if atomic.LoadInt32(task.abort) == 1 { 502 return errAbort 503 } 504 getObjectInput := &GetObjectInput{} 505 getObjectInput.GetObjectMetadataInput = task.GetObjectMetadataInput 506 getObjectInput.IfMatch = task.IfMatch 507 getObjectInput.IfNoneMatch = task.IfNoneMatch 508 getObjectInput.IfModifiedSince = task.IfModifiedSince 509 getObjectInput.IfUnmodifiedSince = task.IfUnmodifiedSince 510 getObjectInput.RangeStart = task.RangeStart 511 getObjectInput.RangeEnd = task.RangeEnd 512 513 var output *GetObjectOutput 514 var err error 515 if task.extensions != nil { 516 output, err = task.obsClient.GetObject(getObjectInput, task.extensions...) 517 } else { 518 output, err = task.obsClient.GetObject(getObjectInput) 519 } 520 521 if err == nil { 522 defer func() { 523 errMsg := output.Body.Close() 524 if errMsg != nil { 525 doLog(LEVEL_WARN, "Failed to close response body.") 526 } 527 }() 528 _err := updateDownloadFile(task.tempFileURL, task.RangeStart, output) 529 if _err != nil { 530 if !task.enableCheckpoint { 531 atomic.CompareAndSwapInt32(task.abort, 0, 1) 532 doLog(LEVEL_WARN, "Task is aborted, part number is [%d]", task.partNumber) 533 } 534 return _err 535 } 536 return output 537 } else if obsError, ok := err.(ObsError); ok && obsError.StatusCode >= 400 && obsError.StatusCode < 500 { 538 atomic.CompareAndSwapInt32(task.abort, 0, 1) 539 doLog(LEVEL_WARN, "Task is aborted, part number is [%d]", task.partNumber) 540 } 541 return err 542 } 543 544 func getObjectInfo(input *DownloadFileInput, obsClient *ObsClient, extensions []extensionOptions) (getObjectmetaOutput *GetObjectMetadataOutput, err error) { 545 if extensions != nil { 546 getObjectmetaOutput, err = obsClient.GetObjectMetadata(&input.GetObjectMetadataInput, extensions...) 547 } else { 548 getObjectmetaOutput, err = obsClient.GetObjectMetadata(&input.GetObjectMetadataInput) 549 } 550 551 return 552 } 553 554 func getDownloadCheckpointFile(dfc *DownloadCheckpoint, input *DownloadFileInput, output *GetObjectMetadataOutput) (needCheckpoint bool, err error) { 555 checkpointFilePath := input.CheckpointFile 556 checkpointFileStat, err := os.Stat(checkpointFilePath) 557 if err != nil { 558 doLog(LEVEL_DEBUG, fmt.Sprintf("Stat checkpoint file failed with error: [%v].", err)) 559 return true, nil 560 } 561 if checkpointFileStat.IsDir() { 562 doLog(LEVEL_ERROR, "Checkpoint file can not be a folder.") 563 return false, errors.New("checkpoint file can not be a folder") 564 } 565 err = loadCheckpointFile(checkpointFilePath, dfc) 566 if err != nil { 567 doLog(LEVEL_WARN, fmt.Sprintf("Load checkpoint file failed with error: [%v].", err)) 568 return true, nil 569 } else if !dfc.isValid(input, output) { 570 if dfc.TempFileInfo.TempFileUrl != "" { 571 _err := os.Remove(dfc.TempFileInfo.TempFileUrl) 572 if _err != nil { 573 doLog(LEVEL_WARN, "Failed to remove temp download file with error [%v].", _err) 574 } 575 } 576 _err := os.Remove(checkpointFilePath) 577 if _err != nil { 578 doLog(LEVEL_WARN, "Failed to remove checkpoint file with error [%v].", _err) 579 } 580 } else { 581 return false, nil 582 } 583 584 return true, nil 585 } 586 587 func sliceObject(objectSize, partSize int64, dfc *DownloadCheckpoint) { 588 cnt := objectSize / partSize 589 if objectSize%partSize > 0 { 590 cnt++ 591 } 592 593 if cnt == 0 { 594 downloadPart := DownloadPartInfo{} 595 downloadPart.PartNumber = 1 596 dfc.DownloadParts = []DownloadPartInfo{downloadPart} 597 } else { 598 downloadParts := make([]DownloadPartInfo, 0, cnt) 599 var i int64 600 for i = 0; i < cnt; i++ { 601 downloadPart := DownloadPartInfo{} 602 downloadPart.PartNumber = i + 1 603 downloadPart.Offset = i * partSize 604 downloadPart.RangeEnd = (i+1)*partSize - 1 605 downloadParts = append(downloadParts, downloadPart) 606 } 607 dfc.DownloadParts = downloadParts 608 if value := objectSize % partSize; value > 0 { 609 dfc.DownloadParts[cnt-1].RangeEnd = dfc.ObjectInfo.Size - 1 610 } 611 } 612 } 613 614 func createFile(tempFileURL string, fileSize int64) error { 615 fd, err := syscall.Open(tempFileURL, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) 616 if err != nil { 617 doLog(LEVEL_WARN, "Failed to open temp download file [%s].", tempFileURL) 618 return err 619 } 620 defer func() { 621 errMsg := syscall.Close(fd) 622 if errMsg != nil { 623 doLog(LEVEL_WARN, "Failed to close file with error [%v].", errMsg) 624 } 625 }() 626 err = syscall.Ftruncate(fd, fileSize) 627 if err != nil { 628 doLog(LEVEL_WARN, "Failed to create file with error [%v].", err) 629 } 630 return err 631 } 632 633 func prepareTempFile(tempFileURL string, fileSize int64) error { 634 parentDir := filepath.Dir(tempFileURL) 635 stat, err := os.Stat(parentDir) 636 if err != nil { 637 doLog(LEVEL_DEBUG, "Failed to stat path with error [%v].", err) 638 _err := os.MkdirAll(parentDir, os.ModePerm) 639 if _err != nil { 640 doLog(LEVEL_ERROR, "Failed to make dir with error [%v].", _err) 641 return _err 642 } 643 } else if !stat.IsDir() { 644 doLog(LEVEL_ERROR, "Cannot create folder [%s] due to a same file exists.", parentDir) 645 return fmt.Errorf("cannot create folder [%s] due to a same file exists", parentDir) 646 } 647 648 err = createFile(tempFileURL, fileSize) 649 if err == nil { 650 return nil 651 } 652 fd, err := os.OpenFile(tempFileURL, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) 653 if err != nil { 654 doLog(LEVEL_ERROR, "Failed to open temp download file [%s].", tempFileURL) 655 return err 656 } 657 defer func() { 658 errMsg := fd.Close() 659 if errMsg != nil { 660 doLog(LEVEL_WARN, "Failed to close file with error [%v].", errMsg) 661 } 662 }() 663 if fileSize > 0 { 664 _, err = fd.WriteAt([]byte("a"), fileSize-1) 665 if err != nil { 666 doLog(LEVEL_ERROR, "Failed to create temp download file with error [%v].", err) 667 return err 668 } 669 } 670 671 return nil 672 } 673 674 func handleDownloadFileResult(tempFileURL string, enableCheckpoint bool, downloadFileError error) error { 675 if downloadFileError != nil { 676 if !enableCheckpoint { 677 _err := os.Remove(tempFileURL) 678 if _err != nil { 679 doLog(LEVEL_WARN, "Failed to remove temp download file with error [%v].", _err) 680 } 681 } 682 return downloadFileError 683 } 684 return nil 685 } 686 687 func (obsClient ObsClient) resumeDownload(input *DownloadFileInput, extensions []extensionOptions) (output *GetObjectMetadataOutput, err error) { 688 getObjectmetaOutput, err := getObjectInfo(input, &obsClient, extensions) 689 if err != nil { 690 return nil, err 691 } 692 693 objectSize := getObjectmetaOutput.ContentLength 694 partSize := input.PartSize 695 dfc := &DownloadCheckpoint{} 696 697 var needCheckpoint = true 698 var checkpointFilePath = input.CheckpointFile 699 var enableCheckpoint = input.EnableCheckpoint 700 if enableCheckpoint { 701 needCheckpoint, err = getDownloadCheckpointFile(dfc, input, getObjectmetaOutput) 702 if err != nil { 703 return nil, err 704 } 705 } 706 707 if needCheckpoint { 708 dfc.Bucket = input.Bucket 709 dfc.Key = input.Key 710 dfc.VersionId = input.VersionId 711 dfc.DownloadFile = input.DownloadFile 712 dfc.ObjectInfo = ObjectInfo{} 713 dfc.ObjectInfo.LastModified = getObjectmetaOutput.LastModified.Unix() 714 dfc.ObjectInfo.Size = getObjectmetaOutput.ContentLength 715 dfc.ObjectInfo.ETag = getObjectmetaOutput.ETag 716 dfc.TempFileInfo = TempFileInfo{} 717 dfc.TempFileInfo.TempFileUrl = input.DownloadFile + ".tmp" 718 dfc.TempFileInfo.Size = getObjectmetaOutput.ContentLength 719 720 sliceObject(objectSize, partSize, dfc) 721 _err := prepareTempFile(dfc.TempFileInfo.TempFileUrl, dfc.TempFileInfo.Size) 722 if _err != nil { 723 return nil, _err 724 } 725 726 if enableCheckpoint { 727 _err := updateCheckpointFile(dfc, checkpointFilePath) 728 if _err != nil { 729 doLog(LEVEL_ERROR, "Failed to update checkpoint file with error [%v].", _err) 730 _errMsg := os.Remove(dfc.TempFileInfo.TempFileUrl) 731 if _errMsg != nil { 732 doLog(LEVEL_WARN, "Failed to remove temp download file with error [%v].", _errMsg) 733 } 734 return nil, _err 735 } 736 } 737 } 738 739 downloadFileError := obsClient.downloadFileConcurrent(input, dfc, extensions) 740 err = handleDownloadFileResult(dfc.TempFileInfo.TempFileUrl, enableCheckpoint, downloadFileError) 741 if err != nil { 742 return nil, err 743 } 744 745 err = os.Rename(dfc.TempFileInfo.TempFileUrl, input.DownloadFile) 746 if err != nil { 747 doLog(LEVEL_ERROR, "Failed to rename temp download file [%s] to download file [%s] with error [%v].", dfc.TempFileInfo.TempFileUrl, input.DownloadFile, err) 748 return nil, err 749 } 750 if enableCheckpoint { 751 err = os.Remove(checkpointFilePath) 752 if err != nil { 753 doLog(LEVEL_WARN, "Download file successfully, but remove checkpoint file failed with error [%v].", err) 754 } 755 } 756 757 return getObjectmetaOutput, nil 758 } 759 760 func updateDownloadFile(filePath string, rangeStart int64, output *GetObjectOutput) error { 761 fd, err := os.OpenFile(filePath, os.O_WRONLY, 0666) 762 if err != nil { 763 doLog(LEVEL_ERROR, "Failed to open file [%s].", filePath) 764 return err 765 } 766 defer func() { 767 errMsg := fd.Close() 768 if errMsg != nil { 769 doLog(LEVEL_WARN, "Failed to close file with error [%v].", errMsg) 770 } 771 }() 772 _, err = fd.Seek(rangeStart, 0) 773 if err != nil { 774 doLog(LEVEL_ERROR, "Failed to seek file with error [%v].", err) 775 return err 776 } 777 fileWriter := bufio.NewWriterSize(fd, 65536) 778 part := make([]byte, 8192) 779 var readErr error 780 var readCount int 781 for { 782 readCount, readErr = output.Body.Read(part) 783 if readCount > 0 { 784 wcnt, werr := fileWriter.Write(part[0:readCount]) 785 if werr != nil { 786 doLog(LEVEL_ERROR, "Failed to write to file with error [%v].", werr) 787 return werr 788 } 789 if wcnt != readCount { 790 doLog(LEVEL_ERROR, "Failed to write to file [%s], expect: [%d], actual: [%d]", filePath, readCount, wcnt) 791 return fmt.Errorf("Failed to write to file [%s], expect: [%d], actual: [%d]", filePath, readCount, wcnt) 792 } 793 } 794 if readErr != nil { 795 if readErr != io.EOF { 796 doLog(LEVEL_ERROR, "Failed to read response body with error [%v].", readErr) 797 return readErr 798 } 799 break 800 } 801 } 802 err = fileWriter.Flush() 803 if err != nil { 804 doLog(LEVEL_ERROR, "Failed to flush file with error [%v].", err) 805 return err 806 } 807 return nil 808 } 809 810 func handleDownloadTaskResult(result interface{}, dfc *DownloadCheckpoint, partNum int64, enableCheckpoint bool, checkpointFile string, lock *sync.Mutex) (err error) { 811 if _, ok := result.(*GetObjectOutput); ok { 812 lock.Lock() 813 defer lock.Unlock() 814 dfc.DownloadParts[partNum-1].IsCompleted = true 815 if enableCheckpoint { 816 _err := updateCheckpointFile(dfc, checkpointFile) 817 if _err != nil { 818 doLog(LEVEL_WARN, "Failed to update checkpoint file with error [%v].", _err) 819 } 820 } 821 } else if result != errAbort { 822 if _err, ok := result.(error); ok { 823 err = _err 824 } 825 } 826 return 827 } 828 829 func (obsClient ObsClient) downloadFileConcurrent(input *DownloadFileInput, dfc *DownloadCheckpoint, extensions []extensionOptions) error { 830 pool := NewRoutinePool(input.TaskNum, MAX_PART_NUM) 831 var downloadPartError atomic.Value 832 var errFlag int32 833 var abort int32 834 lock := new(sync.Mutex) 835 for _, downloadPart := range dfc.DownloadParts { 836 if atomic.LoadInt32(&abort) == 1 { 837 break 838 } 839 if downloadPart.IsCompleted { 840 continue 841 } 842 task := downloadPartTask{ 843 GetObjectInput: GetObjectInput{ 844 GetObjectMetadataInput: input.GetObjectMetadataInput, 845 IfMatch: input.IfMatch, 846 IfNoneMatch: input.IfNoneMatch, 847 IfUnmodifiedSince: input.IfUnmodifiedSince, 848 IfModifiedSince: input.IfModifiedSince, 849 RangeStart: downloadPart.Offset, 850 RangeEnd: downloadPart.RangeEnd, 851 }, 852 obsClient: &obsClient, 853 extensions: extensions, 854 abort: &abort, 855 partNumber: downloadPart.PartNumber, 856 tempFileURL: dfc.TempFileInfo.TempFileUrl, 857 enableCheckpoint: input.EnableCheckpoint, 858 } 859 pool.ExecuteFunc(func() interface{} { 860 result := task.Run() 861 err := handleDownloadTaskResult(result, dfc, task.partNumber, input.EnableCheckpoint, input.CheckpointFile, lock) 862 if err != nil && atomic.CompareAndSwapInt32(&errFlag, 0, 1) { 863 downloadPartError.Store(err) 864 } 865 return nil 866 }) 867 } 868 pool.ShutDown() 869 if err, ok := downloadPartError.Load().(error); ok { 870 return err 871 } 872 873 return nil 874 }