github.com/chnsz/golangsdk@v0.0.0-20240506093406-85a3fbfa605b/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 len(extensions) != 0 {
   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 len(extensions) != 0 {
   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 len(extensions) != 0 {
   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 len(extensions) != 0 {
   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, completedBytes *int64, listener ProgressListener) (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  
   374  		atomic.AddInt64(completedBytes, ufc.UploadParts[partNum-1].PartSize)
   375  
   376  		event := newProgressEvent(TransferDataEvent, *completedBytes, ufc.FileInfo.Size)
   377  		publishProgress(listener, event)
   378  
   379  		if enableCheckpoint {
   380  			_err := updateCheckpointFile(ufc, checkpointFilePath)
   381  			if _err != nil {
   382  				doLog(LEVEL_WARN, "Failed to update checkpoint file with error [%v].", _err)
   383  			}
   384  		}
   385  	} else if result != errAbort {
   386  		if _err, ok := result.(error); ok {
   387  			err = _err
   388  		}
   389  	}
   390  	return
   391  }
   392  
   393  func (obsClient ObsClient) uploadPartConcurrent(ufc *UploadCheckpoint, checkpointFilePath string, input *UploadFileInput, extensions []extensionOptions) error {
   394  	pool := NewRoutinePool(input.TaskNum, MAX_PART_NUM)
   395  	var uploadPartError atomic.Value
   396  	var errFlag int32
   397  	var abort int32
   398  	lock := new(sync.Mutex)
   399  
   400  	var completedBytes int64
   401  	listener := obsClient.getProgressListener(extensions)
   402  	totalBytes := ufc.FileInfo.Size
   403  	event := newProgressEvent(TransferStartedEvent, 0, totalBytes)
   404  	publishProgress(listener, event)
   405  
   406  	for _, uploadPart := range ufc.UploadParts {
   407  		if atomic.LoadInt32(&abort) == 1 {
   408  			break
   409  		}
   410  		if uploadPart.IsCompleted {
   411  			atomic.AddInt64(&completedBytes, uploadPart.PartSize)
   412  			event := newProgressEvent(TransferDataEvent, completedBytes, ufc.FileInfo.Size)
   413  			publishProgress(listener, event)
   414  			continue
   415  		}
   416  		task := uploadPartTask{
   417  			UploadPartInput: UploadPartInput{
   418  				Bucket:     ufc.Bucket,
   419  				Key:        ufc.Key,
   420  				PartNumber: uploadPart.PartNumber,
   421  				UploadId:   ufc.UploadId,
   422  				SseHeader:  input.SseHeader,
   423  				SourceFile: input.UploadFile,
   424  				Offset:     uploadPart.Offset,
   425  				PartSize:   uploadPart.PartSize,
   426  			},
   427  			obsClient:        &obsClient,
   428  			abort:            &abort,
   429  			extensions:       extensions,
   430  			enableCheckpoint: input.EnableCheckpoint,
   431  		}
   432  		pool.ExecuteFunc(func() interface{} {
   433  			result := task.Run()
   434  			err := handleUploadTaskResult(result, ufc, task.PartNumber, input.EnableCheckpoint, input.CheckpointFile, lock, &completedBytes, listener)
   435  			if err != nil && atomic.CompareAndSwapInt32(&errFlag, 0, 1) {
   436  				uploadPartError.Store(err)
   437  			}
   438  			return nil
   439  		})
   440  	}
   441  	pool.ShutDown()
   442  	if err, ok := uploadPartError.Load().(error); ok {
   443  
   444  		event := newProgressEvent(TransferFailedEvent, completedBytes, ufc.FileInfo.Size)
   445  		publishProgress(listener, event)
   446  
   447  		return err
   448  	}
   449  	event = newProgressEvent(TransferCompletedEvent, completedBytes, ufc.FileInfo.Size)
   450  	publishProgress(listener, event)
   451  	return nil
   452  }
   453  
   454  // ObjectInfo defines download object info
   455  type ObjectInfo struct {
   456  	XMLName      xml.Name `xml:"ObjectInfo"`
   457  	LastModified int64    `xml:"LastModified"`
   458  	Size         int64    `xml:"Size"`
   459  	ETag         string   `xml:"ETag"`
   460  }
   461  
   462  // TempFileInfo defines temp download file properties
   463  type TempFileInfo struct {
   464  	XMLName     xml.Name `xml:"TempFileInfo"`
   465  	TempFileUrl string   `xml:"TempFileUrl"`
   466  	Size        int64    `xml:"Size"`
   467  }
   468  
   469  // DownloadPartInfo defines download part properties
   470  type DownloadPartInfo struct {
   471  	XMLName     xml.Name `xml:"DownloadPart"`
   472  	PartNumber  int64    `xml:"PartNumber"`
   473  	RangeEnd    int64    `xml:"RangeEnd"`
   474  	Offset      int64    `xml:"Offset"`
   475  	IsCompleted bool     `xml:"IsCompleted"`
   476  }
   477  
   478  // DownloadCheckpoint defines download checkpoint file properties
   479  type DownloadCheckpoint struct {
   480  	XMLName       xml.Name           `xml:"DownloadFileCheckpoint"`
   481  	Bucket        string             `xml:"Bucket"`
   482  	Key           string             `xml:"Key"`
   483  	VersionId     string             `xml:"VersionId,omitempty"`
   484  	DownloadFile  string             `xml:"FileUrl"`
   485  	ObjectInfo    ObjectInfo         `xml:"ObjectInfo"`
   486  	TempFileInfo  TempFileInfo       `xml:"TempFileInfo"`
   487  	DownloadParts []DownloadPartInfo `xml:"DownloadParts>DownloadPart"`
   488  }
   489  
   490  func (dfc *DownloadCheckpoint) isValid(input *DownloadFileInput, output *GetObjectMetadataOutput) bool {
   491  	if dfc.Bucket != input.Bucket || dfc.Key != input.Key || dfc.VersionId != input.VersionId || dfc.DownloadFile != input.DownloadFile {
   492  		doLog(LEVEL_INFO, "Checkpoint file is invalid, the bucketName or objectKey or downloadFile was changed. clear the record.")
   493  		return false
   494  	}
   495  	if dfc.ObjectInfo.LastModified != output.LastModified.Unix() || dfc.ObjectInfo.ETag != output.ETag || dfc.ObjectInfo.Size != output.ContentLength {
   496  		doLog(LEVEL_INFO, "Checkpoint file is invalid, the object info was changed. clear the record.")
   497  		return false
   498  	}
   499  	if dfc.TempFileInfo.Size != output.ContentLength {
   500  		doLog(LEVEL_INFO, "Checkpoint file is invalid, size was changed. clear the record.")
   501  		return false
   502  	}
   503  	stat, err := os.Stat(dfc.TempFileInfo.TempFileUrl)
   504  	if err != nil || stat.Size() != dfc.ObjectInfo.Size {
   505  		doLog(LEVEL_INFO, "Checkpoint file is invalid, the temp download file was changed. clear the record.")
   506  		return false
   507  	}
   508  
   509  	return true
   510  }
   511  
   512  type downloadPartTask struct {
   513  	GetObjectInput
   514  	obsClient        *ObsClient
   515  	extensions       []extensionOptions
   516  	abort            *int32
   517  	partNumber       int64
   518  	tempFileURL      string
   519  	enableCheckpoint bool
   520  }
   521  
   522  func (task *downloadPartTask) Run() interface{} {
   523  	if atomic.LoadInt32(task.abort) == 1 {
   524  		return errAbort
   525  	}
   526  	getObjectInput := &GetObjectInput{}
   527  	getObjectInput.GetObjectMetadataInput = task.GetObjectMetadataInput
   528  	getObjectInput.IfMatch = task.IfMatch
   529  	getObjectInput.IfNoneMatch = task.IfNoneMatch
   530  	getObjectInput.IfModifiedSince = task.IfModifiedSince
   531  	getObjectInput.IfUnmodifiedSince = task.IfUnmodifiedSince
   532  	getObjectInput.RangeStart = task.RangeStart
   533  	getObjectInput.RangeEnd = task.RangeEnd
   534  
   535  	var output *GetObjectOutput
   536  	var err error
   537  	if len(task.extensions) != 0 {
   538  		output, err = task.obsClient.GetObjectWithoutProgress(getObjectInput, task.extensions...)
   539  	} else {
   540  		output, err = task.obsClient.GetObjectWithoutProgress(getObjectInput)
   541  	}
   542  
   543  	if err == nil {
   544  		defer func() {
   545  			errMsg := output.Body.Close()
   546  			if errMsg != nil {
   547  				doLog(LEVEL_WARN, "Failed to close response body.")
   548  			}
   549  		}()
   550  		_err := updateDownloadFile(task.tempFileURL, task.RangeStart, output)
   551  		if _err != nil {
   552  			if !task.enableCheckpoint {
   553  				atomic.CompareAndSwapInt32(task.abort, 0, 1)
   554  				doLog(LEVEL_WARN, "Task is aborted, part number is [%d]", task.partNumber)
   555  			}
   556  			return _err
   557  		}
   558  		return output
   559  	} else if obsError, ok := err.(ObsError); ok && obsError.StatusCode >= 400 && obsError.StatusCode < 500 {
   560  		atomic.CompareAndSwapInt32(task.abort, 0, 1)
   561  		doLog(LEVEL_WARN, "Task is aborted, part number is [%d]", task.partNumber)
   562  	}
   563  	return err
   564  }
   565  
   566  func getObjectInfo(input *DownloadFileInput, obsClient *ObsClient, extensions []extensionOptions) (getObjectmetaOutput *GetObjectMetadataOutput, err error) {
   567  	if len(extensions) != 0 {
   568  		getObjectmetaOutput, err = obsClient.GetObjectMetadata(&input.GetObjectMetadataInput, extensions...)
   569  	} else {
   570  		getObjectmetaOutput, err = obsClient.GetObjectMetadata(&input.GetObjectMetadataInput)
   571  	}
   572  
   573  	return
   574  }
   575  
   576  func getDownloadCheckpointFile(dfc *DownloadCheckpoint, input *DownloadFileInput, output *GetObjectMetadataOutput) (needCheckpoint bool, err error) {
   577  	checkpointFilePath := input.CheckpointFile
   578  	checkpointFileStat, err := os.Stat(checkpointFilePath)
   579  	if err != nil {
   580  		doLog(LEVEL_DEBUG, fmt.Sprintf("Stat checkpoint file failed with error: [%v].", err))
   581  		return true, nil
   582  	}
   583  	if checkpointFileStat.IsDir() {
   584  		doLog(LEVEL_ERROR, "Checkpoint file can not be a folder.")
   585  		return false, errors.New("checkpoint file can not be a folder")
   586  	}
   587  	err = loadCheckpointFile(checkpointFilePath, dfc)
   588  	if err != nil {
   589  		doLog(LEVEL_WARN, fmt.Sprintf("Load checkpoint file failed with error: [%v].", err))
   590  		return true, nil
   591  	} else if !dfc.isValid(input, output) {
   592  		if dfc.TempFileInfo.TempFileUrl != "" {
   593  			_err := os.Remove(dfc.TempFileInfo.TempFileUrl)
   594  			if _err != nil {
   595  				doLog(LEVEL_WARN, "Failed to remove temp download file with error [%v].", _err)
   596  			}
   597  		}
   598  		_err := os.Remove(checkpointFilePath)
   599  		if _err != nil {
   600  			doLog(LEVEL_WARN, "Failed to remove checkpoint file with error [%v].", _err)
   601  		}
   602  	} else {
   603  		return false, nil
   604  	}
   605  
   606  	return true, nil
   607  }
   608  
   609  func sliceObject(objectSize, partSize int64, dfc *DownloadCheckpoint) {
   610  	cnt := objectSize / partSize
   611  	if objectSize%partSize > 0 {
   612  		cnt++
   613  	}
   614  
   615  	if cnt == 0 {
   616  		downloadPart := DownloadPartInfo{}
   617  		downloadPart.PartNumber = 1
   618  		dfc.DownloadParts = []DownloadPartInfo{downloadPart}
   619  	} else {
   620  		downloadParts := make([]DownloadPartInfo, 0, cnt)
   621  		var i int64
   622  		for i = 0; i < cnt; i++ {
   623  			downloadPart := DownloadPartInfo{}
   624  			downloadPart.PartNumber = i + 1
   625  			downloadPart.Offset = i * partSize
   626  			downloadPart.RangeEnd = (i+1)*partSize - 1
   627  			downloadParts = append(downloadParts, downloadPart)
   628  		}
   629  		dfc.DownloadParts = downloadParts
   630  		if value := objectSize % partSize; value > 0 {
   631  			dfc.DownloadParts[cnt-1].RangeEnd = dfc.ObjectInfo.Size - 1
   632  		}
   633  	}
   634  }
   635  
   636  func createFile(tempFileURL string, fileSize int64) error {
   637  	fd, err := syscall.Open(tempFileURL, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
   638  	if err != nil {
   639  		doLog(LEVEL_WARN, "Failed to open temp download file [%s].", tempFileURL)
   640  		return err
   641  	}
   642  	defer func() {
   643  		errMsg := syscall.Close(fd)
   644  		if errMsg != nil {
   645  			doLog(LEVEL_WARN, "Failed to close file with error [%v].", errMsg)
   646  		}
   647  	}()
   648  	err = syscall.Ftruncate(fd, fileSize)
   649  	if err != nil {
   650  		doLog(LEVEL_WARN, "Failed to create file with error [%v].", err)
   651  	}
   652  	return err
   653  }
   654  
   655  func prepareTempFile(tempFileURL string, fileSize int64) error {
   656  	parentDir := filepath.Dir(tempFileURL)
   657  	stat, err := os.Stat(parentDir)
   658  	if err != nil {
   659  		doLog(LEVEL_DEBUG, "Failed to stat path with error [%v].", err)
   660  		_err := os.MkdirAll(parentDir, os.ModePerm)
   661  		if _err != nil {
   662  			doLog(LEVEL_ERROR, "Failed to make dir with error [%v].", _err)
   663  			return _err
   664  		}
   665  	} else if !stat.IsDir() {
   666  		doLog(LEVEL_ERROR, "Cannot create folder [%s] due to a same file exists.", parentDir)
   667  		return fmt.Errorf("cannot create folder [%s] due to a same file exists", parentDir)
   668  	}
   669  
   670  	err = createFile(tempFileURL, fileSize)
   671  	if err == nil {
   672  		return nil
   673  	}
   674  	fd, err := os.OpenFile(tempFileURL, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
   675  	if err != nil {
   676  		doLog(LEVEL_ERROR, "Failed to open temp download file [%s].", tempFileURL)
   677  		return err
   678  	}
   679  	defer func() {
   680  		errMsg := fd.Close()
   681  		if errMsg != nil {
   682  			doLog(LEVEL_WARN, "Failed to close file with error [%v].", errMsg)
   683  		}
   684  	}()
   685  	if fileSize > 0 {
   686  		_, err = fd.WriteAt([]byte("a"), fileSize-1)
   687  		if err != nil {
   688  			doLog(LEVEL_ERROR, "Failed to create temp download file with error [%v].", err)
   689  			return err
   690  		}
   691  	}
   692  
   693  	return nil
   694  }
   695  
   696  func handleDownloadFileResult(tempFileURL string, enableCheckpoint bool, downloadFileError error) error {
   697  	if downloadFileError != nil {
   698  		if !enableCheckpoint {
   699  			_err := os.Remove(tempFileURL)
   700  			if _err != nil {
   701  				doLog(LEVEL_WARN, "Failed to remove temp download file with error [%v].", _err)
   702  			}
   703  		}
   704  		return downloadFileError
   705  	}
   706  	return nil
   707  }
   708  
   709  func (obsClient ObsClient) resumeDownload(input *DownloadFileInput, extensions []extensionOptions) (output *GetObjectMetadataOutput, err error) {
   710  	getObjectmetaOutput, err := getObjectInfo(input, &obsClient, extensions)
   711  	if err != nil {
   712  		return nil, err
   713  	}
   714  
   715  	objectSize := getObjectmetaOutput.ContentLength
   716  	partSize := input.PartSize
   717  	dfc := &DownloadCheckpoint{}
   718  
   719  	var needCheckpoint = true
   720  	var checkpointFilePath = input.CheckpointFile
   721  	var enableCheckpoint = input.EnableCheckpoint
   722  	if enableCheckpoint {
   723  		needCheckpoint, err = getDownloadCheckpointFile(dfc, input, getObjectmetaOutput)
   724  		if err != nil {
   725  			return nil, err
   726  		}
   727  	}
   728  
   729  	if needCheckpoint {
   730  		dfc.Bucket = input.Bucket
   731  		dfc.Key = input.Key
   732  		dfc.VersionId = input.VersionId
   733  		dfc.DownloadFile = input.DownloadFile
   734  		dfc.ObjectInfo = ObjectInfo{}
   735  		dfc.ObjectInfo.LastModified = getObjectmetaOutput.LastModified.Unix()
   736  		dfc.ObjectInfo.Size = getObjectmetaOutput.ContentLength
   737  		dfc.ObjectInfo.ETag = getObjectmetaOutput.ETag
   738  		dfc.TempFileInfo = TempFileInfo{}
   739  		dfc.TempFileInfo.TempFileUrl = input.DownloadFile + ".tmp"
   740  		dfc.TempFileInfo.Size = getObjectmetaOutput.ContentLength
   741  
   742  		sliceObject(objectSize, partSize, dfc)
   743  		_err := prepareTempFile(dfc.TempFileInfo.TempFileUrl, dfc.TempFileInfo.Size)
   744  		if _err != nil {
   745  			return nil, _err
   746  		}
   747  
   748  		if enableCheckpoint {
   749  			_err := updateCheckpointFile(dfc, checkpointFilePath)
   750  			if _err != nil {
   751  				doLog(LEVEL_ERROR, "Failed to update checkpoint file with error [%v].", _err)
   752  				_errMsg := os.Remove(dfc.TempFileInfo.TempFileUrl)
   753  				if _errMsg != nil {
   754  					doLog(LEVEL_WARN, "Failed to remove temp download file with error [%v].", _errMsg)
   755  				}
   756  				return nil, _err
   757  			}
   758  		}
   759  	}
   760  
   761  	downloadFileError := obsClient.downloadFileConcurrent(input, dfc, extensions)
   762  	err = handleDownloadFileResult(dfc.TempFileInfo.TempFileUrl, enableCheckpoint, downloadFileError)
   763  	if err != nil {
   764  		return nil, err
   765  	}
   766  
   767  	err = os.Rename(dfc.TempFileInfo.TempFileUrl, input.DownloadFile)
   768  	if err != nil {
   769  		doLog(LEVEL_ERROR, "Failed to rename temp download file [%s] to download file [%s] with error [%v].", dfc.TempFileInfo.TempFileUrl, input.DownloadFile, err)
   770  		return nil, err
   771  	}
   772  	if enableCheckpoint {
   773  		err = os.Remove(checkpointFilePath)
   774  		if err != nil {
   775  			doLog(LEVEL_WARN, "Download file successfully, but remove checkpoint file failed with error [%v].", err)
   776  		}
   777  	}
   778  
   779  	return getObjectmetaOutput, nil
   780  }
   781  
   782  func updateDownloadFile(filePath string, rangeStart int64, output *GetObjectOutput) error {
   783  	fd, err := os.OpenFile(filePath, os.O_WRONLY, 0666)
   784  	if err != nil {
   785  		doLog(LEVEL_ERROR, "Failed to open file [%s].", filePath)
   786  		return err
   787  	}
   788  	defer func() {
   789  		errMsg := fd.Close()
   790  		if errMsg != nil {
   791  			doLog(LEVEL_WARN, "Failed to close file with error [%v].", errMsg)
   792  		}
   793  	}()
   794  	_, err = fd.Seek(rangeStart, 0)
   795  	if err != nil {
   796  		doLog(LEVEL_ERROR, "Failed to seek file with error [%v].", err)
   797  		return err
   798  	}
   799  	fileWriter := bufio.NewWriterSize(fd, 65536)
   800  	part := make([]byte, 8192)
   801  	var readErr error
   802  	var readCount int
   803  	for {
   804  		readCount, readErr = output.Body.Read(part)
   805  		if readCount > 0 {
   806  			wcnt, werr := fileWriter.Write(part[0:readCount])
   807  			if werr != nil {
   808  				doLog(LEVEL_ERROR, "Failed to write to file with error [%v].", werr)
   809  				return werr
   810  			}
   811  			if wcnt != readCount {
   812  				doLog(LEVEL_ERROR, "Failed to write to file [%s], expect: [%d], actual: [%d]", filePath, readCount, wcnt)
   813  				return fmt.Errorf("Failed to write to file [%s], expect: [%d], actual: [%d]", filePath, readCount, wcnt)
   814  			}
   815  		}
   816  		if readErr != nil {
   817  			if readErr != io.EOF {
   818  				doLog(LEVEL_ERROR, "Failed to read response body with error [%v].", readErr)
   819  				return readErr
   820  			}
   821  			break
   822  		}
   823  	}
   824  	err = fileWriter.Flush()
   825  	if err != nil {
   826  		doLog(LEVEL_ERROR, "Failed to flush file with error [%v].", err)
   827  		return err
   828  	}
   829  	return nil
   830  }
   831  
   832  func handleDownloadTaskResult(result interface{}, dfc *DownloadCheckpoint, partNum int64, enableCheckpoint bool, checkpointFile string, lock *sync.Mutex, completedBytes *int64, listener ProgressListener) (err error) {
   833  	if output, ok := result.(*GetObjectOutput); ok {
   834  		lock.Lock()
   835  		defer lock.Unlock()
   836  		dfc.DownloadParts[partNum-1].IsCompleted = true
   837  
   838  		atomic.AddInt64(completedBytes, output.ContentLength)
   839  
   840  		event := newProgressEvent(TransferDataEvent, *completedBytes, dfc.ObjectInfo.Size)
   841  		publishProgress(listener, event)
   842  
   843  		if enableCheckpoint {
   844  			_err := updateCheckpointFile(dfc, checkpointFile)
   845  			if _err != nil {
   846  				doLog(LEVEL_WARN, "Failed to update checkpoint file with error [%v].", _err)
   847  			}
   848  		}
   849  	} else if result != errAbort {
   850  		if _err, ok := result.(error); ok {
   851  			err = _err
   852  		}
   853  	}
   854  	return
   855  }
   856  
   857  func (obsClient ObsClient) downloadFileConcurrent(input *DownloadFileInput, dfc *DownloadCheckpoint, extensions []extensionOptions) error {
   858  	pool := NewRoutinePool(input.TaskNum, MAX_PART_NUM)
   859  	var downloadPartError atomic.Value
   860  	var errFlag int32
   861  	var abort int32
   862  	lock := new(sync.Mutex)
   863  
   864  	var completedBytes int64
   865  	listener := obsClient.getProgressListener(extensions)
   866  	totalBytes := dfc.ObjectInfo.Size
   867  	event := newProgressEvent(TransferStartedEvent, 0, totalBytes)
   868  	publishProgress(listener, event)
   869  
   870  	for _, downloadPart := range dfc.DownloadParts {
   871  		if atomic.LoadInt32(&abort) == 1 {
   872  			break
   873  		}
   874  		if downloadPart.IsCompleted {
   875  			atomic.AddInt64(&completedBytes, downloadPart.RangeEnd-downloadPart.Offset+1)
   876  			event := newProgressEvent(TransferDataEvent, completedBytes, dfc.ObjectInfo.Size)
   877  			publishProgress(listener, event)
   878  			continue
   879  		}
   880  		task := downloadPartTask{
   881  			GetObjectInput: GetObjectInput{
   882  				GetObjectMetadataInput: input.GetObjectMetadataInput,
   883  				IfMatch:                input.IfMatch,
   884  				IfNoneMatch:            input.IfNoneMatch,
   885  				IfUnmodifiedSince:      input.IfUnmodifiedSince,
   886  				IfModifiedSince:        input.IfModifiedSince,
   887  				RangeStart:             downloadPart.Offset,
   888  				RangeEnd:               downloadPart.RangeEnd,
   889  			},
   890  			obsClient:        &obsClient,
   891  			extensions:       extensions,
   892  			abort:            &abort,
   893  			partNumber:       downloadPart.PartNumber,
   894  			tempFileURL:      dfc.TempFileInfo.TempFileUrl,
   895  			enableCheckpoint: input.EnableCheckpoint,
   896  		}
   897  		pool.ExecuteFunc(func() interface{} {
   898  			result := task.Run()
   899  			err := handleDownloadTaskResult(result, dfc, task.partNumber, input.EnableCheckpoint, input.CheckpointFile, lock, &completedBytes, listener)
   900  			if err != nil && atomic.CompareAndSwapInt32(&errFlag, 0, 1) {
   901  				downloadPartError.Store(err)
   902  			}
   903  			return nil
   904  		})
   905  	}
   906  	pool.ShutDown()
   907  	if err, ok := downloadPartError.Load().(error); ok {
   908  		event := newProgressEvent(TransferFailedEvent, completedBytes, dfc.ObjectInfo.Size)
   909  		publishProgress(listener, event)
   910  		return err
   911  	}
   912  	event = newProgressEvent(TransferCompletedEvent, completedBytes, dfc.ObjectInfo.Size)
   913  	publishProgress(listener, event)
   914  	return nil
   915  }