mirror of https://github.com/Xhofe/alist
214 lines
6.3 KiB
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]
|
|
}
|
|
}
|