package fsutil import ( "archive/zip" "fmt" "io" "io/fs" "os" "path/filepath" "strings" "github.com/gookit/goutil/x/basefn" ) // Mkdir alias of os.MkdirAll() func Mkdir(dirPath string, perm fs.FileMode) error { return os.MkdirAll(dirPath, perm) } // MkdirQuick with default permission 0755. func MkdirQuick(dirPath string) error { return EnsureDir(dirPath) } // EnsureDir creates a directory if it doesn't exist func EnsureDir(path string) error { if !DirExist(path) { return os.MkdirAll(path, 0755) } return nil } // MkDirs batch makes multi dirs at once func MkDirs(perm fs.FileMode, dirPaths ...string) error { for _, dirPath := range dirPaths { if err := os.MkdirAll(dirPath, perm); err != nil { return err } } return nil } // MkSubDirs batch makes multi sub-dirs at once func MkSubDirs(perm fs.FileMode, parentDir string, subDirs ...string) error { for _, dirName := range subDirs { dirPath := parentDir + "/" + dirName if err := os.MkdirAll(dirPath, perm); err != nil { return err } } return nil } // MkParentDir quickly create parent dir for a given path. func MkParentDir(fpath string) error { dirPath := filepath.Dir(fpath) if !IsDir(dirPath) { return os.MkdirAll(dirPath, 0775) } return nil } // ************************************************************ // options for open file // ************************************************************ // OpenOption for open file type OpenOption struct { // file open flag. see FsCWTFlags Flag int // file perm. see DefaultFilePerm Perm os.FileMode } // OpenOptionFunc for open/write file type OpenOptionFunc func(*OpenOption) // NewOpenOption create a new OpenOption instance // // Defaults: // - open flags: FsCWTFlags (override write) // - file Perm: DefaultFilePerm func NewOpenOption(optFns ...OpenOptionFunc) *OpenOption { opt := &OpenOption{ Flag: FsCWTFlags, Perm: DefaultFilePerm, } for _, fn := range optFns { fn(opt) } return opt } // OpenOptOrNew create a new OpenOption instance if opt is nil func OpenOptOrNew(opt *OpenOption) *OpenOption { if opt == nil { return NewOpenOption() } return opt } // WithFlag set file open flag func WithFlag(flag int) OpenOptionFunc { return func(opt *OpenOption) { opt.Flag = flag } } // WithPerm set file perm func WithPerm(perm os.FileMode) OpenOptionFunc { return func(opt *OpenOption) { opt.Perm = perm } } // ************************************************************ // open/create files // ************************************************************ // some commonly flag consts for open file. const ( FsCWAFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND // create, append write-only FsCWTFlags = os.O_CREATE | os.O_WRONLY | os.O_TRUNC // create, override write-only FsCWFlags = os.O_CREATE | os.O_WRONLY // create, write-only FsRWFlags = os.O_RDWR // read-write, dont create. FsRFlags = os.O_RDONLY // read-only ) // OpenFile like os.OpenFile, but will auto create dir. // // Usage: // // file, err := OpenFile("path/to/file.txt", FsCWFlags, 0666) func OpenFile(filePath string, flag int, perm os.FileMode) (*os.File, error) { fileDir := filepath.Dir(filePath) if err := os.MkdirAll(fileDir, DefaultDirPerm); err != nil { return nil, err } file, err := os.OpenFile(filePath, flag, perm) if err != nil { return nil, err } return file, nil } // MustOpenFile like os.OpenFile, but will auto create dir. // // Usage: // // file := MustOpenFile("path/to/file.txt", FsCWFlags, 0666) func MustOpenFile(filePath string, flag int, perm os.FileMode) *os.File { file, err := OpenFile(filePath, flag, perm) if err != nil { panic(err) } return file } // QuickOpenFile like os.OpenFile, open for append write. if not exists, will create it. // // Alias of OpenAppendFile() func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error) { flag := basefn.FirstOr(fileFlag, FsCWAFlags) return OpenFile(filepath, flag, DefaultFilePerm) } // OpenAppendFile like os.OpenFile, open for append write. if not exists, will create it. func OpenAppendFile(filepath string, filePerm ...os.FileMode) (*os.File, error) { perm := basefn.FirstOr(filePerm, DefaultFilePerm) return OpenFile(filepath, FsCWAFlags, perm) } // OpenTruncFile like os.OpenFile, open for override write. if not exists, will create it. func OpenTruncFile(filepath string, filePerm ...os.FileMode) (*os.File, error) { perm := basefn.FirstOr(filePerm, DefaultFilePerm) return OpenFile(filepath, FsCWTFlags, perm) } // OpenReadFile like os.OpenFile, open file for read contents func OpenReadFile(filepath string) (*os.File, error) { return os.OpenFile(filepath, FsRFlags, OnlyReadFilePerm) } // CreateFile create file if not exists // // Usage: // // CreateFile("path/to/file.txt", 0664, 0666) func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*os.File, error) { dirPath := filepath.Dir(fpath) if !IsDir(dirPath) { err := os.MkdirAll(dirPath, dirPerm) if err != nil { return nil, err } } flag := basefn.FirstOr(fileFlag, FsCWAFlags) return os.OpenFile(fpath, flag, filePerm) } // MustCreateFile create file, will panic on error func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File { file, err := CreateFile(filePath, filePerm, dirPerm) if err != nil { panic(err) } return file } // ************************************************************ // remove files // ************************************************************ // alias methods var ( // MustRm removes the named file or (empty) directory. MustRm = MustRemove // QuietRm removes the named file or (empty) directory. QuietRm = QuietRemove ) // Remove removes the named file or (empty) directory. func Remove(fPath string) error { return os.Remove(fPath) } // MustRemove removes the named file or (empty) directory. // NOTICE: will panic on error func MustRemove(fPath string) { if err := os.Remove(fPath); err != nil { panic(err) } } // QuietRemove removes the named file or (empty) directory. // // NOTICE: will ignore error func QuietRemove(fPath string) { _ = os.Remove(fPath) } // SafeRemoveAll removes path and any children it contains. will ignore error func SafeRemoveAll(path string) { _ = os.RemoveAll(path) } // RmIfExist removes the named file or (empty) directory on existing. func RmIfExist(fPath string) error { return DeleteIfExist(fPath) } // DeleteIfExist removes the named file or (empty) directory on existing. func DeleteIfExist(fPath string) error { if PathExists(fPath) { return os.Remove(fPath) } return nil } // RmFileIfExist removes the named file on existing. func RmFileIfExist(fPath string) error { return DeleteIfFileExist(fPath) } // DeleteIfFileExist removes the named file on existing. func DeleteIfFileExist(fPath string) error { if IsFile(fPath) { return os.Remove(fPath) } return nil } // RemoveSub removes all sub files and dirs of dirPath, but not remove dirPath. func RemoveSub(dirPath string, fns ...FilterFunc) error { return FindInDir(dirPath, func(fPath string, ent fs.DirEntry) error { if ent.IsDir() { if err := RemoveSub(fPath, fns...); err != nil { return err } // skip rm not empty subdir if !IsEmptyDir(fPath) { return nil } } return os.Remove(fPath) }, fns...) } // ************************************************************ // other operates // ************************************************************ // Unzip a zip archive // from https://blog.csdn.net/wangshubo1989/article/details/71743374 func Unzip(archive, targetDir string) (err error) { reader, err := zip.OpenReader(archive) if err != nil { return err } if err = os.MkdirAll(targetDir, DefaultDirPerm); err != nil { return } for _, file := range reader.File { if strings.Contains(file.Name, "..") { return fmt.Errorf("illegal file path in zip: %v", file.Name) } fullPath := filepath.Join(targetDir, file.Name) if file.FileInfo().IsDir() { err = os.MkdirAll(fullPath, file.Mode()) if err != nil { return err } continue } fileReader, err := file.Open() if err != nil { return err } targetFile, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) if err != nil { _ = fileReader.Close() return err } _, err = io.Copy(targetFile, fileReader) // close all _ = fileReader.Close() targetFile.Close() if err != nil { return err } } return }