refactor: change driver interface

This commit is contained in:
Noah Hsu 2022-06-15 20:31:23 +08:00
parent d9eb188b7a
commit 09ef7c7106
12 changed files with 181 additions and 136 deletions

View File

@ -18,11 +18,6 @@ type Scheme struct {
KeyFile string `json:"key_file" env:"KEY_FILE"` KeyFile string `json:"key_file" env:"KEY_FILE"`
} }
type CacheConfig struct {
Expiration int64 `json:"expiration" env:"CACHE_EXPIRATION"`
CleanupInterval int64 `json:"cleanup_interval" env:"CLEANUP_INTERVAL"`
}
type LogConfig struct { type LogConfig struct {
Enable bool `json:"enable" env:"log_enable"` Enable bool `json:"enable" env:"log_enable"`
Path string `json:"path" env:"LOG_PATH"` Path string `json:"path" env:"LOG_PATH"`
@ -35,10 +30,10 @@ type Config struct {
Force bool `json:"force"` Force bool `json:"force"`
Address string `json:"address" env:"ADDR"` Address string `json:"address" env:"ADDR"`
Port int `json:"port" env:"PORT"` Port int `json:"port" env:"PORT"`
CaCheExpiration int `json:"cache_expiration" env:"CACHE_EXPIRATION"`
Assets string `json:"assets" env:"ASSETS"` Assets string `json:"assets" env:"ASSETS"`
Database Database `json:"database"` Database Database `json:"database"`
Scheme Scheme `json:"scheme"` Scheme Scheme `json:"scheme"`
Cache CacheConfig `json:"cache"`
TempDir string `json:"temp_dir" env:"TEMP_DIR"` TempDir string `json:"temp_dir" env:"TEMP_DIR"`
Log LogConfig `json:"log"` Log LogConfig `json:"log"`
} }
@ -55,10 +50,7 @@ func DefaultConfig() *Config {
TablePrefix: "x_", TablePrefix: "x_",
DBFile: "data/data.db", DBFile: "data/data.db",
}, },
Cache: CacheConfig{ CaCheExpiration: 30,
Expiration: 60,
CleanupInterval: 120,
},
Log: LogConfig{ Log: LogConfig{
Enable: true, Enable: true,
Path: "log/%Y-%m-%d-%H:%M.log", Path: "log/%Y-%m-%d-%H:%M.log",

View File

@ -44,12 +44,42 @@ func (d *Driver) GetAddition() driver.Additional {
return d.Addition return d.Addition
} }
func (d *Driver) List(ctx context.Context, path string) ([]model.FileInfo, error) { func (d *Driver) List(ctx context.Context, dir model.Object) ([]model.Object, error) {
//TODO implement me //TODO implement me
panic("implement me") panic("implement me")
} }
func (d *Driver) Link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, error) { func (d *Driver) Link(ctx context.Context, file model.Object, args model.LinkArgs) (*model.Link, error) {
//TODO implement me
panic("implement me")
}
func (d *Driver) MakeDir(ctx context.Context, parentDir model.Object, dirName string) error {
//TODO implement me
panic("implement me")
}
func (d *Driver) Move(ctx context.Context, srcObject, dstDir model.Object) error {
//TODO implement me
panic("implement me")
}
func (d *Driver) Rename(ctx context.Context, srcObject model.Object, newName string) error {
//TODO implement me
panic("implement me")
}
func (d *Driver) Copy(ctx context.Context, srcObject, dstDir model.Object) error {
//TODO implement me
panic("implement me")
}
func (d *Driver) Remove(ctx context.Context, object model.Object) error {
//TODO implement me
panic("implement me")
}
func (d *Driver) Put(ctx context.Context, parentDir model.Object, stream model.FileStreamer) error {
//TODO implement me //TODO implement me
panic("implement me") panic("implement me")
} }
@ -59,34 +89,4 @@ func (d Driver) Other(ctx context.Context, data interface{}) (interface{}, error
panic("implement me") panic("implement me")
} }
func (d *Driver) MakeDir(ctx context.Context, path string) error {
//TODO implement me
panic("implement me")
}
func (d *Driver) Move(ctx context.Context, src, dst string) error {
//TODO implement me
panic("implement me")
}
func (d *Driver) Rename(ctx context.Context, src, dst string) error {
//TODO implement me
panic("implement me")
}
func (d *Driver) Copy(ctx context.Context, src, dst string) error {
//TODO implement me
panic("implement me")
}
func (d *Driver) Remove(ctx context.Context, path string) error {
//TODO implement me
panic("implement me")
}
func (d *Driver) Put(ctx context.Context, parentPath string, stream model.FileStreamer) error {
//TODO implement me
panic("implement me")
}
var _ driver.Driver = (*Driver)(nil) var _ driver.Driver = (*Driver)(nil)

