Repository: AlistGo/alist (language: go)

Issue:
夸克网盘添加大概一天后下载文件报错
### Alist Version / Alist 版本

v2.2.0

### Describe the bug / 问题描述

报错log：
```
ERRO[2022-03-27 19:19:39] <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>412 Precondition Failed</title></head>
<body>
<h1>412 Precondition Failed</h1>
<p>The precondition on the request for the URL evaluated to false.<hr/>Powered by Tengine</body>
 /html>


2022/03/27 19:19:39 [Recovery] 2022/03/27 - 19:19:39 panic recovered:
http: wrote more than the declared Content-Length
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/render/json.go:56 (0xe7fc2b)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:913 (0xe84e57)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:956 (0xe9569b)
/app/server/common/common.go:56 (0xe95628)
/app/server/controllers/proxy.go:64 (0xe9afc3)
/app/server/controllers/down.go:23 (0xe98773)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0xe9df1b)
/app/server/middlewares/down.go:26 (0xe9de04)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0xe8c6cb)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/recovery.go:99 (0xe8c6a8)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0xe8b997)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/logger.go:241 (0xe8b95c)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0xe8aebb)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go:489 (0xe8ab78)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go:445 (0xe8a777)
/usr/lib/go/src/net/http/server.go:2879 (0x6a162b)
/usr/lib/go/src/net/http/server.go:1930 (0x69cf23)
/usr/lib/go/src/runtime/asm_arm64.s:1133 (0x471013)
```
重新更换cookie可解决，两天前发现，第一次出现更换了cookie后正常，更换后过了大概24h（我两次使用间隔是24h，具体时间可能更短）又再次报错。
自己在浏览器上下载了个文件，构造下载链接替换cookie为之前的旧cookie后请求返回：
```
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>412 Precondition Failed</title></head>
<body>
<h1>412 Precondition Failed</h1>
<p>The precondition on the request for the URL evaluated to false.<hr/>Powered by Tengine</body>
</html>
```
cookie的`__puus` 字段有效期只有三个小时，过期后请求会重新设置`__puus` ，估计要加个定时更新cookie
### Reproduction / 复现链接

无

### Logs / 日志

_No response_
夸克网盘添加大概一天后下载文件报错
### Alist Version / Alist 版本

v2.2.0

### Describe the bug / 问题描述

报错log：
```
ERRO[2022-03-27 19:19:39] <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>412 Precondition Failed</title></head>
<body>
<h1>412 Precondition Failed</h1>
<p>The precondition on the request for the URL evaluated to false.<hr/>Powered by Tengine</body>
 /html>


2022/03/27 19:19:39 [Recovery] 2022/03/27 - 19:19:39 panic recovered:
http: wrote more than the declared Content-Length
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/render/json.go:56 (0xe7fc2b)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:913 (0xe84e57)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:956 (0xe9569b)
/app/server/common/common.go:56 (0xe95628)
/app/server/controllers/proxy.go:64 (0xe9afc3)
/app/server/controllers/down.go:23 (0xe98773)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0xe9df1b)
/app/server/middlewares/down.go:26 (0xe9de04)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0xe8c6cb)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/recovery.go:99 (0xe8c6a8)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0xe8b997)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/logger.go:241 (0xe8b95c)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0xe8aebb)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go:489 (0xe8ab78)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go:445 (0xe8a777)
/usr/lib/go/src/net/http/server.go:2879 (0x6a162b)
/usr/lib/go/src/net/http/server.go:1930 (0x69cf23)
/usr/lib/go/src/runtime/asm_arm64.s:1133 (0x471013)
```
重新更换cookie可解决，两天前发现，第一次出现更换了cookie后正常，更换后过了大概24h（我两次使用间隔是24h，具体时间可能更短）又再次报错。
自己在浏览器上下载了个文件，构造下载链接替换cookie为之前的旧cookie后请求返回：
```
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>412 Precondition Failed</title></head>
<body>
<h1>412 Precondition Failed</h1>
<p>The precondition on the request for the URL evaluated to false.<hr/>Powered by Tengine</body>
</html>
```
cookie的`__puus` 字段有效期只有三个小时，过期后请求会重新设置`__puus` ，估计要加个定时更新cookie
### Reproduction / 复现链接

无

### Logs / 日志

