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] } }