From 52575f6ad6c3a58cc53adfe28387920dee703a81 Mon Sep 17 00:00:00 2001 From: Noah Hsu Date: Thu, 16 Jun 2022 21:59:49 +0800 Subject: [PATCH] feat: add meta model and test --- internal/model/meta.go | 11 ++++ internal/operations/account_test.go | 6 +- internal/store/account.go | 18 ++++-- internal/store/error.go | 7 +++ internal/store/meta.go | 90 +++++++++++++++++++++++++++++ internal/store/meta_test.go | 58 +++++++++++++++++++ internal/store/store.go | 2 +- 7 files changed, 184 insertions(+), 8 deletions(-) create mode 100644 internal/model/meta.go create mode 100644 internal/store/error.go create mode 100644 internal/store/meta.go create mode 100644 internal/store/meta_test.go diff --git a/internal/model/meta.go b/internal/model/meta.go new file mode 100644 index 00000000000..6bfb8d83e5d --- /dev/null +++ b/internal/model/meta.go @@ -0,0 +1,11 @@ +package model + +type Meta struct { + ID uint `json:"id" gorm:"primaryKey"` + Path string `json:"path" gorm:"unique" binding:"required"` + Password string `json:"password"` + Hide string `json:"hide"` + Upload bool `json:"upload"` + OnlyShows string `json:"only_shows"` + Readme string `json:"readme"` +} diff --git a/internal/operations/account_test.go b/internal/operations/account_test.go index 7d646f55362..4f151ffe59c 100644 --- a/internal/operations/account_test.go +++ b/internal/operations/account_test.go @@ -42,7 +42,7 @@ func TestCreateAccount(t *testing.T) { } func TestGetAccountVirtualFilesByPath(t *testing.T) { - Setup(t) + setupAccounts(t) virtualFiles := operations.GetAccountVirtualFilesByPath("/a") var names []string for _, virtualFile := range virtualFiles { @@ -57,7 +57,7 @@ func TestGetAccountVirtualFilesByPath(t *testing.T) { } func TestGetBalancedAccount(t *testing.T) { - Setup(t) + setupAccounts(t) account := operations.GetBalancedAccount("/a/d/e") if account.GetAccount().VirtualPath != "/a/d/e" { t.Errorf("expected: /a/d/e, got: %+v", account.GetAccount().VirtualPath) @@ -68,7 +68,7 @@ func TestGetBalancedAccount(t *testing.T) { } } -func Setup(t *testing.T) { +func setupAccounts(t *testing.T) { var accounts = []model.Account{ {Driver: "Local", VirtualPath: "/a/b", Index: 0, Addition: `{"root_folder":"."}`}, {Driver: "Local", VirtualPath: "/a/c", Index: 1, Addition: `{"root_folder":"."}`}, diff --git a/internal/store/account.go b/internal/store/account.go index 46c88cca3c6..949df39f4ab 100644 --- a/internal/store/account.go +++ b/internal/store/account.go @@ -5,6 +5,11 @@ import ( "github.com/pkg/errors" ) +// why don't need `cache` for account? +// because all account store in `operations.accountsMap` +// the most of the read operation is from `operations.accountsMap` +// just for persistence in database + // CreateAccount just insert account to database func CreateAccount(account *model.Account) error { return errors.WithStack(db.Create(account).Error) @@ -21,12 +26,17 @@ func DeleteAccountById(id uint) error { } // GetAccounts Get all accounts from database order by index -func GetAccounts() ([]model.Account, error) { +func GetAccounts(pageIndex, pageSize int) ([]model.Account, int64, error) { + accountDB := db.Model(&model.Account{}) + var count int64 + if err := accountDB.Count(&count).Error; err != nil { + return nil, 0, errors.Wrapf(err, "failed get accounts count") + } var accounts []model.Account - if err := db.Order(columnName("index")).Find(&accounts).Error; err != nil { - return nil, errors.WithStack(err) + if err := accountDB.Order(columnName("index")).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&accounts).Error; err != nil { + return nil, 0, errors.WithStack(err) } - return accounts, nil + return accounts, count, nil } // GetAccountById Get Account by id, used to update account usually diff --git a/internal/store/error.go b/internal/store/error.go new file mode 100644 index 00000000000..24bff4d52f5 --- /dev/null +++ b/internal/store/error.go @@ -0,0 +1,7 @@ +package store + +import "errors" + +var ( + ErrMetaNotFound = errors.New("meta not found") +) diff --git a/internal/store/meta.go b/internal/store/meta.go new file mode 100644 index 00000000000..2922d15b377 --- /dev/null +++ b/internal/store/meta.go @@ -0,0 +1,90 @@ +package store + +import ( + "github.com/Xhofe/go-cache" + "github.com/alist-org/alist/v3/internal/model" + "github.com/alist-org/alist/v3/pkg/singleflight" + "github.com/alist-org/alist/v3/pkg/utils" + "github.com/pkg/errors" + "gorm.io/gorm" + stdpath "path" +) + +var metaCache = cache.NewMemCache(cache.WithShards[*model.Meta](2)) + +// metaG maybe not needed +var metaG singleflight.Group[*model.Meta] + +func GetNearestMeta(path string) (*model.Meta, error) { + path = utils.StandardizationPath(path) + meta, err := GetMetaByPath(path) + if err == nil { + return meta, nil + } + if errors.Cause(err) != gorm.ErrRecordNotFound { + return nil, err + } + if path == "/" { + return nil, errors.WithStack(ErrMetaNotFound) + } + return GetNearestMeta(stdpath.Dir(path)) +} + +func GetMetaByPath(path string) (*model.Meta, error) { + meta, ok := metaCache.Get(path) + if ok { + return meta, nil + } + meta, err, _ := metaG.Do(path, func() (*model.Meta, error) { + meta := model.Meta{Path: path} + if err := db.Where(meta).First(&meta).Error; err != nil { + return nil, errors.Wrapf(err, "failed select meta") + } + metaCache.Set(path, &meta) + return &meta, nil + }) + return meta, err +} + +func GetMetaById(id uint) (*model.Meta, error) { + var u model.Meta + if err := db.First(&u, id).Error; err != nil { + return nil, errors.Wrapf(err, "failed get old meta") + } + return &u, nil +} + +func CreateMeta(u *model.Meta) error { + return errors.WithStack(db.Create(u).Error) +} + +func UpdateMeta(u *model.Meta) error { + old, err := GetMetaById(u.ID) + if err != nil { + return err + } + metaCache.Del(old.Path) + return errors.WithStack(db.Save(u).Error) +} + +func GetMetas(pageIndex, pageSize int) ([]model.Meta, int64, error) { + metaDB := db.Model(&model.Meta{}) + var count int64 + if err := metaDB.Count(&count).Error; err != nil { + return nil, 0, errors.Wrapf(err, "failed get metas count") + } + var metas []model.Meta + if err := metaDB.Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&metas).Error; err != nil { + return nil, 0, errors.Wrapf(err, "failed get find metas") + } + return metas, count, nil +} + +func DeleteMetaById(id uint) error { + old, err := GetMetaById(id) + if err != nil { + return err + } + metaCache.Del(old.Path) + return errors.WithStack(db.Delete(&model.Meta{}, id).Error) +} diff --git a/internal/store/meta_test.go b/internal/store/meta_test.go new file mode 100644 index 00000000000..af7feb16896 --- /dev/null +++ b/internal/store/meta_test.go @@ -0,0 +1,58 @@ +package store + +import ( + "github.com/alist-org/alist/v3/internal/model" + "github.com/pkg/errors" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "testing" +) + +func init() { + db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{}) + if err != nil { + panic("failed to connect database") + } + Init(db) +} + +func TestCreateMeta(t *testing.T) { + metas := []model.Meta{ + {Path: "/"}, + {Path: "/a"}, + {Path: "/a/b"}, + {Path: "/a/b/c"}, + } + for _, meta := range metas { + err := CreateMeta(&meta) + if err != nil { + t.Errorf("failed to create meta: %+v", err) + } + } +} + +func TestUpdateMeta(t *testing.T) { + meta := model.Meta{ID: 1, Path: "/b"} + err := UpdateMeta(&meta) + if err != nil { + t.Errorf("failed to update meta: %+v", err) + } +} + +func TestGetNearestMeta1(t *testing.T) { + meta, err := GetNearestMeta("/b/c/d") + if err != nil { + t.Errorf("failed to get nearest meta: %+v", err) + } + if meta.Path != "/b" { + t.Errorf("unexpected meta: %+v", meta) + } +} + +func TestGetNearestMeta2(t *testing.T) { + meta, err := GetNearestMeta("/c/d/e") + if errors.Cause(err) != ErrMetaNotFound { + t.Errorf("unexpected error: %+v", err) + t.Errorf("unexpected meta: %+v", meta) + } +} diff --git a/internal/store/store.go b/internal/store/store.go index ea4f117f79a..3f92042ea2c 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -10,7 +10,7 @@ var db gorm.DB func Init(d *gorm.DB) { db = *d - err := db.AutoMigrate(&model.Account{}) + err := db.AutoMigrate(new(model.Account), new(model.User), new(model.Meta)) if err != nil { log.Fatalf("failed migrate database: %s", err.Error()) }