mirror of https://github.com/cloudreve/Cloudreve
Fix: uint may overflow / Test: get user storage
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserStorage 获取用户的存储信息
|
||||||
|
func UserStorage(c *gin.Context) {
|
||||||
|
currUser := CurrentUser(c)
|
||||||
|
res := serializer.BuildUserStorageResponse(*currUser)
|
||||||
c.JSON(200, res)
|
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…
Reference in New Issue