Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/internal/op/fs.go
1560 views
1
package op
2
3
import (
4
"context"
5
stdpath "path"
6
"slices"
7
"time"
8
9
"github.com/Xhofe/go-cache"
10
"github.com/alist-org/alist/v3/internal/driver"
11
"github.com/alist-org/alist/v3/internal/errs"
12
"github.com/alist-org/alist/v3/internal/model"
13
"github.com/alist-org/alist/v3/internal/stream"
14
"github.com/alist-org/alist/v3/pkg/generic_sync"
15
"github.com/alist-org/alist/v3/pkg/singleflight"
16
"github.com/alist-org/alist/v3/pkg/utils"
17
"github.com/pkg/errors"
18
log "github.com/sirupsen/logrus"
19
)
20
21
// In order to facilitate adding some other things before and after file op
22
23
var listCache = cache.NewMemCache(cache.WithShards[[]model.Obj](64))
24
var listG singleflight.Group[[]model.Obj]
25
26
func updateCacheObj(storage driver.Driver, path string, oldObj model.Obj, newObj model.Obj) {
27
key := Key(storage, path)
28
objs, ok := listCache.Get(key)
29
if ok {
30
for i, obj := range objs {
31
if obj.GetName() == newObj.GetName() {
32
objs = slices.Delete(objs, i, i+1)
33
break
34
}
35
}
36
for i, obj := range objs {
37
if obj.GetName() == oldObj.GetName() {
38
objs[i] = newObj
39
break
40
}
41
}
42
listCache.Set(key, objs, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
43
}
44
}
45
46
func delCacheObj(storage driver.Driver, path string, obj model.Obj) {
47
key := Key(storage, path)
48
objs, ok := listCache.Get(key)
49
if ok {
50
for i, oldObj := range objs {
51
if oldObj.GetName() == obj.GetName() {
52
objs = append(objs[:i], objs[i+1:]...)
53
break
54
}
55
}
56
listCache.Set(key, objs, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
57
}
58
}
59
60
var addSortDebounceMap generic_sync.MapOf[string, func(func())]
61
62
func addCacheObj(storage driver.Driver, path string, newObj model.Obj) {
63
key := Key(storage, path)
64
objs, ok := listCache.Get(key)
65
if ok {
66
for i, obj := range objs {
67
if obj.GetName() == newObj.GetName() {
68
objs[i] = newObj
69
return
70
}
71
}
72
73
// Simple separation of files and folders
74
if len(objs) > 0 && objs[len(objs)-1].IsDir() == newObj.IsDir() {
75
objs = append(objs, newObj)
76
} else {
77
objs = append([]model.Obj{newObj}, objs...)
78
}
79
80
if storage.Config().LocalSort {
81
debounce, _ := addSortDebounceMap.LoadOrStore(key, utils.NewDebounce(time.Minute))
82
log.Debug("addCacheObj: wait start sort")
83
debounce(func() {
84
log.Debug("addCacheObj: start sort")
85
model.SortFiles(objs, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection)
86
addSortDebounceMap.Delete(key)
87
})
88
}
89
90
listCache.Set(key, objs, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
91
}
92
}
93
94
func ClearCache(storage driver.Driver, path string) {
95
objs, ok := listCache.Get(Key(storage, path))
96
if ok {
97
for _, obj := range objs {
98
if obj.IsDir() {
99
ClearCache(storage, stdpath.Join(path, obj.GetName()))
100
}
101
}
102
}
103
listCache.Del(Key(storage, path))
104
}
105
106
func Key(storage driver.Driver, path string) string {
107
return stdpath.Join(storage.GetStorage().MountPath, utils.FixAndCleanPath(path))
108
}
109
110
// List files in storage, not contains virtual file
111
func List(ctx context.Context, storage driver.Driver, path string, args model.ListArgs) ([]model.Obj, error) {
112
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
113
return nil, errors.Errorf("storage not init: %s", storage.GetStorage().Status)
114
}
115
path = utils.FixAndCleanPath(path)
116
log.Debugf("op.List %s", path)
117
key := Key(storage, path)
118
if !args.Refresh {
119
if files, ok := listCache.Get(key); ok {
120
log.Debugf("use cache when list %s", path)
121
return files, nil
122
}
123
}
124
dir, err := GetUnwrap(ctx, storage, path)
125
if err != nil {
126
return nil, errors.WithMessage(err, "failed get dir")
127
}
128
log.Debugf("list dir: %+v", dir)
129
if !dir.IsDir() {
130
return nil, errors.WithStack(errs.NotFolder)
131
}
132
objs, err, _ := listG.Do(key, func() ([]model.Obj, error) {
133
files, err := storage.List(ctx, dir, args)
134
if err != nil {
135
return nil, errors.Wrapf(err, "failed to list objs")
136
}
137
// set path
138
for _, f := range files {
139
if s, ok := f.(model.SetPath); ok && f.GetPath() == "" && dir.GetPath() != "" {
140
s.SetPath(stdpath.Join(dir.GetPath(), f.GetName()))
141
}
142
}
143
// warp obj name
144
model.WrapObjsName(files)
145
// call hooks
146
go func(reqPath string, files []model.Obj) {
147
HandleObjsUpdateHook(reqPath, files)
148
}(utils.GetFullPath(storage.GetStorage().MountPath, path), files)
149
150
// sort objs
151
if storage.Config().LocalSort {
152
model.SortFiles(files, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection)
153
}
154
model.ExtractFolder(files, storage.GetStorage().ExtractFolder)
155
156
if !storage.Config().NoCache {
157
if len(files) > 0 {
158
log.Debugf("set cache: %s => %+v", key, files)
159
listCache.Set(key, files, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
160
} else {
161
log.Debugf("del cache: %s", key)
162
listCache.Del(key)
163
}
164
}
165
return files, nil
166
})
167
return objs, err
168
}
169
170
// Get object from list of files
171
func Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, error) {
172
path = utils.FixAndCleanPath(path)
173
log.Debugf("op.Get %s", path)
174
175
// get the obj directly without list so that we can reduce the io
176
if g, ok := storage.(driver.Getter); ok {
177
obj, err := g.Get(ctx, path)
178
if err == nil {
179
return model.WrapObjName(obj), nil
180
}
181
}
182
183
// is root folder
184
if utils.PathEqual(path, "/") {
185
var rootObj model.Obj
186
if getRooter, ok := storage.(driver.GetRooter); ok {
187
obj, err := getRooter.GetRoot(ctx)
188
if err != nil {
189
return nil, errors.WithMessage(err, "failed get root obj")
190
}
191
rootObj = obj
192
} else {
193
switch r := storage.GetAddition().(type) {
194
case driver.IRootId:
195
rootObj = &model.Object{
196
ID: r.GetRootId(),
197
Name: RootName,
198
Size: 0,
199
Modified: storage.GetStorage().Modified,
200
IsFolder: true,
201
}
202
case driver.IRootPath:
203
rootObj = &model.Object{
204
Path: r.GetRootPath(),
205
Name: RootName,
206
Size: 0,
207
Modified: storage.GetStorage().Modified,
208
IsFolder: true,
209
}
210
default:
211
return nil, errors.Errorf("please implement IRootPath or IRootId or GetRooter method")
212
}
213
}
214
if rootObj == nil {
215
return nil, errors.Errorf("please implement IRootPath or IRootId or GetRooter method")
216
}
217
return &model.ObjWrapName{
218
Name: RootName,
219
Obj: rootObj,
220
}, nil
221
}
222
223
// not root folder
224
dir, name := stdpath.Split(path)
225
files, err := List(ctx, storage, dir, model.ListArgs{})
226
if err != nil {
227
return nil, errors.WithMessage(err, "failed get parent list")
228
}
229
for _, f := range files {
230
if f.GetName() == name {
231
return f, nil
232
}
233
}
234
log.Debugf("cant find obj with name: %s", name)
235
return nil, errors.WithStack(errs.ObjectNotFound)
236
}
237
238
func GetUnwrap(ctx context.Context, storage driver.Driver, path string) (model.Obj, error) {
239
obj, err := Get(ctx, storage, path)
240
if err != nil {
241
return nil, err
242
}
243
return model.UnwrapObj(obj), err
244
}
245
246
var linkCache = cache.NewMemCache(cache.WithShards[*model.Link](16))
247
var linkG singleflight.Group[*model.Link]
248
249
// Link get link, if is an url. should have an expiry time
250
func Link(ctx context.Context, storage driver.Driver, path string, args model.LinkArgs) (*model.Link, model.Obj, error) {
251
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
252
return nil, nil, errors.Errorf("storage not init: %s", storage.GetStorage().Status)
253
}
254
file, err := GetUnwrap(ctx, storage, path)
255
if err != nil {
256
return nil, nil, errors.WithMessage(err, "failed to get file")
257
}
258
if file.IsDir() {
259
return nil, nil, errors.WithStack(errs.NotFile)
260
}
261
key := Key(storage, path)
262
if link, ok := linkCache.Get(key); ok {
263
return link, file, nil
264
}
265
fn := func() (*model.Link, error) {
266
link, err := storage.Link(ctx, file, args)
267
if err != nil {
268
return nil, errors.Wrapf(err, "failed get link")
269
}
270
if link.Expiration != nil {
271
if link.IPCacheKey {
272
key = key + ":" + args.IP
273
}
274
linkCache.Set(key, link, cache.WithEx[*model.Link](*link.Expiration))
275
}
276
return link, nil
277
}
278
279
if storage.Config().OnlyLocal {
280
link, err := fn()
281
return link, file, err
282
}
283
284
link, err, _ := linkG.Do(key, fn)
285
return link, file, err
286
}
287
288
// Other api
289
func Other(ctx context.Context, storage driver.Driver, args model.FsOtherArgs) (interface{}, error) {
290
obj, err := GetUnwrap(ctx, storage, args.Path)
291
if err != nil {
292
return nil, errors.WithMessagef(err, "failed to get obj")
293
}
294
if o, ok := storage.(driver.Other); ok {
295
return o.Other(ctx, model.OtherArgs{
296
Obj: obj,
297
Method: args.Method,
298
Data: args.Data,
299
})
300
} else {
301
return nil, errs.NotImplement
302
}
303
}
304
305
var mkdirG singleflight.Group[interface{}]
306
307
func MakeDir(ctx context.Context, storage driver.Driver, path string, lazyCache ...bool) error {
308
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
309
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
310
}
311
path = utils.FixAndCleanPath(path)
312
key := Key(storage, path)
313
_, err, _ := mkdirG.Do(key, func() (interface{}, error) {
314
// check if dir exists
315
f, err := GetUnwrap(ctx, storage, path)
316
if err != nil {
317
if errs.IsObjectNotFound(err) {
318
parentPath, dirName := stdpath.Split(path)
319
err = MakeDir(ctx, storage, parentPath)
320
if err != nil {
321
return nil, errors.WithMessagef(err, "failed to make parent dir [%s]", parentPath)
322
}
323
parentDir, err := GetUnwrap(ctx, storage, parentPath)
324
// this should not happen
325
if err != nil {
326
return nil, errors.WithMessagef(err, "failed to get parent dir [%s]", parentPath)
327
}
328
329
switch s := storage.(type) {
330
case driver.MkdirResult:
331
var newObj model.Obj
332
newObj, err = s.MakeDir(ctx, parentDir, dirName)
333
if err == nil {
334
if newObj != nil {
335
addCacheObj(storage, parentPath, model.WrapObjName(newObj))
336
} else if !utils.IsBool(lazyCache...) {
337
ClearCache(storage, parentPath)
338
}
339
}
340
case driver.Mkdir:
341
err = s.MakeDir(ctx, parentDir, dirName)
342
if err == nil && !utils.IsBool(lazyCache...) {
343
ClearCache(storage, parentPath)
344
}
345
default:
346
return nil, errs.NotImplement
347
}
348
return nil, errors.WithStack(err)
349
}
350
return nil, errors.WithMessage(err, "failed to check if dir exists")
351
}
352
// dir exists
353
if f.IsDir() {
354
return nil, nil
355
}
356
// dir to make is a file
357
return nil, errors.New("file exists")
358
})
359
return err
360
}
361
362
func Move(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string, lazyCache ...bool) error {
363
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
364
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
365
}
366
srcPath = utils.FixAndCleanPath(srcPath)
367
dstDirPath = utils.FixAndCleanPath(dstDirPath)
368
srcRawObj, err := Get(ctx, storage, srcPath)
369
if err != nil {
370
return errors.WithMessage(err, "failed to get src object")
371
}
372
srcObj := model.UnwrapObj(srcRawObj)
373
dstDir, err := GetUnwrap(ctx, storage, dstDirPath)
374
if err != nil {
375
return errors.WithMessage(err, "failed to get dst dir")
376
}
377
srcDirPath := stdpath.Dir(srcPath)
378
379
switch s := storage.(type) {
380
case driver.MoveResult:
381
var newObj model.Obj
382
newObj, err = s.Move(ctx, srcObj, dstDir)
383
if err == nil {
384
delCacheObj(storage, srcDirPath, srcRawObj)
385
if newObj != nil {
386
addCacheObj(storage, dstDirPath, model.WrapObjName(newObj))
387
} else if !utils.IsBool(lazyCache...) {
388
ClearCache(storage, dstDirPath)
389
}
390
}
391
case driver.Move:
392
err = s.Move(ctx, srcObj, dstDir)
393
if err == nil {
394
delCacheObj(storage, srcDirPath, srcRawObj)
395
if !utils.IsBool(lazyCache...) {
396
ClearCache(storage, dstDirPath)
397
}
398
}
399
default:
400
return errs.NotImplement
401
}
402
return errors.WithStack(err)
403
}
404
405
func Rename(ctx context.Context, storage driver.Driver, srcPath, dstName string, lazyCache ...bool) error {
406
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
407
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
408
}
409
srcPath = utils.FixAndCleanPath(srcPath)
410
srcRawObj, err := Get(ctx, storage, srcPath)
411
if err != nil {
412
return errors.WithMessage(err, "failed to get src object")
413
}
414
srcObj := model.UnwrapObj(srcRawObj)
415
srcDirPath := stdpath.Dir(srcPath)
416
417
switch s := storage.(type) {
418
case driver.RenameResult:
419
var newObj model.Obj
420
newObj, err = s.Rename(ctx, srcObj, dstName)
421
if err == nil {
422
if newObj != nil {
423
updateCacheObj(storage, srcDirPath, srcRawObj, model.WrapObjName(newObj))
424
} else if !utils.IsBool(lazyCache...) {
425
ClearCache(storage, srcDirPath)
426
}
427
}
428
case driver.Rename:
429
err = s.Rename(ctx, srcObj, dstName)
430
if err == nil && !utils.IsBool(lazyCache...) {
431
ClearCache(storage, srcDirPath)
432
}
433
default:
434
return errs.NotImplement
435
}
436
return errors.WithStack(err)
437
}
438
439
// Copy Just copy file[s] in a storage
440
func Copy(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string, lazyCache ...bool) error {
441
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
442
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
443
}
444
srcPath = utils.FixAndCleanPath(srcPath)
445
dstDirPath = utils.FixAndCleanPath(dstDirPath)
446
srcObj, err := GetUnwrap(ctx, storage, srcPath)
447
if err != nil {
448
return errors.WithMessage(err, "failed to get src object")
449
}
450
dstDir, err := GetUnwrap(ctx, storage, dstDirPath)
451
if err != nil {
452
return errors.WithMessage(err, "failed to get dst dir")
453
}
454
455
switch s := storage.(type) {
456
case driver.CopyResult:
457
var newObj model.Obj
458
newObj, err = s.Copy(ctx, srcObj, dstDir)
459
if err == nil {
460
if newObj != nil {
461
addCacheObj(storage, dstDirPath, model.WrapObjName(newObj))
462
} else if !utils.IsBool(lazyCache...) {
463
ClearCache(storage, dstDirPath)
464
}
465
}
466
case driver.Copy:
467
err = s.Copy(ctx, srcObj, dstDir)
468
if err == nil && !utils.IsBool(lazyCache...) {
469
ClearCache(storage, dstDirPath)
470
}
471
default:
472
return errs.NotImplement
473
}
474
return errors.WithStack(err)
475
}
476
477
func Remove(ctx context.Context, storage driver.Driver, path string) error {
478
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
479
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
480
}
481
if utils.PathEqual(path, "/") {
482
return errors.New("delete root folder is not allowed, please goto the manage page to delete the storage instead")
483
}
484
path = utils.FixAndCleanPath(path)
485
rawObj, err := Get(ctx, storage, path)
486
if err != nil {
487
// if object not found, it's ok
488
if errs.IsObjectNotFound(err) {
489
log.Debugf("%s have been removed", path)
490
return nil
491
}
492
return errors.WithMessage(err, "failed to get object")
493
}
494
dirPath := stdpath.Dir(path)
495
496
switch s := storage.(type) {
497
case driver.Remove:
498
err = s.Remove(ctx, model.UnwrapObj(rawObj))
499
if err == nil {
500
delCacheObj(storage, dirPath, rawObj)
501
// clear folder cache recursively
502
if rawObj.IsDir() {
503
ClearCache(storage, path)
504
}
505
}
506
default:
507
return errs.NotImplement
508
}
509
return errors.WithStack(err)
510
}
511
512
func Put(ctx context.Context, storage driver.Driver, dstDirPath string, file model.FileStreamer, up driver.UpdateProgress, lazyCache ...bool) error {
513
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
514
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
515
}
516
defer func() {
517
if err := file.Close(); err != nil {
518
log.Errorf("failed to close file streamer, %v", err)
519
}
520
}()
521
// UrlTree PUT
522
if storage.GetStorage().Driver == "UrlTree" {
523
var link string
524
dstDirPath, link = urlTreeSplitLineFormPath(stdpath.Join(dstDirPath, file.GetName()))
525
file = &stream.FileStream{Obj: &model.Object{Name: link}}
526
}
527
// if file exist and size = 0, delete it
528
dstDirPath = utils.FixAndCleanPath(dstDirPath)
529
dstPath := stdpath.Join(dstDirPath, file.GetName())
530
tempName := file.GetName() + ".alist_to_delete"
531
tempPath := stdpath.Join(dstDirPath, tempName)
532
fi, err := GetUnwrap(ctx, storage, dstPath)
533
if err == nil {
534
if fi.GetSize() == 0 {
535
err = Remove(ctx, storage, dstPath)
536
if err != nil {
537
return errors.WithMessagef(err, "while uploading, failed remove existing file which size = 0")
538
}
539
} else if storage.Config().NoOverwriteUpload {
540
// try to rename old obj
541
err = Rename(ctx, storage, dstPath, tempName)
542
if err != nil {
543
return err
544
}
545
} else {
546
file.SetExist(fi)
547
}
548
}
549
err = MakeDir(ctx, storage, dstDirPath)
550
if err != nil {
551
return errors.WithMessagef(err, "failed to make dir [%s]", dstDirPath)
552
}
553
parentDir, err := GetUnwrap(ctx, storage, dstDirPath)
554
// this should not happen
555
if err != nil {
556
return errors.WithMessagef(err, "failed to get dir [%s]", dstDirPath)
557
}
558
// if up is nil, set a default to prevent panic
559
if up == nil {
560
up = func(p float64) {}
561
}
562
563
switch s := storage.(type) {
564
case driver.PutResult:
565
var newObj model.Obj
566
newObj, err = s.Put(ctx, parentDir, file, up)
567
if err == nil {
568
if newObj != nil {
569
addCacheObj(storage, dstDirPath, model.WrapObjName(newObj))
570
} else if !utils.IsBool(lazyCache...) {
571
ClearCache(storage, dstDirPath)
572
}
573
}
574
case driver.Put:
575
err = s.Put(ctx, parentDir, file, up)
576
if err == nil && !utils.IsBool(lazyCache...) {
577
ClearCache(storage, dstDirPath)
578
}
579
default:
580
return errs.NotImplement
581
}
582
log.Debugf("put file [%s] done", file.GetName())
583
if storage.Config().NoOverwriteUpload && fi != nil && fi.GetSize() > 0 {
584
if err != nil {
585
// upload failed, recover old obj
586
err := Rename(ctx, storage, tempPath, file.GetName())
587
if err != nil {
588
log.Errorf("failed recover old obj: %+v", err)
589
}
590
} else {
591
// upload success, remove old obj
592
err := Remove(ctx, storage, tempPath)
593
if err != nil {
594
return err
595
} else {
596
key := Key(storage, stdpath.Join(dstDirPath, file.GetName()))
597
linkCache.Del(key)
598
}
599
}
600
}
601
return errors.WithStack(err)
602
}
603
604
func PutURL(ctx context.Context, storage driver.Driver, dstDirPath, dstName, url string, lazyCache ...bool) error {
605
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
606
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
607
}
608
dstDirPath = utils.FixAndCleanPath(dstDirPath)
609
_, err := GetUnwrap(ctx, storage, stdpath.Join(dstDirPath, dstName))
610
if err == nil {
611
return errors.New("obj already exists")
612
}
613
err = MakeDir(ctx, storage, dstDirPath)
614
if err != nil {
615
return errors.WithMessagef(err, "failed to put url")
616
}
617
dstDir, err := GetUnwrap(ctx, storage, dstDirPath)
618
if err != nil {
619
return errors.WithMessagef(err, "failed to put url")
620
}
621
switch s := storage.(type) {
622
case driver.PutURLResult:
623
var newObj model.Obj
624
newObj, err = s.PutURL(ctx, dstDir, dstName, url)
625
if err == nil {
626
if newObj != nil {
627
addCacheObj(storage, dstDirPath, model.WrapObjName(newObj))
628
} else if !utils.IsBool(lazyCache...) {
629
ClearCache(storage, dstDirPath)
630
}
631
}
632
case driver.PutURL:
633
err = s.PutURL(ctx, dstDir, dstName, url)
634
if err == nil && !utils.IsBool(lazyCache...) {
635
ClearCache(storage, dstDirPath)
636
}
637
default:
638
return errs.NotImplement
639
}
640
log.Debugf("put url [%s](%s) done", dstName, url)
641
return errors.WithStack(err)
642
}
643
644