Skip to content

Commit a673503

Browse files
committed
feat(s3): add placeholder toggle and fix empty-folder delete behavior
1 parent 6bde813 commit a673503

File tree

3 files changed

+58
-13
lines changed

3 files changed

+58
-13
lines changed

drivers/s3/driver.go

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ func (d *S3) GetAddition() driver.Additional {
6969
}
7070

7171
func (d *S3) Init(ctx context.Context) error {
72+
if !strings.Contains(d.Storage.Addition, `"use_placeholder"`) {
73+
d.UsePlaceholder = true
74+
}
7275
if d.Region == "" {
7376
d.Region = "alist"
7477
}
@@ -151,16 +154,20 @@ func (d *S3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*mo
151154
}
152155

153156
func (d *S3) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
154-
return d.Put(ctx, &model.Object{
155-
Path: stdpath.Join(parentDir.GetPath(), dirName),
156-
}, &stream.FileStream{
157-
Obj: &model.Object{
158-
Name: getPlaceholderName(d.Placeholder),
159-
Modified: time.Now(),
160-
},
161-
Reader: io.NopCloser(bytes.NewReader([]byte{})),
162-
Mimetype: "application/octet-stream",
163-
}, func(float64) {})
157+
dirPath := stdpath.Join(parentDir.GetPath(), dirName)
158+
if d.UsePlaceholder {
159+
return d.Put(ctx, &model.Object{
160+
Path: dirPath,
161+
}, &stream.FileStream{
162+
Obj: &model.Object{
163+
Name: getPlaceholderName(d.Placeholder),
164+
Modified: time.Now(),
165+
},
166+
Reader: io.NopCloser(bytes.NewReader([]byte{})),
167+
Mimetype: "application/octet-stream",
168+
}, func(float64) {})
169+
}
170+
return d.createDirMarker(ctx, dirPath)
164171
}
165172

166173
func (d *S3) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
@@ -214,6 +221,26 @@ func (d *S3) Put(ctx context.Context, dstDir model.Obj, s model.FileStreamer, up
214221
return err
215222
}
216223

224+
func (d *S3) putEmptyObject(ctx context.Context, key string) error {
225+
uploader := s3manager.NewUploader(d.Session)
226+
contentType := "application/octet-stream"
227+
input := &s3manager.UploadInput{
228+
Bucket: &d.Bucket,
229+
Key: &key,
230+
Body: driver.NewLimitedUploadStream(ctx, bytes.NewReader([]byte{})),
231+
ContentType: &contentType,
232+
}
233+
if storageClass := d.resolveStorageClass(); storageClass != nil {
234+
input.StorageClass = storageClass
235+
}
236+
_, err := uploader.UploadWithContext(ctx, input)
237+
return err
238+
}
239+
240+
func (d *S3) createDirMarker(ctx context.Context, dirPath string) error {
241+
return d.putEmptyObject(ctx, getKey(dirPath, true))
242+
}
243+
217244
var (
218245
_ driver.Driver = (*S3)(nil)
219246
_ driver.Other = (*S3)(nil)

drivers/s3/meta.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Addition struct {
1919
Placeholder string `json:"placeholder"`
2020
ForcePathStyle bool `json:"force_path_style"`
2121
ListObjectVersion string `json:"list_object_version" type:"select" options:"v1,v2" default:"v1"`
22+
UsePlaceholder bool `json:"use_placeholder" default:"true" help:"Create hidden placeholder file (for example .alist) to keep empty folders."`
2223
RemoveBucket bool `json:"remove_bucket" help:"Remove bucket name from path when using custom host."`
2324
AddFilenameToDisposition bool `json:"add_filename_to_disposition" help:"Add filename to Content-Disposition header."`
2425
StorageClass string `json:"storage_class" type:"select" options:",standard,standard_ia,onezone_ia,intelligent_tiering,glacier,glacier_ir,deep_archive,archive" help:"Storage class for new objects. AWS and Tencent COS support different subsets (COS uses ARCHIVE/DEEP_ARCHIVE)."`

drivers/s3/util.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"path"
99
"strings"
1010

11+
"github.com/alist-org/alist/v3/internal/errs"
1112
"github.com/alist-org/alist/v3/internal/model"
1213
"github.com/alist-org/alist/v3/internal/op"
1314
"github.com/alist-org/alist/v3/pkg/utils"
@@ -105,6 +106,9 @@ func (d *S3) listV1(prefix string, args model.ListArgs) ([]model.Obj, error) {
105106
files = append(files, &file)
106107
}
107108
for _, object := range listObjectsResult.Contents {
109+
if strings.HasSuffix(*object.Key, "/") {
110+
continue
111+
}
108112
name := path.Base(*object.Key)
109113
if !args.S3ShowPlaceholder && (name == getPlaceholderName(d.Placeholder) || name == d.Placeholder) {
110114
continue
@@ -210,10 +214,13 @@ func (d *S3) copyFile(ctx context.Context, src string, dst string) error {
210214
}
211215

212216
func (d *S3) copyDir(ctx context.Context, src string, dst string) error {
213-
objs, err := op.List(ctx, d, src, model.ListArgs{S3ShowPlaceholder: true})
217+
objs, err := op.List(ctx, d, src, model.ListArgs{S3ShowPlaceholder: true, Refresh: true})
214218
if err != nil {
215219
return err
216220
}
221+
if len(objs) == 0 && !d.UsePlaceholder {
222+
return d.createDirMarker(ctx, dst)
223+
}
217224
for _, obj := range objs {
218225
cSrc := path.Join(src, obj.GetName())
219226
cDst := path.Join(dst, obj.GetName())
@@ -230,8 +237,13 @@ func (d *S3) copyDir(ctx context.Context, src string, dst string) error {
230237
}
231238

232239
func (d *S3) removeDir(ctx context.Context, src string) error {
233-
objs, err := op.List(ctx, d, src, model.ListArgs{})
240+
d.cleanupDirArtifacts(src)
241+
242+
objs, err := op.List(ctx, d, src, model.ListArgs{Refresh: true})
234243
if err != nil {
244+
if errs.IsObjectNotFound(err) {
245+
return nil
246+
}
235247
return err
236248
}
237249
for _, obj := range objs {
@@ -245,9 +257,14 @@ func (d *S3) removeDir(ctx context.Context, src string) error {
245257
return err
246258
}
247259
}
260+
d.cleanupDirArtifacts(src)
261+
return nil
262+
}
263+
264+
func (d *S3) cleanupDirArtifacts(src string) {
248265
_ = d.removeFile(path.Join(src, getPlaceholderName(d.Placeholder)))
249266
_ = d.removeFile(path.Join(src, d.Placeholder))
250-
return nil
267+
_ = d.removeFile(getKey(src, true))
251268
}
252269

253270
func (d *S3) removeFile(src string) error {

0 commit comments

Comments
 (0)