alist/internal/op/storage.go

331 lines
9.7 KiB
Go

package op
import (
"context"
"fmt"
"sort"
"strings"
"time"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/generic_sync"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
// Although the driver type is stored,
// there is a storage in each driver,
// so it should actually be a storage, just wrapped by the driver
var storagesMap generic_sync.MapOf[string, driver.Driver]
func GetStorageByVirtualPath(virtualPath string) (driver.Driver, error) {
storageDriver, ok := storagesMap.Load(virtualPath)
if !ok {
return nil, errors.Errorf("no mount path for an storage is: %s", virtualPath)
}
return storageDriver, nil
}
// CreateStorage Save the storage to database so storage can get an id
// then instantiate corresponding driver and save it in memory
func CreateStorage(ctx context.Context, storage model.Storage) error {
storage.Modified = time.Now()
storage.MountPath = utils.StandardizePath(storage.MountPath)
var err error
// check driver first
driverName := storage.Driver
driverNew, err := GetDriverNew(driverName)
if err != nil {
return errors.WithMessage(err, "failed get driver new")
}
storageDriver := driverNew()
// insert storage to database
err = db.CreateStorage(&storage)
if err != nil {
return errors.WithMessage(err, "failed create storage in database")
}
// already has an id
err = storageDriver.Init(ctx, storage)
storagesMap.Store(storage.MountPath, storageDriver)
if err != nil {
storageDriver.GetStorage().SetStatus(fmt.Sprintf("%+v", err.Error()))
MustSaveDriverStorage(storageDriver)
return errors.Wrapf(err, "failed init storage but storage is already created")
} else {
storageDriver.GetStorage().SetStatus(WORK)
MustSaveDriverStorage(storageDriver)
}
log.Debugf("storage %+v is created", storageDriver)
return nil
}
// LoadStorage load exist storage in db to memory
func LoadStorage(ctx context.Context, storage model.Storage) error {
storage.MountPath = utils.StandardizePath(storage.MountPath)
// check driver first
driverName := storage.Driver
driverNew, err := GetDriverNew(driverName)
if err != nil {
return errors.WithMessage(err, "failed get driver new")
}
storageDriver := driverNew()
err = storageDriver.Init(ctx, storage)
storagesMap.Store(storage.MountPath, storageDriver)
if err != nil {
storageDriver.GetStorage().SetStatus(fmt.Sprintf("%+v", err.Error()))
MustSaveDriverStorage(storageDriver)
return errors.Wrapf(err, "failed init storage")
} else {
storageDriver.GetStorage().SetStatus(WORK)
MustSaveDriverStorage(storageDriver)
}
log.Debugf("storage %+v is created", storageDriver)
return nil
}
func EnableStorage(ctx context.Context, id uint) error {
storage, err := db.GetStorageById(id)
if err != nil {
return errors.WithMessage(err, "failed get storage")
}
if !storage.Disabled {
return errors.Errorf("this storage have enabled")
}
storage.Disabled = false
err = db.UpdateStorage(storage)
if err != nil {
return errors.WithMessage(err, "failed update storage in db")
}
err = LoadStorage(ctx, *storage)
if err != nil {
return errors.WithMessage(err, "failed load storage")
}
return nil
}
func DisableStorage(ctx context.Context, id uint) error {
storage, err := db.GetStorageById(id)
if err != nil {
return errors.WithMessage(err, "failed get storage")
}
if storage.Disabled {
return errors.Errorf("this storage have disabled")
}
storageDriver, err := GetStorageByVirtualPath(storage.MountPath)
if err != nil {
return errors.WithMessage(err, "failed get storage driver")
}
// drop the storage in the driver
if err := storageDriver.Drop(ctx); err != nil {
return errors.Wrapf(err, "failed drop storage")
}
// delete the storage in the memory
storage.Disabled = true
err = db.UpdateStorage(storage)
if err != nil {
return errors.WithMessage(err, "failed update storage in db")
}
storagesMap.Delete(storage.MountPath)
return nil
}
// UpdateStorage update storage
// get old storage first
// drop the storage then reinitialize
func UpdateStorage(ctx context.Context, storage model.Storage) error {
oldStorage, err := db.GetStorageById(storage.ID)
if err != nil {
return errors.WithMessage(err, "failed get old storage")
}
if oldStorage.Driver != storage.Driver {
return errors.Errorf("driver cannot be changed")
}
storage.Modified = time.Now()
storage.MountPath = utils.StandardizePath(storage.MountPath)
err = db.UpdateStorage(&storage)
if err != nil {
return errors.WithMessage(err, "failed update storage in database")
}
if storage.Disabled {
return nil
}
storageDriver, err := GetStorageByVirtualPath(oldStorage.MountPath)
if oldStorage.MountPath != storage.MountPath {
// mount path renamed, need to drop the storage
storagesMap.Delete(oldStorage.MountPath)
}
if err != nil {
return errors.WithMessage(err, "failed get storage driver")
}
err = storageDriver.Drop(ctx)
if err != nil {
return errors.Wrapf(err, "failed drop storage")
}
err = storageDriver.Init(ctx, storage)
storagesMap.Store(storage.MountPath, storageDriver)
if err != nil {
storageDriver.GetStorage().SetStatus(fmt.Sprintf("%+v", err.Error()))
MustSaveDriverStorage(storageDriver)
return errors.Wrapf(err, "failed init storage")
} else {
storageDriver.GetStorage().SetStatus(WORK)
MustSaveDriverStorage(storageDriver)
}
return nil
}
func DeleteStorageById(ctx context.Context, id uint) error {
storage, err := db.GetStorageById(id)
if err != nil {
return errors.WithMessage(err, "failed get storage")
}
storageDriver, err := GetStorageByVirtualPath(storage.MountPath)
if err != nil {
return errors.WithMessage(err, "failed get storage driver")
}
// drop the storage in the driver
if err := storageDriver.Drop(ctx); err != nil {
return errors.Wrapf(err, "failed drop storage")
}
// delete the storage in the database
if err := db.DeleteStorageById(id); err != nil {
return errors.WithMessage(err, "failed delete storage in database")
}
// delete the storage in the memory
storagesMap.Delete(storage.MountPath)
return nil
}
// MustSaveDriverStorage call from specific driver
func MustSaveDriverStorage(driver driver.Driver) {
err := saveDriverStorage(driver)
if err != nil {
log.Errorf("failed save driver storage: %s", err)
}
}
func saveDriverStorage(driver driver.Driver) error {
storage := driver.GetStorage()
addition := driver.GetAddition()
bytes, err := utils.Json.Marshal(addition)
if err != nil {
return errors.Wrap(err, "error while marshal addition")
}
storage.Addition = string(bytes)
err = db.UpdateStorage(storage)
if err != nil {
return errors.WithMessage(err, "failed update storage in database")
}
return nil
}
// getStoragesByPath get storage by longest match path, contains balance storage.
// for example, there is /a/b,/a/c,/a/d/e,/a/d/e.balance
// getStoragesByPath(/a/d/e/f) => /a/d/e,/a/d/e.balance
func getStoragesByPath(path string) []driver.Driver {
storages := make([]driver.Driver, 0)
curSlashCount := 0
storagesMap.Range(func(key string, value driver.Driver) bool {
virtualPath := utils.GetActualVirtualPath(value.GetStorage().MountPath)
if virtualPath == "/" {
virtualPath = ""
}
// not this
if path != virtualPath && !strings.HasPrefix(path, virtualPath+"/") {
return true
}
slashCount := strings.Count(virtualPath, "/")
// not the longest match
if slashCount < curSlashCount {
return true
}
if slashCount > curSlashCount {
storages = storages[:0]
curSlashCount = slashCount
}
storages = append(storages, value)
return true
})
// make sure the order is the same for same input
sort.Slice(storages, func(i, j int) bool {
return storages[i].GetStorage().MountPath < storages[j].GetStorage().MountPath
})
return storages
}
// GetStorageVirtualFilesByPath Obtain the virtual file generated by the storage according to the path
// for example, there are: /a/b,/a/c,/a/d/e,/a/b.balance1,/av
// GetStorageVirtualFilesByPath(/a) => b,c,d
func GetStorageVirtualFilesByPath(prefix string) []model.Obj {
files := make([]model.Obj, 0)
storages := storagesMap.Values()
sort.Slice(storages, func(i, j int) bool {
if storages[i].GetStorage().Order == storages[j].GetStorage().Order {
return storages[i].GetStorage().MountPath < storages[j].GetStorage().MountPath
}
return storages[i].GetStorage().Order < storages[j].GetStorage().Order
})
prefix = utils.StandardizePath(prefix)
if prefix != "/" {
prefix += "/"
}
set := make(map[string]interface{})
for _, v := range storages {
// TODO should save a balanced storage
// balance storage
if utils.IsBalance(v.GetStorage().MountPath) {
continue
}
virtualPath := v.GetStorage().MountPath
if len(virtualPath) <= len(prefix) {
continue
}
// not prefixed with `prefix`
if !strings.HasPrefix(virtualPath, prefix) {
continue
}
name := strings.Split(strings.TrimPrefix(virtualPath, prefix), "/")[0]
if _, ok := set[name]; ok {
continue
}
files = append(files, &model.Object{
Name: name,
Size: 0,
Modified: v.GetStorage().Modified,
IsFolder: true,
})
set[name] = nil
}
return files
}
var balanceMap generic_sync.MapOf[string, int]
// GetBalancedStorage get storage by path
func GetBalancedStorage(path string) driver.Driver {
path = utils.StandardizePath(path)
storages := getStoragesByPath(path)
storageNum := len(storages)
switch storageNum {
case 0:
return nil
case 1:
return storages[0]
default:
virtualPath := utils.GetActualVirtualPath(storages[0].GetStorage().MountPath)
cur, ok := balanceMap.Load(virtualPath)
i := 0
if ok {
i = cur
i = (i + 1) % storageNum
balanceMap.Store(virtualPath, i)
} else {
balanceMap.Store(virtualPath, i)
}
return storages[i]
}
}