tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/engine/fs/util.go (about)

     1  package fs
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"strconv"
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  // todo: Remove
    14  // todo: MkDir
    15  
    16  func OpenFile(fsys FS, name string, flag int, perm os.FileMode) (File, error) {
    17  	fsopenfile, ok := fsys.(interface {
    18  		OpenFile(name string, flag int, perm os.FileMode) (File, error)
    19  	})
    20  	if !ok {
    21  		return nil, fmt.Errorf("unable to openfile on fs")
    22  	}
    23  	return fsopenfile.OpenFile(name, flag, perm)
    24  }
    25  
    26  func MkdirAll(fsys FS, path string, perm FileMode) error {
    27  	fsmkdir, ok := fsys.(interface {
    28  		MkdirAll(path string, perm FileMode) error
    29  	})
    30  	if !ok {
    31  		return fmt.Errorf("unable to mkdirall on fs")
    32  	}
    33  	return fsmkdir.MkdirAll(path, perm)
    34  }
    35  
    36  func DirExists(fsys FS, path string) (bool, error) {
    37  	fi, err := Stat(fsys, path)
    38  	if err == nil && fi.IsDir() {
    39  		return true, nil
    40  	}
    41  	if os.IsNotExist(err) {
    42  		return false, nil
    43  	}
    44  	return false, err
    45  }
    46  
    47  func IsDir(fsys FS, path string) (bool, error) {
    48  	fi, err := Stat(fsys, path)
    49  	if err != nil {
    50  		return false, err
    51  	}
    52  	return fi.IsDir(), nil
    53  }
    54  
    55  func IsEmpty(fsys FS, path string) (bool, error) {
    56  	if b, _ := Exists(fsys, path); !b {
    57  		return false, fmt.Errorf("path does not exist: %q", path)
    58  	}
    59  	fi, err := Stat(fsys, path)
    60  	if err != nil {
    61  		return false, err
    62  	}
    63  	if fi.IsDir() {
    64  		f, err := fsys.Open(path)
    65  		if err != nil {
    66  			return false, err
    67  		}
    68  		defer f.Close()
    69  		list, err := ReadDir(fsys, path)
    70  		return len(list) == 0, nil
    71  	}
    72  	return fi.Size() == 0, nil
    73  }
    74  
    75  func Exists(fsys FS, path string) (bool, error) {
    76  	_, err := Stat(fsys, path)
    77  	if err == nil {
    78  		return true, nil
    79  	}
    80  	if os.IsNotExist(err) {
    81  		return false, nil
    82  	}
    83  	return false, err
    84  }
    85  
    86  func WriteFile(fsys FS, filename string, data []byte, perm FileMode) error {
    87  	of, ok := fsys.(interface {
    88  		OpenFile(name string, flag int, perm FileMode) (File, error)
    89  	})
    90  	if !ok {
    91  		return ErrPermission
    92  	}
    93  	f, err := of.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	fw, ok := f.(io.WriteCloser)
    98  	if !ok {
    99  		f.Close()
   100  		return ErrPermission
   101  	}
   102  	n, err := fw.Write(data)
   103  	if err == nil && n < len(data) {
   104  		err = io.ErrShortWrite
   105  	}
   106  	if err1 := fw.Close(); err == nil {
   107  		err = err1
   108  	}
   109  	return err
   110  }
   111  
   112  // Random number state.
   113  // We generate random temporary file names so that there's a good
   114  // chance the file doesn't exist yet - keeps the number of tries in
   115  // TempFile to a minimum.
   116  var rand uint32
   117  var randmu sync.Mutex
   118  
   119  func reseed() uint32 {
   120  	return uint32(time.Now().UnixNano() + int64(os.Getpid()))
   121  }
   122  
   123  func nextRandom() string {
   124  	randmu.Lock()
   125  	r := rand
   126  	if r == 0 {
   127  		r = reseed()
   128  	}
   129  	r = r*1664525 + 1013904223 // constants from Numerical Recipes
   130  	rand = r
   131  	randmu.Unlock()
   132  	return strconv.Itoa(int(1e9 + r%1e9))[1:]
   133  }
   134  
   135  func TempDir(fsys FS, dir, prefix string) (name string, err error) {
   136  	if dir == "" {
   137  		dir = os.TempDir()
   138  	}
   139  
   140  	nconflict := 0
   141  	for i := 0; i < 10000; i++ {
   142  		try := filepath.Join(dir, prefix+nextRandom())
   143  		fmkd, ok := fsys.(interface {
   144  			Mkdir(name string, perm FileMode) error
   145  		})
   146  		if !ok {
   147  			return name, ErrPermission
   148  		}
   149  		err = fmkd.Mkdir(try, 0700)
   150  		if os.IsExist(err) {
   151  			if nconflict++; nconflict > 10 {
   152  				randmu.Lock()
   153  				rand = reseed()
   154  				randmu.Unlock()
   155  			}
   156  			continue
   157  		}
   158  		if err == nil {
   159  			name = try
   160  		}
   161  		break
   162  	}
   163  	return
   164  }