github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/downloader/download_manager.go (about)

     1  package downloader
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"sync"
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/qiniu/go-sdk/v7/storagev2/credentials"
    14  	"github.com/qiniu/go-sdk/v7/storagev2/downloader/destination"
    15  	"github.com/qiniu/go-sdk/v7/storagev2/errors"
    16  	httpclient "github.com/qiniu/go-sdk/v7/storagev2/http_client"
    17  	"github.com/qiniu/go-sdk/v7/storagev2/objects"
    18  	"github.com/qiniu/go-sdk/v7/storagev2/region"
    19  	"golang.org/x/sync/errgroup"
    20  )
    21  
    22  type (
    23  	// 下载管理器
    24  	DownloadManager struct {
    25  		destinationDownloader    DestinationDownloader
    26  		objectsManager           *objects.ObjectsManager
    27  		downloadURLsProvider     DownloadURLsProvider
    28  		downloadURLsProviderOnce sync.Once
    29  		options                  httpclient.Options
    30  	}
    31  
    32  	// 下载管理器选项
    33  	DownloadManagerOptions struct {
    34  		// HTTP 客户端选项
    35  		httpclient.Options
    36  
    37  		// 目标下载器
    38  		DestinationDownloader DestinationDownloader
    39  
    40  		// 分片列举版本,如果不填写,默认为 V1
    41  		ListerVersion objects.ListerVersion
    42  
    43  		// 下载 URL 生成器
    44  		DownloadURLsProvider DownloadURLsProvider
    45  	}
    46  
    47  	// 对象下载参数
    48  	ObjectOptions struct {
    49  		DestinationDownloadOptions
    50  		GenerateOptions
    51  
    52  		// 下载 URL 生成器
    53  		DownloadURLsProvider DownloadURLsProvider
    54  	}
    55  
    56  	// 目录下载参数
    57  	DirectoryOptions struct {
    58  		// 是否使用 HTTP 协议,默认为不使用
    59  		UseInsecureProtocol bool
    60  
    61  		// 空间名称
    62  		BucketName string
    63  
    64  		// 对象前缀
    65  		ObjectPrefix string
    66  
    67  		// 下载并发度
    68  		ObjectConcurrency int
    69  
    70  		// 下载 URL 生成器
    71  		DownloadURLsProvider DownloadURLsProvider
    72  
    73  		// 下载前回调函数
    74  		BeforeObjectDownload func(objectName string, objectOptions *ObjectOptions)
    75  
    76  		// 下载进度
    77  		OnDownloadingProgress func(objectName string, progress *DownloadingProgress)
    78  
    79  		// 对象下载成功后回调
    80  		OnObjectDownloaded func(objectName string, info *DownloadedObjectInfo)
    81  
    82  		// 是否下载指定对象
    83  		ShouldDownloadObject func(objectName string) bool
    84  
    85  		// 分隔符,默认为 /
    86  		PathSeparator string
    87  	}
    88  
    89  	// 已经下载的对象信息
    90  	DownloadedObjectInfo struct {
    91  		Size uint64 // 对象大小
    92  	}
    93  
    94  	writeSeekCloser struct {
    95  		w io.Writer
    96  	}
    97  )
    98  
    99  // 创建下载管理器
   100  func NewDownloadManager(options *DownloadManagerOptions) *DownloadManager {
   101  	if options == nil {
   102  		options = &DownloadManagerOptions{}
   103  	}
   104  	destinationDownloader := options.DestinationDownloader
   105  	if destinationDownloader == nil {
   106  		destinationDownloader = NewConcurrentDownloader(nil)
   107  	}
   108  	objectsManager := objects.NewObjectsManager(&objects.ObjectsManagerOptions{
   109  		Options:       options.Options,
   110  		ListerVersion: options.ListerVersion,
   111  	})
   112  	return &DownloadManager{
   113  		destinationDownloader: destinationDownloader,
   114  		objectsManager:        objectsManager,
   115  		options:               options.Options,
   116  	}
   117  }
   118  
   119  // 下载对象到文件
   120  func (downloadManager *DownloadManager) DownloadToFile(ctx context.Context, objectName, filePath string, options *ObjectOptions) (uint64, error) {
   121  	dest, err := destination.NewFileDestination(filePath)
   122  	if err != nil {
   123  		return 0, err
   124  	}
   125  	defer dest.Close()
   126  	return downloadManager.downloadToDestination(ctx, objectName, dest, options)
   127  }
   128  
   129  // 下载对象到 io.Writer
   130  func (downloadManager *DownloadManager) DownloadToWriter(ctx context.Context, objectName string, writer io.Writer, options *ObjectOptions) (uint64, error) {
   131  	var dest destination.Destination
   132  	if writeAtCloser, ok := writer.(destination.WriteAtCloser); ok {
   133  		dest = destination.NewWriteAtCloserDestination(writeAtCloser, "")
   134  	} else {
   135  		dest = destination.NewWriteCloserDestination(&writeSeekCloser{writer}, "")
   136  	}
   137  	defer dest.Close()
   138  	return downloadManager.downloadToDestination(ctx, objectName, dest, options)
   139  }
   140  
   141  func (downloadManager *DownloadManager) downloadToDestination(ctx context.Context, objectName string, dest destination.Destination, options *ObjectOptions) (uint64, error) {
   142  	if options == nil {
   143  		options = &ObjectOptions{}
   144  	}
   145  	downloadURLsProvider := options.DownloadURLsProvider
   146  	if downloadURLsProvider == nil {
   147  		if err := downloadManager.initDownloadURLsProvider(ctx); err != nil {
   148  			return 0, err
   149  		}
   150  	}
   151  	if downloadURLsProvider == nil {
   152  		downloadURLsProvider = downloadManager.downloadURLsProvider
   153  	}
   154  	if downloadURLsProvider == nil {
   155  		return 0, errors.MissingRequiredFieldError{Name: "DownloadURLsProvider"}
   156  	}
   157  	urls, err := downloadURLsProvider.GetURLsIter(ctx, objectName, &options.GenerateOptions)
   158  	if err != nil {
   159  		return 0, err
   160  	}
   161  	return downloadManager.destinationDownloader.Download(ctx, urls, dest, &options.DestinationDownloadOptions)
   162  }
   163  
   164  // 下载目录
   165  func (downloadManager *DownloadManager) DownloadDirectory(ctx context.Context, targetDirPath string, options *DirectoryOptions) error {
   166  	var err error
   167  
   168  	if options == nil {
   169  		options = &DirectoryOptions{}
   170  	}
   171  	if options.BucketName == "" {
   172  		return &errors.MissingRequiredFieldError{Name: "BucketName"}
   173  	}
   174  	objectConcurrency := options.ObjectConcurrency
   175  	if objectConcurrency == 0 {
   176  		objectConcurrency = 4
   177  	}
   178  	pathSeparator := options.PathSeparator
   179  	if pathSeparator == "" {
   180  		pathSeparator = "/"
   181  	}
   182  
   183  	g, ctx := errgroup.WithContext(ctx)
   184  	g.SetLimit(objectConcurrency)
   185  
   186  	lister := downloadManager.objectsManager.Bucket(options.BucketName).List(ctx, &objects.ListObjectsOptions{
   187  		Prefix: options.ObjectPrefix,
   188  	})
   189  	defer lister.Close()
   190  
   191  	var object objects.ObjectDetails
   192  	for lister.Next(&object) {
   193  		objectName := object.Name
   194  		if options.ShouldDownloadObject != nil && !options.ShouldDownloadObject(objectName) {
   195  			continue
   196  		}
   197  
   198  		relativePath := strings.TrimPrefix(objectName, options.ObjectPrefix)
   199  		if pathSeparator != string(filepath.Separator) {
   200  			relativePath = strings.Replace(relativePath, pathSeparator, string(filepath.Separator), -1)
   201  		}
   202  		fullPath := filepath.Join(targetDirPath, relativePath)
   203  		if relativePath == "" || strings.HasSuffix(relativePath, string(filepath.Separator)) {
   204  			if err = os.MkdirAll(fullPath, 0700); err != nil {
   205  				return err
   206  			}
   207  		} else {
   208  			if err = os.MkdirAll(filepath.Dir(fullPath), 0700); err != nil {
   209  				return err
   210  			}
   211  			g.Go(func() error {
   212  				var destinationDownloadOptions DestinationDownloadOptions
   213  				if onDownloadingProgress := options.OnDownloadingProgress; onDownloadingProgress != nil {
   214  					destinationDownloadOptions.OnDownloadingProgress = func(progress *DownloadingProgress) {
   215  						onDownloadingProgress(objectName, progress)
   216  					}
   217  				}
   218  				objectOptions := ObjectOptions{
   219  					DestinationDownloadOptions: destinationDownloadOptions,
   220  					GenerateOptions: GenerateOptions{
   221  						BucketName:          options.BucketName,
   222  						UseInsecureProtocol: options.UseInsecureProtocol,
   223  					},
   224  					DownloadURLsProvider: options.DownloadURLsProvider,
   225  				}
   226  				if options.BeforeObjectDownload != nil {
   227  					options.BeforeObjectDownload(objectName, &objectOptions)
   228  				}
   229  				n, err := downloadManager.DownloadToFile(ctx, objectName, fullPath, &objectOptions)
   230  				if err == nil && options.OnObjectDownloaded != nil {
   231  					options.OnObjectDownloaded(objectName, &DownloadedObjectInfo{Size: n})
   232  				}
   233  				return err
   234  			})
   235  		}
   236  	}
   237  	if err = lister.Error(); err != nil {
   238  		return err
   239  	}
   240  	return g.Wait()
   241  }
   242  
   243  func (downloadManager *DownloadManager) initDownloadURLsProvider(ctx context.Context) (err error) {
   244  	if downloadManager.downloadURLsProvider == nil {
   245  		if credentialsProvider := downloadManager.getCredentialsProvider(); credentialsProvider != nil {
   246  			downloadManager.downloadURLsProviderOnce.Do(func() {
   247  				var creds *credentials.Credentials
   248  				creds, err = credentialsProvider.Get(ctx)
   249  				if err != nil {
   250  					return
   251  				}
   252  				bucketRegionsQueryOptions := region.BucketRegionsQueryOptions{
   253  					UseInsecureProtocol: downloadManager.options.UseInsecureProtocol,
   254  					Client:              downloadManager.options.BasicHTTPClient,
   255  					Resolver:            downloadManager.options.Resolver,
   256  					Chooser:             downloadManager.options.Chooser,
   257  					BeforeResolve:       downloadManager.options.BeforeResolve,
   258  					AfterResolve:        downloadManager.options.AfterResolve,
   259  					ResolveError:        downloadManager.options.ResolveError,
   260  					BeforeBackoff:       downloadManager.options.BeforeBackoff,
   261  					AfterBackoff:        downloadManager.options.AfterBackoff,
   262  					BeforeRequest:       downloadManager.options.BeforeRequest,
   263  					AfterResponse:       downloadManager.options.AfterResponse,
   264  					AccelerateUploading: downloadManager.options.AccelerateUploading,
   265  				}
   266  				if hostRetryConfig := downloadManager.options.HostRetryConfig; hostRetryConfig != nil {
   267  					bucketRegionsQueryOptions.RetryMax = hostRetryConfig.RetryMax
   268  					bucketRegionsQueryOptions.Backoff = hostRetryConfig.Backoff
   269  				}
   270  				downloadManager.downloadURLsProvider = SignURLsProvider(
   271  					NewDefaultSrcURLsProvider(creds.AccessKey, &DefaultSrcURLsProviderOptions{
   272  						BucketRegionsQueryOptions: bucketRegionsQueryOptions,
   273  						BucketHosts:               httpclient.DefaultBucketHosts(),
   274  					}),
   275  					NewCredentialsSigner(creds),
   276  					&SignOptions{TTL: 3 * time.Minute},
   277  				)
   278  			})
   279  		}
   280  	}
   281  	return
   282  }
   283  
   284  func (downloadManager *DownloadManager) getCredentialsProvider() credentials.CredentialsProvider {
   285  	credentialsProvider := downloadManager.options.Credentials
   286  	if credentialsProvider == nil {
   287  		if defaultCreds := credentials.Default(); defaultCreds != nil {
   288  			credentialsProvider = defaultCreds
   289  		}
   290  	}
   291  	return credentialsProvider
   292  }
   293  
   294  func (w *writeSeekCloser) Write(p []byte) (int, error) {
   295  	return w.w.Write(p)
   296  }
   297  
   298  func (w *writeSeekCloser) Seek(offset int64, whence int) (int64, error) {
   299  	if seeker, ok := w.w.(io.Seeker); ok {
   300  		return seeker.Seek(offset, whence)
   301  	}
   302  	return 0, syscall.ESPIPE
   303  }
   304  
   305  func (w *writeSeekCloser) Close() error {
   306  	if closer, ok := w.w.(io.Closer); ok {
   307  		return closer.Close()
   308  	}
   309  	return nil
   310  }