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  }