View File

@ -29,16 +29,22 @@ type Other interface {
} }
type Reader interface { type Reader interface {
List(ctx context.Context, path string) ([]model.FileInfo, error) List(ctx context.Context, dir model.Object) ([]model.Object, error)
Link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, error) Link(ctx context.Context, file model.Object, args model.LinkArgs) (*model.Link, error)
//Get(ctx context.Context, path string) (FileInfo, error) // maybe not need //Get(ctx context.Context, path string) (FileInfo, error) // maybe not need
} }
type Writer interface { type Writer interface {
MakeDir(ctx context.Context, path string) error // MakeDir make a folder named `dirName` in `parentDir`
Move(ctx context.Context, srcPath, dstPath string) error MakeDir(ctx context.Context, parentDir model.Object, dirName string) error
Rename(ctx context.Context, srcPath, dstName string) error // Move move `srcObject` to `dstDir`
Copy(ctx context.Context, srcPath, dstPath string) error Move(ctx context.Context, srcObject, dstDir model.Object) error
Remove(ctx context.Context, path string) error // Rename rename `srcObject` to `newName`
Put(ctx context.Context, parentPath string, stream model.FileStreamer) error Rename(ctx context.Context, srcObject model.Object, newName string) error
// Copy copy `srcObject` to `dstDir`
Copy(ctx context.Context, srcObject, dstDir model.Object) error
// Remove remove `object`
Remove(ctx context.Context, object model.Object) error
// Put put `stream` to `parentDir`
Put(ctx context.Context, parentDir model.Object, stream model.FileStreamer) error
} }

5
internal/fs/fs.go Normal file
View File

@ -0,0 +1,5 @@
package fs
// the param named path of functions in this package is a virtual path
// So, the purpose of this package is to convert virtual path to actual path
// then pass the actual path to the operations package

View File

