style: shorten name operations to op

This commit is contained in:
Noah Hsu
2022-08-31 21:01:15 +08:00
parent 9ec6d5be7a
commit 7ac1d14eeb
35 changed files with 110 additions and 110 deletions

157
internal/op/driver.go Normal file
View File

@ -0,0 +1,157 @@
package op
import (
"reflect"
"strings"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/pkg/errors"
)
type New func() driver.Driver
var driverNewMap = map[string]New{}
var driverInfoMap = map[string]driver.Info{}
func RegisterDriver(config driver.Config, driver New) {
// log.Infof("register driver: [%s]", config.Name)
registerDriverItems(config, driver().GetAddition())
driverNewMap[config.Name] = driver
}
func GetDriverNew(name string) (New, error) {
n, ok := driverNewMap[name]
if !ok {
return nil, errors.Errorf("no driver named: %s", name)
}
return n, nil
}
func GetDriverNames() []string {
var driverNames []string
for k := range driverInfoMap {
driverNames = append(driverNames, k)
}
return driverNames
}
func GetDriverInfoMap() map[string]driver.Info {
return driverInfoMap
}
func registerDriverItems(config driver.Config, addition driver.Additional) {
// log.Debugf("addition of %s: %+v", config.Name, addition)
tAddition := reflect.TypeOf(addition)
mainItems := getMainItems(config)
additionalItems := getAdditionalItems(tAddition, config.DefaultRoot)
driverInfoMap[config.Name] = driver.Info{
Common: mainItems,
Additional: additionalItems,
Config: config,
}
}
func getMainItems(config driver.Config) []driver.Item {
items := []driver.Item{{
Name: "mount_path",
Type: conf.TypeString,
Required: true,
Help: "",
}, {
Name: "index",
Type: conf.TypeNumber,
Help: "use to sort",
}, {
Name: "remark",
Type: conf.TypeText,
}, {
Name: "down_proxy_url",
Type: conf.TypeText,
}}
if !config.NoCache {
items = append(items, driver.Item{
Name: "cache_expiration",
Type: conf.TypeNumber,
Default: "30",
Required: true,
Help: "The cache expiration time for this storage",
})
}
if !config.OnlyProxy && !config.OnlyLocal {
items = append(items, []driver.Item{{
Name: "web_proxy",
Type: conf.TypeBool,
}, {
Name: "webdav_policy",
Type: conf.TypeSelect,
Options: "302_redirect, use_proxy_url, native_proxy",
Default: "302_redirect",
Required: true,
},
}...)
} else {
items = append(items, driver.Item{
Name: "webdav_policy",
Type: conf.TypeSelect,
Default: "native_proxy",
Options: "use_proxy_url, native_proxy",
Required: true,
})
}
if config.LocalSort {
items = append(items, []driver.Item{{
Name: "order_by",
Type: conf.TypeSelect,
Options: "name,size,modified",
}, {
Name: "order_direction",
Type: conf.TypeSelect,
Options: "asc,desc",
}}...)
}
items = append(items, driver.Item{
Name: "extract_folder",
Type: conf.TypeSelect,
Options: "front,back",
})
return items
}
func getAdditionalItems(t reflect.Type, defaultRoot string) []driver.Item {
var items []driver.Item
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Type.Kind() == reflect.Struct {
items = append(items, getAdditionalItems(field.Type, defaultRoot)...)
continue
}
tag := field.Tag
ignore, ok1 := tag.Lookup("ignore")
name, ok2 := tag.Lookup("json")
if (ok1 && ignore == "true") || !ok2 {
continue
}
item := driver.Item{
Name: name,
Type: strings.ToLower(field.Type.Name()),
Default: tag.Get("default"),
Options: tag.Get("options"),
Required: tag.Get("required") == "true",
Help: tag.Get("help"),
}
if tag.Get("type") != "" {
item.Type = tag.Get("type")
}
if item.Name == "root_folder" && item.Default == "" {
item.Default = defaultRoot
}
// set default type to string
if item.Type == "" {
item.Type = "string"
}
items = append(items, item)
}
return items
}

View File

@ -0,0 +1,17 @@
package op_test
import (
"testing"
_ "github.com/alist-org/alist/v3/drivers"
"github.com/alist-org/alist/v3/internal/op"
)
func TestDriverItemsMap(t *testing.T) {
itemsMap := op.GetDriverInfoMap()
if len(itemsMap) != 0 {
t.Logf("driverInfoMap: %v", itemsMap)
} else {
t.Errorf("expected driverInfoMap not empty, but got empty")
}
}

289
internal/op/fs.go Normal file
View File

