package operations import ( "context" "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/utils" "github.com/pkg/errors" "sort" "strings" "sync" "time" ) // 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 = map[string]driver.Driver{} func GetAccountByVirtualPath(virtualPath string) (driver.Driver, error) { accountDriver, ok := accountsMap[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() 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[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() err = store.UpdateAccount(&account) if err != nil { return errors.WithMessage(err, "failed update account in database") } if oldAccount.VirtualPath != account.VirtualPath { // virtual path renamed delete(accountsMap, oldAccount.VirtualPath) } accountDriver, err := GetAccountByVirtualPath(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[account.VirtualPath] = accountDriver return nil } // SaveDriverAccount call from specific driver 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 for _, v := range accountsMap { virtualPath := utils.GetActualVirtualPath(v.GetAccount().VirtualPath) if virtualPath == "/" { virtualPath = "" } // not this if path != virtualPath && !strings.HasPrefix(path, virtualPath+"/") { continue } slashCount := strings.Count(virtualPath, "/") // not the longest match if slashCount < curSlashCount { continue } if slashCount > curSlashCount { accounts = accounts[:0] curSlashCount = slashCount } accounts = append(accounts, v) } // 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) []driver.FileInfo { files := make([]driver.FileInfo, 0) accounts := make([]driver.Driver, len(accountsMap)) i := 0 for _, v := range accountsMap { accounts[i] = v i += 1 } 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 { // balance account if utils.IsBalance(v.GetAccount().VirtualPath) { continue } full := v.GetAccount().VirtualPath if len(full) <= len(prefix) { continue } // not prefixed with `prefix` if !strings.HasPrefix(full, prefix+"/") && prefix != "/" { continue } name := strings.Split(strings.TrimPrefix(full, prefix), "/")[0] if _, ok := set[name]; ok { continue } files = append(files, model.File{ Name: name, Size: 0, Modified: v.GetAccount().Modified, }) set[name] = nil } return files } var balanceMap sync.Map // 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.(int) i = (i + 1) % accountNum balanceMap.Store(virtualPath, i) } else { balanceMap.Store(virtualPath, i) } return accounts[i] } }