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