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 }