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  }