@ -15,7 +15,7 @@ import (
// List files // List files
// TODO: hide // TODO: hide
// TODO: sort // TODO: sort
func List(ctx context.Context, path string) ([]model.FileInfo, error) { func List(ctx context.Context, path string) ([]model.Object, error) {
account, actualPath, err := operations.GetAccountAndActualPath(path) account, actualPath, err := operations.GetAccountAndActualPath(path)
virtualFiles := operations.GetAccountVirtualFilesByPath(path) virtualFiles := operations.GetAccountVirtualFilesByPath(path)
if err != nil { if err != nil {
@ -40,7 +40,7 @@ func List(ctx context.Context, path string) ([]model.FileInfo, error) {
return files, nil return files, nil
} }
func Get(ctx context.Context, path string) (model.FileInfo, error) { func Get(ctx context.Context, path string) (model.Object, error) {
path = utils.StandardizationPath(path) path = utils.StandardizationPath(path)
// maybe a virtual file // maybe a virtual file
if path != "/" { if path != "/" {

View File

@ -12,7 +12,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func containsByName(files []model.FileInfo, file model.FileInfo) bool { func containsByName(files []model.Object, file model.Object) bool {
for _, f := range files { for _, f := range files {
if f.GetName() == file.GetName() { if f.GetName() == file.GetName() {
return true return true
@ -23,7 +23,7 @@ func containsByName(files []model.FileInfo, file model.FileInfo) bool {
var httpClient = &http.Client{} var httpClient = &http.Client{}
func getFileStreamFromLink(file model.FileInfo, link *model.Link) (model.FileStreamer, error) { func getFileStreamFromLink(file model.Object, link *model.Link) (model.FileStreamer, error) {
var rc io.ReadCloser var rc io.ReadCloser
mimetype := mime.TypeByExtension(stdpath.Ext(file.GetName())) mimetype := mime.TypeByExtension(stdpath.Ext(file.GetName()))
if link.Data != nil { if link.Data != nil {
@ -57,7 +57,7 @@ func getFileStreamFromLink(file model.FileInfo, link *model.Link) (model.FileStr
mimetype = "application/octet-stream" mimetype = "application/octet-stream"
} }
stream := model.FileStream{ stream := model.FileStream{
FileInfo: file, Object: file,
ReadCloser: rc, ReadCloser: rc,
Mimetype: mimetype, Mimetype: mimetype,
} }

View File

@ -3,6 +3,7 @@ package model
import "time" import "time"
type File struct { type File struct {
ID string
Name string Name string
Size uint64 Size uint64
Modified time.Time Modified time.Time
@ -25,7 +26,6 @@ func (f File) IsDir() bool {
return f.IsFolder return f.IsFolder
} }
type FileWithId struct { func (f File) GetID() string {
Id string return f.ID
File
} }

View File

@ -5,16 +5,17 @@ import (
"time" "time"
) )
type FileInfo interface { type Object interface {
GetSize() uint64 GetSize() uint64
GetName() string GetName() string
ModTime() time.Time ModTime() time.Time
IsDir() bool IsDir() bool
GetID() string
} }
type FileStreamer interface { type FileStreamer interface {
io.ReadCloser io.ReadCloser
FileInfo Object
GetMimetype() string GetMimetype() string
} }

View File

@ -5,7 +5,7 @@ import (
) )
type FileStream struct { type FileStream struct {
FileInfo Object
io.ReadCloser io.ReadCloser
Mimetype string Mimetype string
} }

View File

@ -136,8 +136,8 @@ func getAccountsByPath(path string) []driver.Driver {
// GetAccountVirtualFilesByPath Obtain the virtual file generated by the account according to the path // 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 // for example, there are: /a/b,/a/c,/a/d/e,/a/b.balance1,/av
// GetAccountVirtualFilesByPath(/a) => b,c,d // GetAccountVirtualFilesByPath(/a) => b,c,d
func GetAccountVirtualFilesByPath(prefix string) []model.FileInfo { func GetAccountVirtualFilesByPath(prefix string) []model.Object {
files := make([]model.FileInfo, 0) files := make([]model.Object, 0)
accounts := accountsMap.Values() accounts := accountsMap.Values()
sort.Slice(accounts, func(i, j int) bool { sort.Slice(accounts, func(i, j int) bool {
if accounts[i].GetAccount().Index == accounts[j].GetAccount().Index { if accounts[i].GetAccount().Index == accounts[j].GetAccount().Index {

View File

@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/Xhofe/go-cache" "github.com/Xhofe/go-cache"
"github.com/alist-org/alist/v3/conf"
"github.com/alist-org/alist/v3/internal/driver" "github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/model" "github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/singleflight" "github.com/alist-org/alist/v3/pkg/singleflight"
@ -15,50 +16,57 @@ import (
// In order to facilitate adding some other things before and after file operations // In order to facilitate adding some other things before and after file operations
var filesCache = cache.NewMemCache(cache.WithShards[[]model.FileInfo](64)) var filesCache = cache.NewMemCache(cache.WithShards[[]model.Object](64))
var filesG singleflight.Group[[]model.FileInfo] var filesG singleflight.Group[[]model.Object]
// List files in storage, not contains virtual file // List files in storage, not contains virtual file
func List(ctx context.Context, account driver.Driver, path string) ([]model.FileInfo, error) { func List(ctx context.Context, account driver.Driver, path string) ([]model.Object, error) {
dir, err := Get(ctx, account, path)
if err != nil {
return nil, errors.WithMessage(err, "failed get dir")
}
if account.Config().NoCache { if account.Config().NoCache {
return account.List(ctx, path) return account.List(ctx, dir)
} }
key := stdpath.Join(account.GetAccount().VirtualPath, path) key := stdpath.Join(account.GetAccount().VirtualPath, path)
if files, ok := filesCache.Get(key); ok { if files, ok := filesCache.Get(key); ok {
return files, nil return files, nil
} }
files, err, _ := filesG.Do(key, func() ([]model.FileInfo, error) { files, err, _ := filesG.Do(key, func() ([]model.Object, error) {
files, err := account.List(ctx, path) files, err := account.List(ctx, dir)
if err != nil { if err != nil {
return nil, errors.WithMessage(err, "failed to list files") return nil, errors.WithMessage(err, "failed to list files")
} }
// TODO: get duration from global config or account's config // TODO: maybe can get duration from account's config
filesCache.Set(key, files, cache.WithEx[[]model.FileInfo](time.Minute*30)) filesCache.Set(key, files, cache.WithEx[[]model.Object](time.Minute*time.Duration(conf.Conf.CaCheExpiration)))
return files, nil return files, nil
}) })
return files, err return files, err
} }
func Get(ctx context.Context, account driver.Driver, path string) (model.FileInfo, error) { // Get get object from list of files
if r, ok := account.GetAddition().(driver.RootFolderId); ok && utils.PathEqual(path, "/") { // TODO: maybe should set object ID with path here
return model.FileWithId{ func Get(ctx context.Context, account driver.Driver, path string) (model.Object, error) {
Id: r.GetRootFolderId(), // is root folder
File: model.File{ if r, ok := account.GetAddition().(driver.IRootFolderId); ok && utils.PathEqual(path, "/") {
return model.File{
ID: r.GetRootFolderId(),
Name: "root", Name: "root",
Size: 0, Size: 0,
Modified: account.GetAccount().Modified, Modified: account.GetAccount().Modified,
IsFolder: true, IsFolder: true,
},
}, nil }, nil
} }
if r, ok := account.GetAddition().(driver.IRootFolderPath); ok && utils.PathEqual(path, r.GetRootFolderPath()) { if r, ok := account.GetAddition().(driver.IRootFolderPath); ok && utils.PathEqual(path, r.GetRootFolderPath()) {
return model.File{ return model.File{
ID: r.GetRootFolderPath(),
Name: "root", Name: "root",
Size: 0, Size: 0,
Modified: account.GetAccount().Modified, Modified: account.GetAccount().Modified,
IsFolder: true, IsFolder: true,
}, nil }, nil
} }
// not root folder
dir, name := stdpath.Split(path) dir, name := stdpath.Split(path)
files, err := List(ctx, account, dir) files, err := List(ctx, account, dir)
if err != nil { if err != nil {
@ -82,7 +90,11 @@ func Link(ctx context.Context, account driver.Driver, path string, args model.Li
return link, nil return link, nil
} }
fn := func() (*model.Link, error) { fn := func() (*model.Link, error) {
link, err := account.Link(ctx, path, args) file, err := Get(ctx, account, path)
if err != nil {
return nil, errors.WithMessage(err, "failed to get file")
}
link, err := account.Link(ctx, file, args)
if err != nil { if err != nil {
return nil, errors.WithMessage(err, "failed get link") return nil, errors.WithMessage(err, "failed get link")
} }
@ -98,54 +110,81 @@ func Link(ctx context.Context, account driver.Driver, path string, args model.Li
func MakeDir(ctx context.Context, account driver.Driver, path string) error { func MakeDir(ctx context.Context, account driver.Driver, path string) error {
// check if dir exists // check if dir exists
f, err := Get(ctx, account, path) f, err := Get(ctx, account, path)
if f != nil && f.IsDir() { if err != nil {
return nil if driver.IsErrObjectNotFound(err) {
} parentPath, dirName := stdpath.Split(path)
if err != nil && !driver.IsErrObjectNotFound(err) {
return errors.WithMessage(err, "failed to check if dir exists")
}
parentPath := stdpath.Dir(path)
err = MakeDir(ctx, account, parentPath) err = MakeDir(ctx, account, parentPath)
if err != nil { if err != nil {
return errors.WithMessagef(err, "failed to make parent dir [%s]", parentPath) return errors.WithMessagef(err, "failed to make parent dir [%s]", parentPath)
} }
return account.MakeDir(ctx, path) parentDir, err := Get(ctx, account, parentPath)
// this should not happen
if err != nil {
return errors.WithMessagef(err, "failed to get parent dir [%s]", parentPath)
}
return account.MakeDir(ctx, parentDir, dirName)
} else {
return errors.WithMessage(err, "failed to check if dir exists")
}
} else {
// dir exists
if f.IsDir() {
return nil
} else {
// dir to make is a file
return errors.New("file exists")
}
}
} }
func Move(ctx context.Context, account driver.Driver, srcPath, dstPath string) error { func Move(ctx context.Context, account driver.Driver, srcPath, dstPath string) error {
return account.Move(ctx, srcPath, dstPath) srcObject, err := Get(ctx, account, srcPath)
if err != nil {
return errors.WithMessage(err, "failed to get src object")
}
dstDir, err := Get(ctx, account, stdpath.Dir(dstPath))
return account.Move(ctx, srcObject, dstDir)
} }
func Rename(ctx context.Context, account driver.Driver, srcPath, dstName string) error { func Rename(ctx context.Context, account driver.Driver, srcPath, dstName string) error {
return account.Rename(ctx, srcPath, dstName) srcObject, err := Get(ctx, account, srcPath)
if err != nil {
return errors.WithMessage(err, "failed to get src object")
}
return account.Rename(ctx, srcObject, dstName)
} }
// Copy Just copy file[s] in an account // Copy Just copy file[s] in an account
func Copy(ctx context.Context, account driver.Driver, srcPath, dstPath string) error { func Copy(ctx context.Context, account driver.Driver, srcPath, dstPath string) error {
return account.Copy(ctx, srcPath, dstPath) srcObject, err := Get(ctx, account, srcPath)
if err != nil {
return errors.WithMessage(err, "failed to get src object")
}
dstDir, err := Get(ctx, account, stdpath.Dir(dstPath))
return account.Copy(ctx, srcObject, dstDir)
} }
func Remove(ctx context.Context, account driver.Driver, path string) error { func Remove(ctx context.Context, account driver.Driver, path string) error {
return account.Remove(ctx, path) object, err := Get(ctx, account, path)
if err != nil {
// if object not found, it's ok
if driver.IsErrObjectNotFound(err) {
return nil
}
return errors.WithMessage(err, "failed to get object")
}
return account.Remove(ctx, object)
} }
func Put(ctx context.Context, account driver.Driver, parentPath string, file model.FileStreamer) error { func Put(ctx context.Context, account driver.Driver, parentPath string, file model.FileStreamer) error {
f, err := Get(ctx, account, parentPath) err := MakeDir(ctx, account, parentPath)
if err != nil { if err != nil {
// if parent dir not exists, create it return errors.WithMessagef(err, "failed to make dir [%s]", parentPath)
if driver.IsErrObjectNotFound(err) { }
err = MakeDir(ctx, account, parentPath) parentDir, err := Get(ctx, account, parentPath)
// this should not happen
if err != nil { if err != nil {
return errors.WithMessagef(err, "failed to make parent dir [%s]", parentPath) return errors.WithMessagef(err, "failed to get dir [%s]", parentPath)
} }
} else { return account.Put(ctx, parentDir, file)
return errors.WithMessage(err, "failed to get parent dir")
}
} else {
// object exists, check if it is a dir
if !f.IsDir() {
return errors.Errorf("object [%s] is not a dir", parentPath)
}
}
return account.Put(ctx, parentPath, file)
} }

View File

@ -1,22 +1,24 @@
package operations package operations
import ( import (
stdpath "path"
"strings"
"github.com/alist-org/alist/v3/internal/driver" "github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/pkg/utils" "github.com/alist-org/alist/v3/pkg/utils"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path"
"strings"
) )
func ActualPath(account driver.Additional, rawPath string) string { func ActualPath(account driver.Additional, rawPath string) string {
if i, ok := account.(driver.IRootFolderPath); ok { if i, ok := account.(driver.IRootFolderPath); ok {
rawPath = path.Join(i.GetRootFolderPath(), rawPath) rawPath = stdpath.Join(i.GetRootFolderPath(), rawPath)
} }
return utils.StandardizationPath(rawPath) return utils.StandardizationPath(rawPath)
} }
// GetAccountAndActualPath Get the corresponding account, and remove the virtual path prefix in path // GetAccountAndActualPath Get the corresponding account
// for path: remove the virtual path prefix and join the actual root folder if exists
func GetAccountAndActualPath(rawPath string) (driver.Driver, string, error) { func GetAccountAndActualPath(rawPath string) (driver.Driver, string, error) {
rawPath = utils.StandardizationPath(rawPath) rawPath = utils.StandardizationPath(rawPath)
account := GetBalancedAccount(rawPath) account := GetBalancedAccount(rawPath)