diff --git a/zfile/file.go b/zfile/file.go index 5128c61..c767023 100644 --- a/zfile/file.go +++ b/zfile/file.go @@ -11,7 +11,9 @@ import ( "net/http" "os" "path/filepath" + "sort" "strings" + "time" ) var ProjectPath = "./" @@ -263,3 +265,89 @@ func HasPermission(path string, perm os.FileMode, noUp ...bool) bool { func HasReadWritePermission(path string) bool { return HasPermission(path, fs.FileMode(0o600)) } + +type fileInfo struct { + path string + size uint64 + modTime time.Time +} + +type fileInfos []fileInfo + +func (f fileInfos) Len() int { return len(f) } +func (f fileInfos) Less(i, j int) bool { return f[i].modTime.Before(f[j].modTime) } +func (f fileInfos) Swap(i, j int) { f[i], f[j] = f[j], f[i] } + +type DirStatOptions struct { + MaxSize uint64 // max size + MaxTotal uint64 // max total files +} + +// StatDir get directory size and total files +func StatDir(path string, options ...DirStatOptions) (size, total uint64, err error) { + var ( + totalSize uint64 + totalFiles uint64 + files = make([]fileInfo, 0, 1024) + needCollect = len(options) > 0 && (options[0].MaxSize > 0 || options[0].MaxTotal > 0) + ) + + err = filepath.WalkDir(path, func(filePath string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.Type()&os.ModeSymlink != 0 { + return nil + } + + if !d.IsDir() { + info, err := d.Info() + if err != nil { + return err + } + + fileSize := uint64(info.Size()) + totalSize += fileSize + totalFiles++ + + if needCollect { + files = append(files, fileInfo{ + path: filePath, + size: fileSize, + modTime: info.ModTime(), + }) + } + } + return nil + }) + + if err != nil { + return totalSize, totalFiles, err + } + + if needCollect { + needCleanup := totalSize > options[0].MaxSize + if options[0].MaxTotal > 0 && totalFiles > options[0].MaxTotal { + needCleanup = true + } + + if needCleanup { + sort.Sort(fileInfos(files)) + for i := 0; i < len(files); i++ { + if (options[0].MaxSize == 0 || totalSize <= options[0].MaxSize) && + (options[0].MaxTotal == 0 || totalFiles <= options[0].MaxTotal) { + break + } + + if err := os.Remove(files[i].path); err != nil { + return totalSize, totalFiles, fmt.Errorf("failed to delete file %s: %v", files[i].path, err) + } + totalSize -= files[i].size + totalFiles-- + } + } + } + + return totalSize, totalFiles, nil +} diff --git a/zfile/file_test.go b/zfile/file_test.go index beec1ef..d1fbdc8 100644 --- a/zfile/file_test.go +++ b/zfile/file_test.go @@ -1,6 +1,7 @@ package zfile import ( + "fmt" "os" "path/filepath" "strings" @@ -130,10 +131,7 @@ func TestPermissionDenied(t *testing.T) { dir := "./permission_denied" os.Mkdir(dir, 0o000) - defer func() { - os.Chmod(dir, 777) - Rmdir(dir) - }() + defer Rmdir(dir) tt.Run("NotPermission", func(tt *TestUtil) { tt.EqualTrue(!HasReadWritePermission(dir)) @@ -156,3 +154,26 @@ func TestPermissionDenied(t *testing.T) { tt.Log(HasPermission("/", 0o664)) }) } + +func TestGetDirSize(t *testing.T) { + tt := NewTest(t) + + tmp := RealPathMkdir("./tmp-size") + defer Rmdir(tmp) + + for i := 0; i < 20; i++ { + WriteFile(filepath.Join(tmp, fmt.Sprintf("file-%d.txt", i)), []byte(strings.Repeat("a", (i+1)*8*KB))) + } + + size, total, err := StatDir(tmp) + tt.NoError(err) + tt.EqualTrue(size > 1*MB) + tt.EqualTrue(total == 20) + tt.Log(SizeFormat(size), size) + + size, total, err = StatDir(tmp, DirStatOptions{MaxSize: 1 * MB, MaxTotal: 3}) + tt.NoError(err) + tt.EqualTrue(size < 1*MB) + tt.EqualTrue(total == 3) + tt.Log(SizeFormat(size), size) +} diff --git a/znet/valid.go b/znet/valid.go index be3dc5e..1e257c7 100644 --- a/znet/valid.go +++ b/znet/valid.go @@ -43,6 +43,6 @@ func (c *Context) Valid(defRule zvalid.Engine, key string, name ...string) zvali return valid(defRule, value, key, name...) } -func valid(defRule zvalid.Engine, value, key string, name ...string) (valid zvalid.Engine) { +func valid(defRule zvalid.Engine, value, _ string, name ...string) (valid zvalid.Engine) { return defRule.Verifi(value, name...) } diff --git a/znet/web_test.go b/znet/web_test.go index 4419897..8c9fb43 100644 --- a/znet/web_test.go +++ b/znet/web_test.go @@ -872,7 +872,7 @@ func TestMethodAndName(t *testing.T) { r := New("TestMethodAndName") r.SetMode(DebugMode) g := r.Group("/TestMethodAndName") - h := func(s string) func(c *Context) { + h := func(_ string) func(c *Context) { return func(c *Context) { c.String(200, c.GetParam("id")) }