github.com/searKing/golang/go@v1.2.117/os/clean.go (about) 1 // Copyright 2022 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package os 6 7 import ( 8 "errors" 9 "os" 10 "sort" 11 "time" 12 13 filepath_ "github.com/searKing/golang/go/path/filepath" 14 ) 15 16 type DiskQuota struct { 17 MaxAge time.Duration // max age of files 18 MaxCount int // max count of files 19 MaxUsedProportion float32 // max used proportion of files 20 MaxIUsedProportion float32 // max used proportion of inodes 21 } 22 23 func (q DiskQuota) NoLimit() bool { 24 return q.MaxAge <= 0 && q.MaxCount <= 0 && q.MaxUsedProportion <= 0 && q.MaxIUsedProportion <= 0 25 } 26 27 func (q DiskQuota) ExceedBytes(avail, total int64) bool { 28 return q.MaxUsedProportion > 0 && float32(total-avail) > q.MaxUsedProportion*float32(total) 29 } 30 31 func (q DiskQuota) ExceedInodes(inodes, inodesFree int64) bool { 32 return q.MaxIUsedProportion > 0 && float32(inodes-inodesFree) > q.MaxIUsedProportion*float32(inodes) 33 } 34 35 // UnlinkOldestFiles unlink old files if need 36 func UnlinkOldestFiles(pattern string, quora DiskQuota) error { 37 return UnlinkOldestFilesFunc(pattern, quora, func(name string) bool { return true }) 38 } 39 40 // UnlinkOldestFilesFunc unlink old files satisfying f(c) if need 41 func UnlinkOldestFilesFunc(pattern string, quora DiskQuota, f func(name string) bool) error { 42 if quora.NoLimit() { 43 return nil 44 } 45 46 now := time.Now() 47 48 // find old files 49 var filesNotExpired []string 50 filesExpired, err := filepath_.GlobFunc(pattern, func(name string) bool { 51 fi, err := os.Stat(name) 52 if err != nil { 53 return false 54 } 55 56 fl, err := os.Lstat(name) 57 if err != nil { 58 return false 59 } 60 if quora.MaxAge <= 0 { 61 filesNotExpired = append(filesNotExpired, name) 62 return false 63 } 64 65 if now.Sub(fi.ModTime()) < quora.MaxAge { 66 filesNotExpired = append(filesNotExpired, name) 67 return false 68 } 69 70 if fl.Mode()&os.ModeSymlink == os.ModeSymlink { 71 return false 72 } 73 return true 74 }) 75 if err != nil { 76 return err 77 } 78 79 var filesExceedMaxCount []string 80 var filesLeft = filesNotExpired 81 if quora.MaxCount > 0 && len(filesNotExpired) > 0 { 82 removeCount := len(filesNotExpired) - quora.MaxCount 83 if removeCount < 0 { 84 removeCount = 0 85 } 86 sort.Sort(rotateFileSlice(filesNotExpired)) 87 filesExceedMaxCount = filesNotExpired[:removeCount] 88 filesLeft = filesNotExpired[removeCount:] 89 } 90 if f == nil { 91 f = func(name string) bool { 92 return true 93 } 94 } 95 96 var errs []error 97 for _, path := range filesExpired { 98 if f(path) { 99 err = os.Remove(path) 100 if err != nil { 101 errs = append(errs, err) 102 } 103 } 104 } 105 for _, path := range filesExceedMaxCount { 106 if f(path) { 107 err = os.Remove(path) 108 if err != nil { 109 errs = append(errs, err) 110 } 111 } 112 } 113 114 var needGC = func(name string) bool { 115 total, _, avail, inodes, inodesFree, err := DiskUsage(name) 116 if err != nil { 117 return false 118 } 119 if total <= 0 { 120 return false 121 } 122 if quora.ExceedBytes(avail, total) { 123 return true 124 } 125 if quora.ExceedInodes(inodes-inodesFree, inodes) { 126 return true 127 } 128 return false 129 } 130 131 for _, path := range filesLeft { 132 if !needGC(path) { 133 return nil 134 } 135 136 if f(path) { 137 err = os.Remove(path) 138 if err != nil { 139 errs = append(errs, err) 140 } 141 } 142 } 143 return errors.Join(errs...) 144 }