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  }