Fix: uint may overflow / Test: get user storage

pull/247/head
HFO4 5 years ago
parent 9386371097
commit f4c414c0f6

@ -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'

@ -138,8 +138,7 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
{Name: "sendfile", Value: `0`, Type: "download"}, {Name: "sendfile", Value: `0`, Type: "download"},
{Name: "defaultTheme", Value: `#3f51b5`, Type: "basic"}, {Name: "defaultTheme", Value: `#3f51b5`, Type: "basic"},
{Name: "header", Value: `X-Sendfile`, Type: "download"}, {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"}}}} {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"},
`, Type: "basic"},
{Name: "refererCheck", Value: `true`, Type: "share"}, {Name: "refererCheck", Value: `true`, Type: "share"},
{Name: "header", Value: `X-Sendfile`, Type: "download"}, {Name: "header", Value: `X-Sendfile`, Type: "download"},
{Name: "aria2_tmppath", Value: `/path/to/public/download`, Type: "aria2"}, {Name: "aria2_tmppath", Value: `/path/to/public/download`, Type: "aria2"},

@ -1,6 +1,7 @@
package model package model
import ( import (
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
@ -8,10 +9,12 @@ import (
func TestMigration(t *testing.T) { func TestMigration(t *testing.T) {
asserts := assert.New(t) asserts := assert.New(t)
conf.DatabaseConfig.Type = "sqlite3"
DB, _ = gorm.Open("sqlite3", ":memory:") DB, _ = gorm.Open("sqlite3", ":memory:")
asserts.NotPanics(func() { asserts.NotPanics(func() {
migration() migration()
}) })
conf.DatabaseConfig.Type = "mysql"
DB = mockDB DB = mockDB
} }

@ -65,7 +65,7 @@ func (user *User) DeductionStorage(size uint64) bool {
DB.Model(user).UpdateColumn("storage", gorm.Expr("storage - ?", size)) DB.Model(user).UpdateColumn("storage", gorm.Expr("storage - ?", size))
return true return true
} }
// 如果要减少的容量超出用容量,则设为零 // 如果要减少的容量超出用容量,则设为零
user.Storage = 0 user.Storage = 0
DB.Model(user).UpdateColumn("storage", 0) DB.Model(user).UpdateColumn("storage", 0)

@ -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 { func (fs *FileSystem) deleteGroupedFile(ctx context.Context, files map[uint][]*model.File) map[uint][]string {
// 失败的文件列表 // 失败的文件列表
// TODO 并行删除
failed := make(map[uint][]string, len(files)) failed := make(map[uint][]string, len(files))
for policyID, toBeDeletedFiles := range files { for policyID, toBeDeletedFiles := range files {

@ -7,6 +7,7 @@ import (
"github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util" "github.com/HFO4/cloudreve/pkg/util"
"path" "path"
"sync"
) )
/* ================= /* =================
@ -209,12 +210,25 @@ func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor fu
return []Object{}, nil 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)) objects := make([]Object, 0, len(childFiles)+len(childFolders))
// 所有对象的父目录 // 所有对象的父目录
var processedPath string var processedPath string

@ -299,17 +299,17 @@ func TestFileSystem_Delete(t *testing.T) {
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "type"}).AddRow(1, "local")) mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "type"}).AddRow(1, "local"))
// 删除文件记录 // 删除文件记录
mock.ExpectBegin() mock.ExpectBegin()
mock.ExpectExec("DELETE(.+)"). mock.ExpectExec("DELETE(.+)files").
WillReturnResult(sqlmock.NewResult(0, 3)) WillReturnResult(sqlmock.NewResult(0, 3))
mock.ExpectCommit() mock.ExpectCommit()
// 归还容量 // 归还容量
mock.ExpectBegin() mock.ExpectBegin()
mock.ExpectExec("UPDATE(.+)"). mock.ExpectExec("UPDATE(.+)users").
WillReturnResult(sqlmock.NewResult(0, 3)) WillReturnResult(sqlmock.NewResult(0, 3))
mock.ExpectCommit() mock.ExpectCommit()
// 删除目录 // 删除目录
mock.ExpectBegin() mock.ExpectBegin()
mock.ExpectExec("DELETE(.+)"). mock.ExpectExec("DELETE(.+)folders").
WillReturnResult(sqlmock.NewResult(0, 3)) WillReturnResult(sqlmock.NewResult(0, 3))
mock.ExpectCommit() mock.ExpectCommit()

@ -22,11 +22,11 @@ type User struct {
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
PreferredTheme string `json:"preferred_theme"` PreferredTheme string `json:"preferred_theme"`
Policy Policy `json:"policy"` Policy policy `json:"policy"`
Group Group `json:"group"` Group group `json:"group"`
} }
type Policy struct { type policy struct {
SaveType string `json:"saveType"` SaveType string `json:"saveType"`
MaxSize string `json:"maxSize"` MaxSize string `json:"maxSize"`
AllowedType []string `json:"allowedType"` AllowedType []string `json:"allowedType"`
@ -34,12 +34,18 @@ type Policy struct {
AllowGetSource bool `json:"allowSource"` AllowGetSource bool `json:"allowSource"`
} }
type Group struct { type group struct {
AllowShare bool `json:"allowShare"` AllowShare bool `json:"allowShare"`
AllowRemoteDownload bool `json:"allowRemoteDownload"` AllowRemoteDownload bool `json:"allowRemoteDownload"`
AllowTorrentDownload bool `json:"allowTorrentDownload"` AllowTorrentDownload bool `json:"allowTorrentDownload"`
} }
type storage struct {
Used uint64 `json:"used"`
Free uint64 `json:"free"`
Total uint64 `json:"total"`
}
// BuildUser 序列化用户 // BuildUser 序列化用户
func BuildUser(user model.User) User { func BuildUser(user model.User) User {
aria2Option := user.Group.GetAria2Option() aria2Option := user.Group.GetAria2Option()
@ -51,14 +57,14 @@ func BuildUser(user model.User) User {
Avatar: user.Avatar, Avatar: user.Avatar,
CreatedAt: user.CreatedAt.Unix(), CreatedAt: user.CreatedAt.Unix(),
PreferredTheme: user.OptionsSerialized.PreferredTheme, PreferredTheme: user.OptionsSerialized.PreferredTheme,
Policy: Policy{ Policy: policy{
SaveType: user.Policy.Type, 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, AllowedType: user.Policy.OptionsSerialized.FileType,
UploadURL: user.Policy.Server, UploadURL: user.Policy.Server,
AllowGetSource: user.Policy.IsOriginLinkEnable, AllowGetSource: user.Policy.IsOriginLinkEnable,
}, },
Group: Group{ Group: group{
AllowShare: user.Group.ShareEnabled, AllowShare: user.Group.ShareEnabled,
AllowRemoteDownload: aria2Option[0], AllowRemoteDownload: aria2Option[0],
AllowTorrentDownload: aria2Option[2], AllowTorrentDownload: aria2Option[2],
@ -72,3 +78,20 @@ func BuildUserResponse(user model.User) Response {
Data: BuildUser(user), 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,
}
}

@ -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)
}
}

@ -20,8 +20,14 @@ func UserLogin(c *gin.Context) {
// UserMe 获取当前登录的用户 // UserMe 获取当前登录的用户
func UserMe(c *gin.Context) { func UserMe(c *gin.Context) {
user := CurrentUser(c) currUser := CurrentUser(c)
res := serializer.BuildUserResponse(*user) res := serializer.BuildUserResponse(*currUser)
c.JSON(200, res) c.JSON(200, res)
}
// UserStorage 获取用户的存储信息
func UserStorage(c *gin.Context) {
currUser := CurrentUser(c)
res := serializer.BuildUserStorageResponse(*currUser)
c.JSON(200, res)
} }

@ -57,6 +57,7 @@ func InitRouter() *gin.Engine {
{ {
// 当前登录用户信息 // 当前登录用户信息
user.GET("me", controllers.UserMe) user.GET("me", controllers.UserMe)
user.GET("storage", controllers.UserStorage)
} }
// 文件 // 文件

Loading…
Cancel
Save