@ -0,0 +1,289 @@
package op
import (
"context"
"os"
stdpath "path"
"strings"
"time"
"github.com/Xhofe/go-cache"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/singleflight"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
// In order to facilitate adding some other things before and after file op
var listCache = cache.NewMemCache(cache.WithShards[[]model.Obj](64))
var listG singleflight.Group[[]model.Obj]
func ClearCache(storage driver.Driver, path string) {
key := stdpath.Join(storage.GetStorage().MountPath, path)
listCache.Del(key)
}
// List files in storage, not contains virtual file
func List(ctx context.Context, storage driver.Driver, path string, args model.ListArgs, refresh ...bool) ([]model.Obj, error) {
path = utils.StandardizePath(path)
log.Debugf("op.List %s", path)
dir, err := Get(ctx, storage, path)
if err != nil {
return nil, errors.WithMessage(err, "failed get dir")
}
if !dir.IsDir() {
return nil, errors.WithStack(errs.NotFolder)
}
if storage.Config().NoCache {
objs, err := storage.List(ctx, dir, args)
return objs, errors.WithStack(err)
}
key := stdpath.Join(storage.GetStorage().MountPath, path)
if len(refresh) == 0 || !refresh[0] {
if files, ok := listCache.Get(key); ok {
return files, nil
}
}
objs, err, _ := listG.Do(key, func() ([]model.Obj, error) {
files, err := storage.List(ctx, dir, args)
if err != nil {
return nil, errors.Wrapf(err, "failed to list objs")
}
listCache.Set(key, files, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
return files, nil
})
return objs, err
}
func isRoot(path, rootFolderPath string) bool {
if utils.PathEqual(path, rootFolderPath) {
return true
}
rootFolderPath = strings.TrimSuffix(rootFolderPath, "/")
rootFolderPath = strings.TrimPrefix(rootFolderPath, "\\")
// relative path, this shouldn't happen, because root folder path is absolute
if utils.PathEqual(path, "/") && rootFolderPath == "." {
return true
}
return false
}
// Get object from list of files
func Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, error) {
path = utils.StandardizePath(path)
log.Debugf("op.Get %s", path)
if g, ok := storage.(driver.Getter); ok {
obj, err := g.Get(ctx, path)
if err == nil {
return obj, nil
}
}
// is root folder
if r, ok := storage.GetAddition().(driver.IRootFolderId); ok && utils.PathEqual(path, "/") {
return model.Object{
ID: r.GetRootFolderId(),
Name: "root",
Size: 0,
Modified: storage.GetStorage().Modified,
IsFolder: true,
}, nil
}
if r, ok := storage.GetAddition().(driver.IRootFolderPath); ok && isRoot(path, r.GetRootFolderPath()) {
return model.Object{
ID: r.GetRootFolderPath(),
Name: "root",
Size: 0,
Modified: storage.GetStorage().Modified,
IsFolder: true,
}, nil
}
// not root folder
dir, name := stdpath.Split(path)
files, err := List(ctx, storage, dir, model.ListArgs{})
if err != nil {
return nil, errors.WithMessage(err, "failed get parent list")
}
for _, f := range files {
if f.GetName() == name {
// use path as id, why don't set id in List function?
// because files maybe cache, set id here can reduce memory usage
if f.GetID() == "" {
if s, ok := f.(model.SetID); ok {
s.SetID(path)
}
}
return f, nil
}
}
return nil, errors.WithStack(errs.ObjectNotFound)
}
var linkCache = cache.NewMemCache(cache.WithShards[*model.Link](16))
var linkG singleflight.Group[*model.Link]
// Link get link, if is an url. should have an expiry time
func Link(ctx context.Context, storage driver.Driver, path string, args model.LinkArgs) (*model.Link, model.Obj, error) {
file, err := Get(ctx, storage, path)
if err != nil {
return nil, nil, errors.WithMessage(err, "failed to get file")
}
if file.IsDir() {
return nil, nil, errors.WithStack(errs.NotFile)
}
key := stdpath.Join(storage.GetStorage().MountPath, path)
if link, ok := linkCache.Get(key); ok {
return link, file, nil
}
fn := func() (*model.Link, error) {
link, err := storage.Link(ctx, file, args)
if err != nil {
return nil, errors.Wrapf(err, "failed get link")
}
if link.Expiration != nil {
linkCache.Set(key, link, cache.WithEx[*model.Link](*link.Expiration))
}
return link, nil
}
link, err, _ := linkG.Do(key, fn)
return link, file, err
}
// Other api
func Other(ctx context.Context, storage driver.Driver, args model.FsOtherArgs) (interface{}, error) {
obj, err := Get(ctx, storage, args.Path)
if err != nil {
return nil, errors.WithMessagef(err, "failed to get obj")
}
return storage.Other(ctx, model.OtherArgs{
Obj: obj,
Method: args.Method,
Data: args.Data,
})
}
func MakeDir(ctx context.Context, storage driver.Driver, path string) error {
// check if dir exists
f, err := Get(ctx, storage, path)
if err != nil {
if errs.IsObjectNotFound(err) {
parentPath, dirName := stdpath.Split(path)
err = MakeDir(ctx, storage, parentPath)
if err != nil {
return errors.WithMessagef(err, "failed to make parent dir [%s]", parentPath)
}
parentDir, err := Get(ctx, storage, parentPath)
// this should not happen
if err != nil {
return errors.WithMessagef(err, "failed to get parent dir [%s]", parentPath)
}
return errors.WithStack(storage.MakeDir(ctx, parentDir, dirName))
} else {
return errors.WithMessage(err, "failed to check if dir exists")
}
} else {
// dir exists
if f.IsDir() {
return nil
} else {
// dir to make is a file
return errors.New("file exists")
}
}
}
func Move(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string) error {
srcObj, err := Get(ctx, storage, srcPath)
if err != nil {
return errors.WithMessage(err, "failed to get src object")
}
dstDir, err := Get(ctx, storage, dstDirPath)
if err != nil {
return errors.WithMessage(err, "failed to get dst dir")
}
return errors.WithStack(storage.Move(ctx, srcObj, dstDir))
}
func Rename(ctx context.Context, storage driver.Driver, srcPath, dstName string) error {
srcObj, err := Get(ctx, storage, srcPath)
if err != nil {
return errors.WithMessage(err, "failed to get src object")
}
return errors.WithStack(storage.Rename(ctx, srcObj, dstName))
}
// Copy Just copy file[s] in a storage
func Copy(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string) error {
srcObj, err := Get(ctx, storage, srcPath)
if err != nil {
return errors.WithMessage(err, "failed to get src object")
}
dstDir, err := Get(ctx, storage, dstDirPath)
return errors.WithStack(storage.Copy(ctx, srcObj, dstDir))
}
func Remove(ctx context.Context, storage driver.Driver, path string) error {
obj, err := Get(ctx, storage, path)
if err != nil {
// if object not found, it's ok
if errs.IsObjectNotFound(err) {
return nil
}
return errors.WithMessage(err, "failed to get object")
}
return errors.WithStack(storage.Remove(ctx, obj))
}
func Put(ctx context.Context, storage driver.Driver, dstDirPath string, file model.FileStreamer, up driver.UpdateProgress) error {
defer func() {
if f, ok := file.GetReadCloser().(*os.File); ok {
err := os.RemoveAll(f.Name())
if err != nil {
log.Errorf("failed to remove file [%s]", f.Name())
}
}
}()
defer func() {
if err := file.Close(); err != nil {
log.Errorf("failed to close file streamer, %v", err)
}
}()
// if file exist and size = 0, delete it
dstPath := stdpath.Join(dstDirPath, file.GetName())
fi, err := Get(ctx, storage, dstPath)
if err == nil {
if fi.GetSize() == 0 {
err = Remove(ctx, storage, dstPath)
if err != nil {
return errors.WithMessagef(err, "failed remove file that exist and have size 0")
}
}
}
err = MakeDir(ctx, storage, dstDirPath)
if err != nil {
return errors.WithMessagef(err, "failed to make dir [%s]", dstDirPath)
}
parentDir, err := Get(ctx, storage, dstDirPath)
// this should not happen
if err != nil {
return errors.WithMessagef(err, "failed to get dir [%s]", dstDirPath)
}
// if up is nil, set a default to prevent panic
if up == nil {
up = func(p int) {}
}
err = storage.Put(ctx, parentDir, file, up)
log.Debugf("put file [%s] done", file.GetName())
if err == nil {
// set as complete
up(100)
// clear cache
key := stdpath.Join(storage.GetStorage().MountPath, dstDirPath)
listCache.Del(key)
}
return errors.WithStack(err)
}

39
internal/op/path.go Normal file
View File

@ -0,0 +1,39 @@
package op
import (
stdpath "path"
"strings"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
// ActualPath Get the actual path
// !!! maybe and \ in the path when use windows local
func ActualPath(storage driver.Additional, rawPath string) string {
if i, ok := storage.(driver.IRootFolderPath); ok {
rawPath = stdpath.Join(i.GetRootFolderPath(), rawPath)
}
return utils.StandardizePath(rawPath)
}
// GetStorageAndActualPath Get the corresponding storage and actual path
// for path: remove the virtual path prefix and join the actual root folder if exists
func GetStorageAndActualPath(rawPath string) (driver.Driver, string, error) {
rawPath = utils.StandardizePath(rawPath)
if strings.Contains(rawPath, "..") {
return nil, "", errors.WithStack(errs.RelativePath)
}
storage := GetBalancedStorage(rawPath)
if storage == nil {
return nil, "", errors.Errorf("can't find storage with rawPath: %s", rawPath)
}
log.Debugln("use storage: ", storage.GetStorage().MountPath)
virtualPath := utils.GetActualVirtualPath(storage.GetStorage().MountPath)
actualPath := strings.TrimPrefix(rawPath, virtualPath)
actualPath = ActualPath(storage.GetAddition(), actualPath)
return storage, actualPath, nil
}

325
internal/op/storage.go Normal file
View File

@ -0,0 +1,325 @@
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 virtual 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)
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)
storagesMap.Store(storage.MountPath, 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)
if err != nil {
return errors.Wrapf(err, "failed init storage but storage is already created")
}
log.Debugf("storage %+v is created", storageDriver)
storagesMap.Store(storage.MountPath, 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")
}
err = LoadStorage(ctx, *storage)
if err != nil {
return errors.WithMessage(err, "failed load storage")
}
// reget storage from db, because it maybe hava updated
storage, err = db.GetStorageById(id)
if err != nil {
return errors.WithMessage(err, "failed reget storage again")
}
storage.Disabled = false
err = db.UpdateStorage(storage)
if err != nil {
return errors.WithMessage(err, "failed update storage in db, but have load in memory. you can try restart")
}
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
storagesMap.Delete(storage.MountPath)
storage.Disabled = true
err = db.UpdateStorage(storage)
if err != nil {
return errors.WithMessage(err, "failed update storage in db, but have drop in memory. you can try restart")
}
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 {
// virtual 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)
if err != nil {
return errors.Wrapf(err, "failed init storage")
}
storagesMap.Store(storage.MountPath, 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().Index == storages[j].GetStorage().Index {
return storages[i].GetStorage().MountPath < storages[j].GetStorage().MountPath
}
return storages[i].GetStorage().Index < storages[j].GetStorage().Index
})
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]
}
}

