alist/internal/operations/account.go

214 lines
6.3 KiB
Go

package operations
import (
"context"
log "github.com/sirupsen/logrus"
"sort"
"strings"
"time"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/store"
"github.com/alist-org/alist/v3/pkg/generic_sync"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/pkg/errors"
)
// Although the driver type is stored,
// there is an account in each driver,
// so it should actually be an account, just wrapped by the driver
var accountsMap generic_sync.MapOf[string, driver.Driver]
func GetAccountByVirtualPath(virtualPath string) (driver.Driver, error) {
accountDriver, ok := accountsMap.Load(virtualPath)
if !ok {
return nil, errors.Errorf("no virtual path for an account is: %s", virtualPath)
}
return accountDriver, nil
}
// CreateAccount Save the account to database so account can get an id
// then instantiate corresponding driver and save it in memory
func CreateAccount(ctx context.Context, account model.Account) error {
account.Modified = time.Now()
account.VirtualPath = utils.StandardizationPath(account.VirtualPath)
err := store.CreateAccount(&account)
if err != nil {
return errors.WithMessage(err, "failed create account in database")
}
// already has an id
driverName := account.Driver
driverNew, err := GetDriverNew(driverName)
if err != nil {
return errors.WithMessage(err, "failed get driver new")
}
accountDriver := driverNew()
err = accountDriver.Init(ctx, account)
if err != nil {
return errors.WithMessage(err, "failed init account")
}
accountsMap.Store(account.VirtualPath, accountDriver)
return nil
}
// UpdateAccount update account
// get old account first
// drop the account then reinitialize
func UpdateAccount(ctx context.Context, account model.Account) error {
oldAccount, err := store.GetAccountById(account.ID)
if err != nil {
return errors.WithMessage(err, "failed get old account")
}
account.Modified = time.Now()
account.VirtualPath = utils.StandardizationPath(account.VirtualPath)
err = store.UpdateAccount(&account)
if err != nil {
return errors.WithMessage(err, "failed update account in database")
}
accountDriver, err := GetAccountByVirtualPath(oldAccount.VirtualPath)
if oldAccount.VirtualPath != account.VirtualPath {
// virtual path renamed, need to drop the account
accountsMap.Delete(oldAccount.VirtualPath)
}
if err != nil {
return errors.WithMessage(err, "failed get account driver")
}
err = accountDriver.Drop(ctx)
if err != nil {
return errors.WithMessage(err, "failed drop account")
}
err = accountDriver.Init(ctx, account)
if err != nil {
return errors.WithMessage(err, "failed init account")
}
accountsMap.Store(account.VirtualPath, accountDriver)
return nil
}
// MustSaveDriverAccount call from specific driver
func MustSaveDriverAccount(driver driver.Driver) {
err := saveDriverAccount(driver)
if err != nil {
log.Errorf("failed save driver account: %s", err)
}
}
func saveDriverAccount(driver driver.Driver) error {
account := driver.GetAccount()
addition := driver.GetAddition()
bytes, err := utils.Json.Marshal(addition)
if err != nil {
return errors.Wrap(err, "error while marshal addition")
}
account.Addition = string(bytes)
err = store.UpdateAccount(&account)
if err != nil {
return errors.WithMessage(err, "failed update account in database")
}
return nil
}
// GetAccountsByPath get account by longest match path, contains balance account.
// for example, there is /a/b,/a/c,/a/d/e,/a/d/e.balance
// GetAccountsByPath(/a/d/e/f) => /a/d/e,/a/d/e.balance
func getAccountsByPath(path string) []driver.Driver {
accounts := make([]driver.Driver, 0)
curSlashCount := 0
accountsMap.Range(func(key string, value driver.Driver) bool {
virtualPath := utils.GetActualVirtualPath(value.GetAccount().VirtualPath)
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 {
accounts = accounts[:0]
curSlashCount = slashCount
}
accounts = append(accounts, value)
return true
})
// make sure the order is the same for same input
sort.Slice(accounts, func(i, j int) bool {
return accounts[i].GetAccount().VirtualPath < accounts[j].GetAccount().VirtualPath
})
return accounts
}
// GetAccountVirtualFilesByPath Obtain the virtual file generated by the account according to the path
// for example, there are: /a/b,/a/c,/a/d/e,/a/b.balance1,/av
// GetAccountVirtualFilesByPath(/a) => b,c,d
func GetAccountVirtualFilesByPath(prefix string) []model.Obj {
files := make([]model.Obj, 0)
accounts := accountsMap.Values()
sort.Slice(accounts, func(i, j int) bool {
if accounts[i].GetAccount().Index == accounts[j].GetAccount().Index {
return accounts[i].GetAccount().VirtualPath < accounts[j].GetAccount().VirtualPath
}
return accounts[i].GetAccount().Index < accounts[j].GetAccount().Index
})
prefix = utils.StandardizationPath(prefix)
set := make(map[string]interface{})
for _, v := range accounts {
// TODO should save a balanced account
// balance account
if utils.IsBalance(v.GetAccount().VirtualPath) {
continue
}
virtualPath := v.GetAccount().VirtualPath
if len(virtualPath) <= len(prefix) {
continue
}
// not prefixed with `prefix`
if !strings.HasPrefix(virtualPath, prefix+"/") && prefix != "/" {
continue
}
name := strings.Split(strings.TrimPrefix(virtualPath, prefix), "/")[1]
if _, ok := set[name]; ok {
continue
}
files = append(files, model.Object{
Name: name,
Size: 0,
Modified: v.GetAccount().Modified,
})
set[name] = nil
}
return files
}
var balanceMap generic_sync.MapOf[string, int]
// GetBalancedAccount get account by path
func GetBalancedAccount(path string) driver.Driver {
path = utils.StandardizationPath(path)
accounts := getAccountsByPath(path)
accountNum := len(accounts)
switch accountNum {
case 0:
return nil
case 1:
return accounts[0]
default:
virtualPath := utils.GetActualVirtualPath(accounts[0].GetAccount().VirtualPath)
cur, ok := balanceMap.Load(virtualPath)
i := 0
if ok {
i = cur
i = (i + 1) % accountNum
balanceMap.Store(virtualPath, i)
} else {
balanceMap.Store(virtualPath, i)
}
return accounts[i]
}
}