From f4c414c0f62fd040ac0aeab36b528b541149109b Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Sat, 7 Dec 2019 15:05:48 +0800 Subject: [PATCH] Fix: uint may overflow / Test: get user storage --- azure-pipelines.yml | 43 -------------------------- models/migration.go | 3 +- models/migration_test.go | 3 ++ models/user.go | 2 +- pkg/filesystem/file.go | 1 + pkg/filesystem/path.go | 18 +++++++++-- pkg/filesystem/path_test.go | 6 ++-- pkg/serializer/user.go | 37 +++++++++++++++++----- pkg/serializer/user_test.go | 61 +++++++++++++++++++++++++++++++++++++ routers/controllers/user.go | 10 ++++-- routers/router.go | 1 + 11 files changed, 125 insertions(+), 60 deletions(-) delete mode 100644 azure-pipelines.yml create mode 100644 pkg/serializer/user_test.go diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 8899c25..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,43 +0,0 @@ -# Go -# Build your Go project. -# Add steps that test, save build artifacts, deploy, and more: -# https://docs.microsoft.com/azure/devops/pipelines/languages/go - -trigger: -- master - -pool: - vmImage: 'ubuntu-latest' - -variables: - GOBIN: '$(GOPATH)/bin' # Go binaries path - GOROOT: '/usr/local/go1.13' # Go installation path - GOPATH: '$(system.defaultWorkingDirectory)/gopath' # Go workspace path - modulePath: '$(GOPATH)/src/github.com/$(build.repository.name)' # Path to the module's code - -steps: -- script: | - mkdir -p '$(GOBIN)' - mkdir -p '$(GOPATH)/pkg' - mkdir -p '$(modulePath)' - shopt -s extglob - shopt -s dotglob - mv !(gopath) '$(modulePath)' - echo '##vso[task.prependpath]$(GOBIN)' - echo '##vso[task.prependpath]$(GOROOT)/bin' - displayName: 'Set up the Go workspace' - -- script: | - go version - go get -v -t -d ./... - if [ -f Gopkg.toml ]; then - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh - dep ensure - fi - go build -v . - workingDirectory: '$(modulePath)' - displayName: 'Get dependencies, then build' - -- script: go test -v ./... - workingDirectory: '$(modulePath)' - displayName: 'Run tests' \ No newline at end of file diff --git a/models/migration.go b/models/migration.go index a065fa0..3c78a62 100644 --- a/models/migration.go +++ b/models/migration.go @@ -138,8 +138,7 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti {Name: "sendfile", Value: `0`, Type: "download"}, {Name: "defaultTheme", Value: `#3f51b5`, Type: "basic"}, {Name: "header", Value: `X-Sendfile`, Type: "download"}, - {Name: "themes", Value: `{"#3f51b5":{"palette":{"common":{"black":"#000","white":"#fff"},"background":{"paper":"#fff","default":"#fafafa"},"primary":{"light":"#7986cb","main":"#3f51b5","dark":"#303f9f","contrastText":"#fff"},"secondary":{"light":"#ff4081","main":"#f50057","dark":"#c51162","contrastText":"#fff"},"error":{"light":"#e57373","main":"#f44336","dark":"#d32f2f","contrastText":"#fff"},"text":{"primary":"rgba(0, 0, 0, 0.87)","secondary":"rgba(0, 0, 0, 0.54)","disabled":"rgba(0, 0, 0, 0.38)","hint":"rgba(0, 0, 0, 0.38)"},"explorer":{"filename":"#474849","icon":"#8f8f8f","bgSelected":"#D5DAF0","emptyIcon":"#e8e8e8"}}}} -`, Type: "basic"}, + {Name: "themes", Value: `{"#3f51b5":{"palette":{"primary":{"light":"#7986cb","main":"#3f51b5","dark":"#303f9f","contrastText":"#fff"},"secondary":{"light":"#ff4081","main":"#f50057","dark":"#c51162","contrastText":"#fff"},"error":{"light":"#e57373","main":"#f44336","dark":"#d32f2f","contrastText":"#fff"},"explorer":{"filename":"#474849","icon":"#8f8f8f","bgSelected":"#D5DAF0","emptyIcon":"#e8e8e8"}}}}`, Type: "basic"}, {Name: "refererCheck", Value: `true`, Type: "share"}, {Name: "header", Value: `X-Sendfile`, Type: "download"}, {Name: "aria2_tmppath", Value: `/path/to/public/download`, Type: "aria2"}, diff --git a/models/migration_test.go b/models/migration_test.go index f2bf414..5c1cf8c 100644 --- a/models/migration_test.go +++ b/models/migration_test.go @@ -1,6 +1,7 @@ package model import ( + "github.com/HFO4/cloudreve/pkg/conf" "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" "testing" @@ -8,10 +9,12 @@ import ( func TestMigration(t *testing.T) { asserts := assert.New(t) + conf.DatabaseConfig.Type = "sqlite3" DB, _ = gorm.Open("sqlite3", ":memory:") asserts.NotPanics(func() { migration() }) + conf.DatabaseConfig.Type = "mysql" DB = mockDB } diff --git a/models/user.go b/models/user.go index 2db0403..67e3440 100644 --- a/models/user.go +++ b/models/user.go @@ -65,7 +65,7 @@ func (user *User) DeductionStorage(size uint64) bool { DB.Model(user).UpdateColumn("storage", gorm.Expr("storage - ?", size)) return true } - // 如果要减少的容量超出以用容量,则设为零 + // 如果要减少的容量超出已用容量,则设为零 user.Storage = 0 DB.Model(user).UpdateColumn("storage", 0) diff --git a/pkg/filesystem/file.go b/pkg/filesystem/file.go index b34a136..5b63a41 100644 --- a/pkg/filesystem/file.go +++ b/pkg/filesystem/file.go @@ -107,6 +107,7 @@ func (fs *FileSystem) GetContent(ctx context.Context, path string) (io.ReadSeeke // 返回每个分组失败的文件列表 func (fs *FileSystem) deleteGroupedFile(ctx context.Context, files map[uint][]*model.File) map[uint][]string { // 失败的文件列表 + // TODO 并行删除 failed := make(map[uint][]string, len(files)) for policyID, toBeDeletedFiles := range files { diff --git a/pkg/filesystem/path.go b/pkg/filesystem/path.go index 661375c..d972807 100644 --- a/pkg/filesystem/path.go +++ b/pkg/filesystem/path.go @@ -7,6 +7,7 @@ import ( "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/util" "path" + "sync" ) /* ================= @@ -209,12 +210,25 @@ func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor fu return []Object{}, nil } + var wg sync.WaitGroup + var childFolders []model.Folder + var childFiles []model.File + wg.Add(2) + // 获取子目录 - childFolders, _ := folder.GetChildFolder() + go func() { + childFolders, _ = folder.GetChildFolder() + wg.Done() + }() + // 获取子文件 - childFiles, _ := folder.GetChildFile() + go func() { + childFiles, _ = folder.GetChildFile() + wg.Done() + }() // 汇总处理结果 + wg.Wait() objects := make([]Object, 0, len(childFiles)+len(childFolders)) // 所有对象的父目录 var processedPath string diff --git a/pkg/filesystem/path_test.go b/pkg/filesystem/path_test.go index 77571ed..9da0bf9 100644 --- a/pkg/filesystem/path_test.go +++ b/pkg/filesystem/path_test.go @@ -299,17 +299,17 @@ func TestFileSystem_Delete(t *testing.T) { mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "type"}).AddRow(1, "local")) // 删除文件记录 mock.ExpectBegin() - mock.ExpectExec("DELETE(.+)"). + mock.ExpectExec("DELETE(.+)files"). WillReturnResult(sqlmock.NewResult(0, 3)) mock.ExpectCommit() // 归还容量 mock.ExpectBegin() - mock.ExpectExec("UPDATE(.+)"). + mock.ExpectExec("UPDATE(.+)users"). WillReturnResult(sqlmock.NewResult(0, 3)) mock.ExpectCommit() // 删除目录 mock.ExpectBegin() - mock.ExpectExec("DELETE(.+)"). + mock.ExpectExec("DELETE(.+)folders"). WillReturnResult(sqlmock.NewResult(0, 3)) mock.ExpectCommit() diff --git a/pkg/serializer/user.go b/pkg/serializer/user.go index b61dfa3..a5cd139 100644 --- a/pkg/serializer/user.go +++ b/pkg/serializer/user.go @@ -22,11 +22,11 @@ type User struct { Avatar string `json:"avatar"` CreatedAt int64 `json:"created_at"` PreferredTheme string `json:"preferred_theme"` - Policy Policy `json:"policy"` - Group Group `json:"group"` + Policy policy `json:"policy"` + Group group `json:"group"` } -type Policy struct { +type policy struct { SaveType string `json:"saveType"` MaxSize string `json:"maxSize"` AllowedType []string `json:"allowedType"` @@ -34,12 +34,18 @@ type Policy struct { AllowGetSource bool `json:"allowSource"` } -type Group struct { +type group struct { AllowShare bool `json:"allowShare"` AllowRemoteDownload bool `json:"allowRemoteDownload"` AllowTorrentDownload bool `json:"allowTorrentDownload"` } +type storage struct { + Used uint64 `json:"used"` + Free uint64 `json:"free"` + Total uint64 `json:"total"` +} + // BuildUser 序列化用户 func BuildUser(user model.User) User { aria2Option := user.Group.GetAria2Option() @@ -51,14 +57,14 @@ func BuildUser(user model.User) User { Avatar: user.Avatar, CreatedAt: user.CreatedAt.Unix(), PreferredTheme: user.OptionsSerialized.PreferredTheme, - Policy: Policy{ + Policy: policy{ SaveType: user.Policy.Type, - MaxSize: fmt.Sprintf("%.2fmb", float64(user.Policy.MaxSize)/1024*1024), + MaxSize: fmt.Sprintf("%.2fmb", float64(user.Policy.MaxSize)/(1024*1024)), AllowedType: user.Policy.OptionsSerialized.FileType, UploadURL: user.Policy.Server, AllowGetSource: user.Policy.IsOriginLinkEnable, }, - Group: Group{ + Group: group{ AllowShare: user.Group.ShareEnabled, AllowRemoteDownload: aria2Option[0], AllowTorrentDownload: aria2Option[2], @@ -72,3 +78,20 @@ func BuildUserResponse(user model.User) Response { Data: BuildUser(user), } } + +// BuildUserStorageResponse 序列化用户存储概况响应 +func BuildUserStorageResponse(user model.User) Response { + storageResp := storage{ + Used: user.Storage, + Free: user.Group.MaxStorage - user.Storage, + Total: user.Group.MaxStorage, + } + + if user.Group.MaxStorage < user.Storage { + storageResp.Free = 0 + } + + return Response{ + Data: storageResp, + } +} diff --git a/pkg/serializer/user_test.go b/pkg/serializer/user_test.go new file mode 100644 index 0000000..fa3df73 --- /dev/null +++ b/pkg/serializer/user_test.go @@ -0,0 +1,61 @@ +package serializer + +import ( + model "github.com/HFO4/cloudreve/models" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestBuildUser(t *testing.T) { + asserts := assert.New(t) + user := model.User{ + Policy: model.Policy{MaxSize: 1024 * 1024}, + } + res := BuildUser(user) + asserts.Equal("1.00mb", res.Policy.MaxSize) + +} + +func TestBuildUserResponse(t *testing.T) { + asserts := assert.New(t) + user := model.User{ + Policy: model.Policy{MaxSize: 1024 * 1024}, + } + res := BuildUserResponse(user) + asserts.Equal("1.00mb", res.Data.(User).Policy.MaxSize) +} + +func TestBuildUserStorageResponse(t *testing.T) { + asserts := assert.New(t) + + { + user := model.User{ + Storage: 0, + Group: model.Group{MaxStorage: 10}, + } + res := BuildUserStorageResponse(user) + asserts.Equal(uint64(0), res.Data.(storage).Used) + asserts.Equal(uint64(10), res.Data.(storage).Total) + asserts.Equal(uint64(10), res.Data.(storage).Free) + } + { + user := model.User{ + Storage: 6, + Group: model.Group{MaxStorage: 10}, + } + res := BuildUserStorageResponse(user) + asserts.Equal(uint64(6), res.Data.(storage).Used) + asserts.Equal(uint64(10), res.Data.(storage).Total) + asserts.Equal(uint64(4), res.Data.(storage).Free) + } + { + user := model.User{ + Storage: 20, + Group: model.Group{MaxStorage: 10}, + } + res := BuildUserStorageResponse(user) + asserts.Equal(uint64(20), res.Data.(storage).Used) + asserts.Equal(uint64(10), res.Data.(storage).Total) + asserts.Equal(uint64(0), res.Data.(storage).Free) + } +} diff --git a/routers/controllers/user.go b/routers/controllers/user.go index f58ed2b..4463cdc 100644 --- a/routers/controllers/user.go +++ b/routers/controllers/user.go @@ -20,8 +20,14 @@ func UserLogin(c *gin.Context) { // UserMe 获取当前登录的用户 func UserMe(c *gin.Context) { - user := CurrentUser(c) - res := serializer.BuildUserResponse(*user) + currUser := CurrentUser(c) + res := serializer.BuildUserResponse(*currUser) c.JSON(200, res) +} +// UserStorage 获取用户的存储信息 +func UserStorage(c *gin.Context) { + currUser := CurrentUser(c) + res := serializer.BuildUserStorageResponse(*currUser) + c.JSON(200, res) } diff --git a/routers/router.go b/routers/router.go index 5484064..f49ec7d 100644 --- a/routers/router.go +++ b/routers/router.go @@ -57,6 +57,7 @@ func InitRouter() *gin.Engine { { // 当前登录用户信息 user.GET("me", controllers.UserMe) + user.GET("storage", controllers.UserStorage) } // 文件