View File

@ -0,0 +1,85 @@
package op_test
import (
"context"
"testing"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/pkg/utils"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func init() {
dB, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.Init(dB)
}
func TestCreateStorage(t *testing.T) {
var storages = []struct {
storage model.Storage
isErr bool
}{
{storage: model.Storage{Driver: "local", MountPath: "/local", Addition: `{"root_folder":"."}`}, isErr: false},
{storage: model.Storage{Driver: "local", MountPath: "/local", Addition: `{"root_folder":"."}`}, isErr: true},
{storage: model.Storage{Driver: "None", MountPath: "/none", Addition: `{"root_folder":"."}`}, isErr: true},
}
for _, storage := range storages {
err := op.CreateStorage(context.Background(), storage.storage)
if err != nil {
if !storage.isErr {
t.Errorf("failed to create storage: %+v", err)
} else {
t.Logf("expect failed to create storage: %+v", err)
}
}
}
}
func TestGetStorageVirtualFilesByPath(t *testing.T) {
setupStorages(t)
virtualFiles := op.GetStorageVirtualFilesByPath("/a")
var names []string
for _, virtualFile := range virtualFiles {
names = append(names, virtualFile.GetName())
}
var expectedNames = []string{"b", "c", "d"}
if utils.SliceEqual(names, expectedNames) {
t.Logf("passed")
} else {
t.Errorf("expected: %+v, got: %+v", expectedNames, names)
}
}
func TestGetBalancedStorage(t *testing.T) {
setupStorages(t)
storage := op.GetBalancedStorage("/a/d/e")
if storage.GetStorage().MountPath != "/a/d/e" {
t.Errorf("expected: /a/d/e, got: %+v", storage.GetStorage().MountPath)
}
storage = op.GetBalancedStorage("/a/d/e")
if storage.GetStorage().MountPath != "/a/d/e.balance" {
t.Errorf("expected: /a/d/e.balance, got: %+v", storage.GetStorage().MountPath)
}
}
func setupStorages(t *testing.T) {
var storages = []model.Storage{
{Driver: "local", MountPath: "/a/b", Index: 0, Addition: `{"root_folder":"."}`},
{Driver: "local", MountPath: "/a/c", Index: 1, Addition: `{"root_folder":"."}`},
{Driver: "local", MountPath: "/a/d", Index: 2, Addition: `{"root_folder":"."}`},
{Driver: "local", MountPath: "/a/d/e", Index: 3, Addition: `{"root_folder":"."}`},
{Driver: "local", MountPath: "/a/d/e.balance", Index: 4, Addition: `{"root_folder":"."}`},
}
for _, storage := range storages {
err := op.CreateStorage(context.Background(), storage)
if err != nil {
t.Fatalf("failed to create storage: %+v", err)
}
}
}