_No response_


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index c9380ebe850..0293d175b1f 100755
--- a/README.md
+++ b/README.md
@@ -86,4 +86,4 @@ The `AList` is open-source software licensed under the AGPL-3.0 license.
 
 ---
 
-> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)
\ No newline at end of file
+> [@Blog](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)
\ No newline at end of file
diff --git a/README_cn.md b/README_cn.md
index 0f15f212c33..3e27f6cd3e0 100644
--- a/README_cn.md
+++ b/README_cn.md
@@ -86,4 +86,4 @@
 
 ---
 
-> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)
\ No newline at end of file
+> [@Blog](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)
\ No newline at end of file
diff --git a/bootstrap/account.go b/bootstrap/account.go
index b5d8ea97c4c..8766e43ab04 100644
--- a/bootstrap/account.go
+++ b/bootstrap/account.go
@@ -3,6 +3,7 @@ package bootstrap
 import (
 	"github.com/Xhofe/alist/conf"
 	"github.com/Xhofe/alist/drivers/base"
+	"github.com/Xhofe/alist/drivers/operate"
 	"github.com/Xhofe/alist/model"
 	log "github.com/sirupsen/logrus"
 )
@@ -20,7 +21,8 @@ func InitAccounts() {
 			log.Errorf("no [%s] driver", account.Type)
 		} else {
 			log.Infof("start init account: [%s], type: [%s]", account.Name, account.Type)
-			err := driver.Save(&accounts[i], nil)
+			//err := driver.Save(&accounts[i], nil)
+			err := operate.Save(driver, &accounts[i], nil)
 			if err != nil {
 				log.Errorf("init account [%s] error:[%s]", account.Name, err.Error())
 			} else {
diff --git a/drivers/alidrive/alidrive.go b/drivers/alidrive/alidrive.go
index bdb1b9aaec9..bac9dd7aaba 100644
--- a/drivers/alidrive/alidrive.go
+++ b/drivers/alidrive/alidrive.go
@@ -228,7 +228,7 @@ func (driver AliDrive) batch(srcId, dstId string, url string, account *model.Acc
 		}
 		return fmt.Errorf("%s", e.Message)
 	}
-	status := jsoniter.Get(res.Body(), "status").ToInt()
+	status := jsoniter.Get(res.Body(), "responses", 0, "status").ToInt()
 	if status < 400 && status >= 100 {
 		return nil
 	}
diff --git a/drivers/alidrive/driver.go b/drivers/alidrive/driver.go
index 8ef0c7b07ce..a2a4ad7b6ff 100644
--- a/drivers/alidrive/driver.go
+++ b/drivers/alidrive/driver.go
@@ -94,15 +94,15 @@ func (driver AliDrive) Save(account *model.Account, old *model.Account) error {
 	log.Debugf("user info: %+v", resp)
 	account.DriveId = resp["default_drive_id"].(string)
 	cronId, err := conf.Cron.AddFunc("@every 2h", func() {
-		name := account.Name
-		log.Debugf("ali account name: %s", name)
-		newAccount, ok := model.GetAccount(name)
+		id := account.ID
+		log.Debugf("ali account id: %d", id)
+		newAccount, err := model.GetAccountById(id)
 		log.Debugf("ali account: %+v", newAccount)
-		if !ok {
+		if err != nil {
 			return
 		}
-		err = driver.RefreshToken(&newAccount)
-		_ = model.SaveAccount(&newAccount)
+		err = driver.RefreshToken(newAccount)
+		_ = model.SaveAccount(newAccount)
 	})
 	if err != nil {
 		return err
diff --git a/drivers/operate/operate.go b/drivers/operate/operate.go
index 3c9dcc35110..637826144a0 100644
--- a/drivers/operate/operate.go
+++ b/drivers/operate/operate.go
@@ -8,6 +8,10 @@ import (
 	"runtime/debug"
 )
 
+func Save(driver base.Driver, account, old *model.Account) error {
+	return driver.Save(account, old)
+}
+
 func Path(driver base.Driver, account *model.Account, path string) (*model.File, []model.File, error) {
 	return driver.Path(path, account)
 }
diff --git a/drivers/quark/driver.go b/drivers/quark/driver.go
index 15eba079fec..9adcb5ca246 100644
--- a/drivers/quark/driver.go
+++ b/drivers/quark/driver.go
@@ -64,6 +64,12 @@ func (driver Quark) Save(account *model.Account, old *model.Account) error {
 		return nil
 	}
 	_, err := driver.Get("/config", nil, nil, account)
+	if err == nil {
+		account.Status = "work"
+	} else {
+		account.Status = err.Error()
+	}
+	_ = model.SaveAccount(account)
 	return err
 }
 
diff --git a/drivers/quark/quark.go b/drivers/quark/quark.go
index e193346f788..80442ab30be 100644
--- a/drivers/quark/quark.go
+++ b/drivers/quark/quark.go
@@ -8,6 +8,7 @@ import (
 	"github.com/Xhofe/alist/drivers/base"
 	"github.com/Xhofe/alist/model"
 	"github.com/Xhofe/alist/utils"
+	"github.com/Xhofe/alist/utils/cookie"
 	"github.com/go-resty/resty/v2"
 	log "github.com/sirupsen/logrus"
 	"net/http"
@@ -62,7 +63,12 @@ func (driver Quark) Request(pathname string, method int, headers, query, form ma
 	if err != nil {
 		return nil, err
 	}
-	log.Debugf("%s response: %s", pathname, res.String())
+	__puus := cookie.GetCookie(res.Cookies(), "__puus")
+	if __puus != nil {
+		account.AccessToken = cookie.SetStr(account.AccessToken, "__puus", __puus.Value)
+		_ = model.SaveAccount(account)
+	}
+	//log.Debugf("%s response: %s", pathname, res.String())
 	if e.Status >= 400 || e.Code != 0 {
 		return nil, errors.New(e.Message)
 	}
diff --git a/drivers/s3/driver.go b/drivers/s3/driver.go
index 390ca08a0fd..8c5735bab6b 100644
--- a/drivers/s3/driver.go
+++ b/drivers/s3/driver.go
@@ -1,6 +1,7 @@
 package s3
 
 import (
+	"bytes"
 	"fmt"
 	"github.com/Xhofe/alist/conf"
 	"github.com/Xhofe/alist/drivers/base"
@@ -9,6 +10,7 @@ import (
 	"github.com/aws/aws-sdk-go/service/s3"
 	"github.com/aws/aws-sdk-go/service/s3/s3manager"
 	log "github.com/sirupsen/logrus"
+	"io/ioutil"
 	"net/url"
 	"path/filepath"
 	"time"
@@ -78,6 +80,7 @@ func (driver S3) Items() []base.Item {
 			Label:       "placeholder filename",
 			Type:        base.TypeString,
 			Description: "default empty string",
+			Default:     defaultPlaceholderName,
 		},
 		{
 			Name:  "bool_1",
@@ -213,8 +216,19 @@ func (driver S3) Preview(path string, account *model.Account) (interface{}, erro
 }
 
 func (driver S3) MakeDir(path string, account *model.Account) error {
-	// not support, default as success
-	return nil
+	// not support, generate a placeholder file
+	_, err := driver.File(path, account)
+	// exist
+	if err != base.ErrPathNotFound {
+		return nil
+	}
+	return driver.Upload(&model.FileStream{
+		File:       ioutil.NopCloser(bytes.NewReader([]byte{})),
+		Size:       0,
+		ParentPath: path,
+		Name:       getPlaceholderName(account.Zone),
+		MIMEType:   "application/octet-stream",
+	}, account)
 }
 
 func (driver S3) Move(src string, dst string, account *model.Account) error {
@@ -277,6 +291,7 @@ func (driver S3) Upload(file *model.FileStream, account *model.Account) error {
 	}
 	uploader := s3manager.NewUploader(s)
 	key := driver.GetKey(utils.Join(file.ParentPath, file.GetFileName()), account, false)
+	log.Debugln("key:", key)
 	input := &s3manager.UploadInput{
 		Bucket: &account.Bucket,
 		Key:    &key,
diff --git a/drivers/s3/s3.go b/drivers/s3/s3.go
index 98459218bd6..165f7d0ac2f 100644
--- a/drivers/s3/s3.go
+++ b/drivers/s3/s3.go
@@ -88,7 +88,7 @@ func (driver S3) List(prefix string, account *model.Account) ([]model.File, erro
 		}
 		for _, object := range listObjectsResult.Contents {
 			name := utils.Base(*object.Key)
-			if name == account.Zone {
+			if name == getPlaceholderName(account.Zone) {
 				continue
 			}
 			file := model.File{
@@ -152,7 +152,7 @@ func (driver S3) ListV2(prefix string, account *model.Account) ([]model.File, er
 		}
 		for _, object := range listObjectsResult.Contents {
 			name := utils.Base(*object.Key)
-			if name == account.Zone {
+			if name == getPlaceholderName(account.Zone) {
 				continue
 			}
 			file := model.File{
diff --git a/drivers/s3/util.go b/drivers/s3/util.go
new file mode 100644
index 00000000000..a4136d63cd3
--- /dev/null
+++ b/drivers/s3/util.go
@@ -0,0 +1,10 @@
+package s3
+
+var defaultPlaceholderName = ".placeholder"
+
+func getPlaceholderName(placeholder string) string {
+	if placeholder == "" {
+		return defaultPlaceholderName
+	}
+	return placeholder
+}
diff --git a/drivers/xunlei/xunlei.go b/drivers/xunlei/xunlei.go
index cd9f32f5f46..89440da0e7a 100644
--- a/drivers/xunlei/xunlei.go
+++ b/drivers/xunlei/xunlei.go
@@ -13,7 +13,7 @@ import (
 	log "github.com/sirupsen/logrus"
 )
 
-var xunleiClient = resty.New().SetHeaders(map[string]string{"Accept": "application/json;charset=UTF-8"})
+var xunleiClient = resty.New().SetHeaders(map[string]string{"Accept": "application/json;charset=UTF-8"}).SetTimeout(base.DefaultTimeout)
 
 // 一个账户只允许登陆一次
 var userStateCache = struct {
diff --git a/model/account.go b/model/account.go
index dc44c69ce4e..5c69c6be7e4 100644
--- a/model/account.go
+++ b/model/account.go
@@ -2,6 +2,7 @@ package model
 
 import (
 	"github.com/Xhofe/alist/conf"
+	"github.com/Xhofe/alist/utils"
 	log "github.com/sirupsen/logrus"
 	"strings"
 	"sync"
@@ -97,6 +98,7 @@ func RegisterAccount(account Account) {
 	accountsMap[account.Name] = account
 }
 
+// GetAccount 根据名称获取账号（不包含负载均衡账号） 用于定时任务更新账号
 func GetAccount(name string) (Account, bool) {
 	if len(accountsMap) == 1 {
 		for _, v := range accountsMap {
@@ -107,25 +109,28 @@ func GetAccount(name string) (Account, bool) {
 	return account, ok
 }
 
-func GetAccountsByName(name string) []Account {
-	accounts := make([]Account, 0)
-	if AccountsCount() == 1 {
-		account, _ := GetAccount("")
-		accounts = append(accounts, account)
-		return accounts
-	}
-	for _, v := range accountsMap {
-		if v.Name == name || strings.HasPrefix(v.Name, name+balance) {
-			accounts = append(accounts, v)
-		}
-	}
-	return accounts
-}
+// GetAccountsByName 根据名称获取账号（包含负载均衡账号）
+//func GetAccountsByName(name string) []Account {
+//	accounts := make([]Account, 0)
+//	if AccountsCount() == 1 {
+//		for _, v := range accountsMap {
+//			accounts = append(accounts, v)
+//		}
+//		return accounts
+//	}
+//	for _, v := range accountsMap {
+//		if v.Name == name || strings.HasPrefix(v.Name, name+balance) {
+//			accounts = append(accounts, v)
+//		}
+//	}
+//	return accounts
+//}
 
 var balanceMap sync.Map
 
+// GetBalancedAccount 根据名称获取账号，负载均衡之后的
 func GetBalancedAccount(name string) (Account, bool) {
-	accounts := GetAccountsByName(name)
+	accounts := GetAccountsByPath(name)
 	accountNum := len(accounts)
 	switch accountNum {
 	case 0:
@@ -147,6 +152,7 @@ func GetBalancedAccount(name string) (Account, bool) {
 	}
 }
 
+// GetAccountById 根据id获取账号，用于更新账号
 func GetAccountById(id uint) (*Account, error) {
 	var account Account
 	account.ID = id
@@ -156,31 +162,100 @@ func GetAccountById(id uint) (*Account, error) {
 	return &account, nil
 }
 
-func GetAccountFiles() ([]File, error) {
+// GetAccountFiles 获取账号虚拟文件（去除负载均衡）
+//func GetAccountFiles() ([]File, error) {
+//	files := make([]File, 0)
+//	var accounts []Account
+//	if err := conf.DB.Order(columnName("index")).Find(&accounts).Error; err != nil {
+//		return nil, err
+//	}
+//	for _, v := range accounts {
+//		if strings.Contains(v.Name, balance) {
+//			continue
+//		}
+//		files = append(files, File{
+//			Name:      v.Name,
+//			Size:      0,
+//			Driver:    v.Type,
+//			Type:      conf.FOLDER,
+//			UpdatedAt: v.UpdatedAt,
+//		})
+//	}
+//	return files, nil
+//}
+
+// GetAccounts 获取所有账号
+func GetAccounts() ([]Account, error) {
+	var accounts []Account
+	if err := conf.DB.Order(columnName("index")).Find(&accounts).Error; err != nil {
+		return nil, err
+	}
+	return accounts, nil
+}
+
+// GetAccountsByPath 根据路径获取账号，最长匹配，未负载均衡
+// 如有账号： /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) []Account {
+	accounts := make([]Account, 0)
+	curSlashCount := 0
+	for _, v := range accountsMap {
+		name := utils.ParsePath(v.Name)
+		bIndex := strings.LastIndex(name, balance)
+		if bIndex != -1 {
+			name = v.Name[:bIndex]
+		}
+		// 不是这个账号
+		if path != name && !strings.HasPrefix(path, name+"/") {
+			continue
+		}
+		slashCount := strings.Count(name, "/")
+		// 不是最长匹配
+		if slashCount < curSlashCount {
+			continue
+		}
+		if slashCount > curSlashCount {
+			accounts = accounts[:0]
+			curSlashCount = slashCount
+		}
+		accounts = append(accounts, v)
+	}
+	return accounts
+}
+
+// GetAccountFilesByPath 根据路径获取账号虚拟文件
+// 如有账号： /a/b,/a/c,/a/d/e,/a/b.balance1,/av
+// GetAccountFilesByPath(/a) => b,c,d
+func GetAccountFilesByPath(prefix string) ([]File, error) {
 	files := make([]File, 0)
 	var accounts []Account
 	if err := conf.DB.Order(columnName("index")).Find(&accounts).Error; err != nil {
 		return nil, err
 	}
+	prefix = utils.ParsePath(prefix)
+	set := make(map[string]interface{})
 	for _, v := range accounts {
+		// 负载均衡账号
 		if strings.Contains(v.Name, balance) {
 			continue
 		}
+		full := utils.ParsePath(v.Name)
+		// 不是以prefix为前缀
+		if !strings.HasPrefix(full, prefix+"/") && prefix != "/" {
+			continue
+		}
+		name := strings.Split(strings.TrimPrefix(strings.TrimPrefix(full, prefix), "/"), "/")[0]
+		if _, ok := set[name]; ok {
+			continue
+		}
 		files = append(files, File{
-			Name:      v.Name,
+			Name:      name,
 			Size:      0,
 			Driver:    v.Type,
 			Type:      conf.FOLDER,
 			UpdatedAt: v.UpdatedAt,
 		})
+		set[name] = nil
 	}
 	return files, nil
 }
-
-func GetAccounts() ([]Account, error) {
-	var accounts []Account
-	if err := conf.DB.Order(columnName("index")).Find(&accounts).Error; err != nil {
-		return nil, err
-	}
-	return accounts, nil
-}
diff --git a/model/setting.go b/model/setting.go
index 7cb7e26ca4c..fa41d814532 100644
--- a/model/setting.go
+++ b/model/setting.go
@@ -93,11 +93,11 @@ func LoadSettings() {
 	if err == nil {
 		conf.TextTypes = strings.Split(textTypes.Value, ",")
 	}
-	audioTypes, err := GetSettingByKey("text types")
+	audioTypes, err := GetSettingByKey("audio types")
 	if err == nil {
 		conf.AudioTypes = strings.Split(audioTypes.Value, ",")
 	}
-	videoTypes, err := GetSettingByKey("text types")
+	videoTypes, err := GetSettingByKey("video types")
 	if err == nil {
 		conf.VideoTypes = strings.Split(videoTypes.Value, ",")
 	}
diff --git a/server/common/common.go b/server/common/common.go
index 8abc198475a..ff4c7ea48aa 100644
--- a/server/common/common.go
+++ b/server/common/common.go
@@ -1,7 +1,6 @@
 package common
 
 import (
-	"errors"
 	"fmt"
 	"github.com/Xhofe/alist/drivers/base"
 	"github.com/Xhofe/alist/model"
@@ -25,30 +24,16 @@ type PathReq struct {
 }
 
 func ParsePath(rawPath string) (*model.Account, string, base.Driver, error) {
-	var path, name string
-	switch model.AccountsCount() {
-	case 0:
-		return nil, "", nil, fmt.Errorf("no accounts,please add one first")
-	case 1:
-		path = rawPath
-		break
-	default:
-		if path == "/" {
-			return nil, "", nil, errors.New("can't operate root of multiple accounts")
-		}
-		paths := strings.Split(rawPath, "/")
-		path = "/" + strings.Join(paths[2:], "/")
-		name = paths[1]
-	}
-	account, ok := model.GetBalancedAccount(name)
+	rawPath = utils.ParsePath(rawPath)
+	account, ok := model.GetBalancedAccount(rawPath)
 	if !ok {
-		return nil, "", nil, fmt.Errorf("no [%s] account", name)
+		return nil, "", nil, fmt.Errorf("path not found")
 	}
 	driver, ok := base.GetDriver(account.Type)
 	if !ok {
 		return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type)
 	}
-	return &account, path, driver, nil
+	return &account, strings.TrimPrefix(rawPath, utils.ParsePath(account.Name)), driver, nil
 }
 
 func ErrorResp(c *gin.Context, err error, code int) {
diff --git a/server/common/files.go b/server/common/files.go
new file mode 100644
index 00000000000..e051732bf44
--- /dev/null
+++ b/server/common/files.go
@@ -0,0 +1,37 @@
+package common
+
+import (
+	"github.com/Xhofe/alist/drivers/base"
+	"github.com/Xhofe/alist/drivers/operate"
+	"github.com/Xhofe/alist/model"
+)
+
+func Path(rawPath string) (*model.File, []model.File, *model.Account, base.Driver, string, error) {
+	account, path, driver, err := ParsePath(rawPath)
+	if err != nil {
+		if err.Error() == "path not found" {
+			accountFiles, err := model.GetAccountFilesByPath(rawPath)
+			if err != nil {
+				return nil, nil, nil, nil, "", err
+			}
+			if len(accountFiles) != 0 {
+				return nil, accountFiles, nil, nil, path, nil
+			}
+		}
+		return nil, nil, nil, nil, "", err
+	}
+	file, files, err := operate.Path(driver, account, path)
+	if err != nil {
+		return nil, nil, nil, nil, "", err
+	}
+	if file != nil {
+		return file, nil, account, driver, path, nil
+	} else {
+		accountFiles, err := model.GetAccountFilesByPath(rawPath)
+		if err != nil {
+			return nil, nil, nil, nil, "", err
+		}
+		files = append(files, accountFiles...)
+		return nil, files, account, driver, path, nil
+	}
+}
diff --git a/server/controllers/account.go b/server/controllers/account.go
index 44ef55d9723..a705f4c7e90 100644
--- a/server/controllers/account.go
+++ b/server/controllers/account.go
@@ -3,6 +3,7 @@ package controllers
 import (
 	"fmt"
 	"github.com/Xhofe/alist/drivers/base"
+	"github.com/Xhofe/alist/drivers/operate"
 	"github.com/Xhofe/alist/model"
 	"github.com/Xhofe/alist/server/common"
 	"github.com/gin-gonic/gin"
@@ -37,7 +38,8 @@ func CreateAccount(c *gin.Context) {
 		common.ErrorResp(c, err, 500)
 	} else {
 		log.Debugf("new account: %+v", req)
-		err = driver.Save(&req, nil)
+		//err = driver.Save(&req, nil)
+		err = operate.Save(driver, &req, nil)
 		if err != nil {
 			common.ErrorResp(c, err, 500)
 			return
@@ -71,7 +73,8 @@ func SaveAccount(c *gin.Context) {
 		common.ErrorResp(c, err, 500)
 	} else {
 		log.Debugf("save account: %+v", req)
-		err = driver.Save(&req, old)
+		//err = driver.Save(&req, old)
+		err = operate.Save(driver, &req, nil)
 		if err != nil {
 			common.ErrorResp(c, err, 500)
 			return
@@ -93,7 +96,8 @@ func DeleteAccount(c *gin.Context) {
 	} else {
 		driver, ok := base.GetDriver(account.Type)
 		if ok {
-			_ = driver.Save(nil, account)
+			//_ = driver.Save(nil, account)
+			_ = operate.Save(driver, nil, account)
 		} else {
 			log.Errorf("no driver: %s", account.Type)
 		}
diff --git a/server/controllers/file/folder.go b/server/controllers/file/folder.go
index 293beebbc4c..ada34bd4aed 100644
--- a/server/controllers/file/folder.go
+++ b/server/controllers/file/folder.go
@@ -1,7 +1,6 @@
 package file
 
 import (
-	"github.com/Xhofe/alist/drivers/operate"
 	"github.com/Xhofe/alist/model"
 	"github.com/Xhofe/alist/server/common"
 	"github.com/gin-gonic/gin"
@@ -18,31 +17,18 @@ func Folder(c *gin.Context) {
 		return
 	}
 	var files = make([]model.File, 0)
-	var err error
-	if model.AccountsCount() > 1 && (req.Path == "/" || req.Path == "") {
-		files, err = model.GetAccountFiles()
-		if err != nil {
-			common.ErrorResp(c, err, 500)
-			return
-		}
-	} else {
-		account, path, driver, err := common.ParsePath(req.Path)
-		if err != nil {
-			common.ErrorResp(c, err, 500)
-			return
-		}
-		file, rawFiles, err := operate.Path(driver, account, path)
-		if err != nil {
-			common.ErrorResp(c, err, 500)
-			return
-		}
-		if file != nil {
-			common.ErrorStrResp(c, "Not folder", 400)
-		}
-		for _, file := range rawFiles {
-			if file.IsDir() {
-				files = append(files, file)
-			}
+	_, rawFiles, _, _, _, err := common.Path(req.Path)
+	if err != nil {
+		common.ErrorResp(c, err, 500)
+		return
+	}
+	if rawFiles == nil {
+		common.ErrorStrResp(c, "not a folder", 400)
+		return
+	}
+	for _, file := range rawFiles {
+		if file.IsDir() {
+			files = append(files, file)
 		}
 	}
 	c.JSON(200, common.Resp{
diff --git a/server/controllers/path.go b/server/controllers/path.go
index d3cc026c361..f3580748c24 100644
--- a/server/controllers/path.go
+++ b/server/controllers/path.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"github.com/Xhofe/alist/conf"
 	"github.com/Xhofe/alist/drivers/base"
-	"github.com/Xhofe/alist/drivers/operate"
 	"github.com/Xhofe/alist/model"
 	"github.com/Xhofe/alist/server/common"
 	"github.com/Xhofe/alist/utils"
@@ -79,39 +78,12 @@ func Path(c *gin.Context) {
 	if meta != nil && meta.Upload {
 		upload = true
 	}
-	if model.AccountsCount() > 1 && (req.Path == "/" || req.Path == "") {
-		files, err := model.GetAccountFiles()
-		if err != nil {
-			common.ErrorResp(c, err, 500)
-			return
-		}
-		if !ok {
-			files = common.Hide(meta, files)
-		}
-		c.JSON(200, common.Resp{
-			Code:    200,
-			Message: "success",
-			Data: PathResp{
-				Type: "folder",
-				Meta: Meta{
-					Driver: "root",
-				},
-				Files: files,
-			},
-		})
-		return
-	}
 	err := CheckPagination(&req)
 	if err != nil {
 		common.ErrorResp(c, err, 400)
 		return
 	}
-	account, path, driver, err := common.ParsePath(req.Path)
-	if err != nil {
-		common.ErrorResp(c, err, 500)
-		return
-	}
-	file, files, err := operate.Path(driver, account, path)
+	file, files, account, driver, path, err := common.Path(req.Path)
 	if err != nil {
 		common.ErrorResp(c, err, 500)
 		return
@@ -147,10 +119,14 @@ func Path(c *gin.Context) {
 		if !ok {
 			files = common.Hide(meta, files)
 		}
-		if driver.Config().LocalSort {
-			model.SortFiles(files, account)
+		driverName := "root"
+		if driver != nil {
+			if driver.Config().LocalSort {
+				model.SortFiles(files, account)
+			}
+			model.ExtractFolder(files, account)
+			driverName = driver.Config().Name
 		}
-		model.ExtractFolder(files, account)
 		total, files := Pagination(files, &req)
 		c.JSON(200, common.Resp{
 			Code:    200,
@@ -158,7 +134,7 @@ func Path(c *gin.Context) {
 			Data: PathResp{
 				Type: "folder",
 				Meta: Meta{
-					Driver: driver.Config().Name,
+					Driver: driverName,
 					Upload: upload,
 					Total:  total,
 				},
diff --git a/server/webdav/file.go b/server/webdav/file.go
index 995cc26b75d..5a61d4217b1 100644
--- a/server/webdav/file.go
+++ b/server/webdav/file.go
@@ -50,17 +50,18 @@ func (fs *FileSystem) File(rawPath string) (*model.File, error) {
 
 func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File, error) {
 	rawPath = utils.ParsePath(rawPath)
-	var files []model.File
-	var err error
-	if model.AccountsCount() > 1 && rawPath == "/" {
-		files, err = model.GetAccountFiles()
-	} else {
-		account, path_, driver, err := common.ParsePath(rawPath)
-		if err != nil {
-			return nil, err
-		}
-		files, err = operate.Files(driver, account, path_)
-	}
+	//var files []model.File
+	//var err error
+	//if model.AccountsCount() > 1 && rawPath == "/" {
+	//	files, err = model.GetAccountFilesByPath("/")
+	//} else {
+	//	account, path_, driver, err := common.ParsePath(rawPath)
+	//	if err != nil {
+	//		return nil, err
+	//	}
+	//	files, err = operate.Files(driver, account, path_)
+	//}
+	_, files, _, _, _, err := common.Path(rawPath)
 	if err != nil {
 		return nil, err
 	}
@@ -125,10 +126,10 @@ func (fs *FileSystem) Link(w http.ResponseWriter, r *http.Request, rawPath strin
 	}
 	if driver.Config().OnlyProxy || account.WebdavProxy {
 		link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath)
-		if conf.GetBool("check down link") {
-			sign := utils.SignWithToken(utils.Base(rawPath), conf.Token)
-			link += "?sign=" + sign
-		}
+		//if conf.GetBool("check down link") {
+		sign := utils.SignWithToken(utils.Base(rawPath), conf.Token)
+		link += "?sign=" + sign
+		//}
 	} else {
 		link_, err := driver.Link(base.Args{Path: path_, IP: ClientIP(r)}, account)
 		if err != nil {
diff --git a/utils/cookie/cookie.go b/utils/cookie/cookie.go
new file mode 100644
index 00000000000..8a6ca859351
--- /dev/null
+++ b/utils/cookie/cookie.go
@@ -0,0 +1,59 @@
+package cookie
+
+import (
+	"net/http"
+	"strings"
+)
+
+func Parse(str string) []*http.Cookie {
+	header := http.Header{}
+	header.Add("Cookie", str)
+	request := http.Request{Header: header}
+	return request.Cookies()
+}
+
+func ToString(cookies []*http.Cookie) string {
+	if cookies == nil {
+		return ""
+	}
+	cookieStrings := make([]string, len(cookies))
+	for i, cookie := range cookies {
+		cookieStrings[i] = cookie.String()
+	}
+	return strings.Join(cookieStrings, ";")
+}
+
+func SetCookie(cookies []*http.Cookie, name, value string) []*http.Cookie {
+	for i, cookie := range cookies {
+		if cookie.Name == name {
+			cookies[i].Value = value
+			return cookies
+		}
+	}
+	cookies = append(cookies, &http.Cookie{Name: name, Value: value})
+	return cookies
+}
+
+func GetCookie(cookies []*http.Cookie, name string) *http.Cookie {
+	for _, cookie := range cookies {
+		if cookie.Name == name {
+			return cookie
+		}
+	}
+	return nil
+}
+
+func SetStr(cookiesStr, name, value string) string {
+	cookies := Parse(cookiesStr)
+	cookies = SetCookie(cookies, name, value)
+	return ToString(cookies)
+}
+
+func GetStr(cookiesStr, name string) string {
+	cookies := Parse(cookiesStr)
+	cookie := GetCookie(cookies, name)
+	if cookie == nil {
+		return ""
+	}
+	return cookie.Value
+}


Test patch: