From 472d4799d12df494da4a73e134de0a07e370c1e4 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Wed, 18 Mar 2020 11:53:25 +0000 Subject: [PATCH] qingstor: make `rclone cleanup` remove pending multipart uploads older than 24h --- backend/qingstor/qingstor.go | 81 +++++++++++++++++++++++++++++++++--- docs/content/overview.md | 2 +- docs/content/qingstor.md | 6 +++ 3 files changed, 83 insertions(+), 6 deletions(-) diff --git a/backend/qingstor/qingstor.go b/backend/qingstor/qingstor.go index 91b038de5..037a0d42b 100644 --- a/backend/qingstor/qingstor.go +++ b/backend/qingstor/qingstor.go @@ -864,6 +864,76 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { }) } +// cleanUpBucket removes all pending multipart uploads for a given bucket +func (f *Fs) cleanUpBucket(ctx context.Context, bucket string) (err error) { + fs.Infof(f, "cleaning bucket %q of pending multipart uploads older than 24 hours", bucket) + bucketInit, err := f.svc.Bucket(bucket, f.zone) + if err != nil { + return err + } + maxLimit := int(listLimitSize) + var marker *string + for { + req := qs.ListMultipartUploadsInput{ + Limit: &maxLimit, + KeyMarker: marker, + } + var resp *qs.ListMultipartUploadsOutput + resp, err = bucketInit.ListMultipartUploads(&req) + if err != nil { + return errors.Wrap(err, "clean up bucket list multipart uploads") + } + for _, upload := range resp.Uploads { + if upload.Created != nil && upload.Key != nil && upload.UploadID != nil { + age := time.Since(*upload.Created) + if age > 24*time.Hour { + fs.Infof(f, "removing pending multipart upload for %q dated %v (%v ago)", *upload.Key, upload.Created, age) + req := qs.AbortMultipartUploadInput{ + UploadID: upload.UploadID, + } + _, abortErr := bucketInit.AbortMultipartUpload(*upload.Key, &req) + if abortErr != nil { + err = errors.Wrapf(abortErr, "failed to remove multipart upload for %q", *upload.Key) + fs.Errorf(f, "%v", err) + } + } else { + fs.Debugf(f, "ignoring pending multipart upload for %q dated %v (%v ago)", *upload.Key, upload.Created, age) + } + } + } + if resp.HasMore != nil && !*resp.HasMore { + break + } + // Use NextMarker if set, otherwise use last Key + if resp.NextKeyMarker == nil || *resp.NextKeyMarker == "" { + fs.Errorf(f, "Expecting NextKeyMarker but didn't find one") + break + } else { + marker = resp.NextKeyMarker + } + } + return err +} + +// CleanUp removes all pending multipart uploads +func (f *Fs) CleanUp(ctx context.Context) (err error) { + if f.rootBucket != "" { + return f.cleanUpBucket(ctx, f.rootBucket) + } + entries, err := f.listBuckets(ctx) + if err != nil { + return err + } + for _, entry := range entries { + cleanErr := f.cleanUpBucket(ctx, f.opt.Enc.FromStandardName(entry.Remote())) + if err != nil { + fs.Errorf(f, "Failed to cleanup bucket: %q", cleanErr) + err = cleanErr + } + } + return err +} + // readMetaData gets the metadata if it hasn't already been fetched // // it also sets the info @@ -1090,9 +1160,10 @@ func (o *Object) MimeType(ctx context.Context) string { // Check the interfaces are satisfied var ( - _ fs.Fs = &Fs{} - _ fs.Copier = &Fs{} - _ fs.Object = &Object{} - _ fs.ListRer = &Fs{} - _ fs.MimeTyper = &Object{} + _ fs.Fs = &Fs{} + _ fs.CleanUpper = &Fs{} + _ fs.Copier = &Fs{} + _ fs.Object = &Object{} + _ fs.ListRer = &Fs{} + _ fs.MimeTyper = &Object{} ) diff --git a/docs/content/overview.md b/docs/content/overview.md index 4051f1c04..fecb400d0 100644 --- a/docs/content/overview.md +++ b/docs/content/overview.md @@ -341,7 +341,7 @@ operations more efficient. | pCloud | Yes | Yes | Yes | Yes | Yes | No | No | No [#2178](https://github.com/rclone/rclone/issues/2178) | Yes | Yes | | premiumize.me | Yes | No | Yes | Yes | No | No | No | Yes | Yes | Yes | | put.io | Yes | No | Yes | Yes | Yes | No | Yes | No [#2178](https://github.com/rclone/rclone/issues/2178) | Yes | Yes | -| QingStor | No | Yes | No | No | No | Yes | No | No [#2178](https://github.com/rclone/rclone/issues/2178) | No | No | +| QingStor | No | Yes | No | No | Yes | Yes | No | No [#2178](https://github.com/rclone/rclone/issues/2178) | No | No | | SFTP | No | No | Yes | Yes | No | No | Yes | No [#2178](https://github.com/rclone/rclone/issues/2178) | Yes | Yes | | SugarSync | Yes | Yes | Yes | Yes | No | No | Yes | Yes | No | Yes | | WebDAV | Yes | Yes | Yes | Yes | No | No | Yes ‡ | No [#2178](https://github.com/rclone/rclone/issues/2178) | Yes | Yes | diff --git a/docs/content/qingstor.md b/docs/content/qingstor.md index c87bc1f2a..d52769798 100644 --- a/docs/content/qingstor.md +++ b/docs/content/qingstor.md @@ -105,6 +105,12 @@ rclone supports multipart uploads with QingStor which means that it can upload files bigger than 5GB. Note that files uploaded with multipart upload don't have an MD5SUM. +Note that incomplete multipart uploads older than 24 hours can be +removed with `rclone cleanup remote:bucket` just for one bucket +`rclone cleanup remote:` for all buckets. QingStor does not ever +remove incomplete multipart uploads so it may be necessary to run this +from time to time. + ### Buckets and Zone ### With QingStor you can list buckets (`rclone lsd`) using any zone,