github.com/sandwich-go/boost@v1.3.29/xos/dir.go (about) 1 package xos 2 3 import ( 4 "fmt" 5 "github.com/sandwich-go/boost" 6 "github.com/sandwich-go/boost/xerror" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "sort" 11 12 "github.com/sandwich-go/boost/xslice" 13 ) 14 15 // MkdirAll 创建 path 的目录 16 func MkdirAll(path string) error { 17 return Mkdir(filepath.Dir(path)) 18 } 19 20 // Mkdir 创建目录 21 func Mkdir(dir string) error { 22 if ExistsFile(dir) { 23 return nil 24 } 25 return os.MkdirAll(dir, 0775) 26 } 27 28 // IsEmpty 检测目录是否为空,如目录下存在隐藏文件也会认为是非空目录 29 func IsEmpty(path string) bool { 30 stat, err := os.Stat(path) 31 if err != nil { 32 return true 33 } 34 if stat.IsDir() { 35 file, err0 := os.Open(path) 36 if err0 != nil { 37 return true 38 } 39 defer func() { 40 _ = file.Close() 41 }() 42 names, err1 := file.Readdirnames(-1) 43 if err1 != nil { 44 return true 45 } 46 return len(names) == 0 47 } else { 48 return stat.Size() == 0 49 } 50 } 51 52 // RemoveSubDirsUnderDir 删除指定目录下的子目录 53 func RemoveSubDirsUnderDir(dir string, filter func(dir string) bool) error { 54 if !ExistsDir(dir) { 55 return nil 56 } 57 fs, err := ioutil.ReadDir(dir) 58 if err != nil { 59 return err 60 } 61 fileList := make([]string, 0) 62 for _, f := range fs { 63 if f.IsDir() { 64 fd := filepath.Join(dir, f.Name()) 65 if filter == nil || filter(fd) { 66 fileList = append(fileList, fd) 67 } 68 } 69 } 70 for _, filePath := range fileList { 71 subFileList := make([]string, 0) 72 _ = filepath.Walk(filePath, FileWalkFunc(&subFileList)) 73 // 排序,是为了先删除文件,再删除空目录 74 sort.Sort(sort.Reverse(sort.StringSlice(subFileList))) 75 for _, subFilePath := range subFileList { 76 _ = os.Remove(subFilePath) 77 } 78 } 79 return nil 80 } 81 82 func removeEmptyDir(path string) error { 83 files, err := ioutil.ReadDir(path) 84 if err != nil { 85 return err 86 } 87 if len(files) != 0 { 88 return fmt.Errorf("not empty dir, %s", path) 89 } 90 return os.Remove(path) 91 } 92 93 // RemoveEmptyDirs 删除空目录 94 // 若目录不为空,则报错 95 func RemoveEmptyDirs(root string) error { 96 return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 97 if err != nil { 98 return err 99 } 100 if info.IsDir() { 101 err = removeEmptyDir(path) 102 } 103 return err 104 }) 105 } 106 107 // RemoveDirs 删除指定目录下所有内容 108 func RemoveDirs(dir string) error { 109 d, err := os.Open(dir) 110 if err != nil { 111 return err 112 } 113 defer func() { 114 _ = d.Close() 115 }() 116 names, err0 := d.Readdirnames(-1) 117 if err0 != nil { 118 return err0 119 } 120 for _, name := range names { 121 err = os.RemoveAll(filepath.Join(dir, name)) 122 if err != nil { 123 return err 124 } 125 } 126 return nil 127 } 128 129 // RemoveFilesUnderDir 删除目录下的文件 130 func RemoveFilesUnderDir(pathStr string, filter func(filePath string) bool) { 131 fileList := make([]string, 0) 132 _ = filepath.Walk(pathStr, FileWalkFuncWithIncludeFilter(&fileList, filter)) 133 for _, filePath := range fileList { 134 _ = os.Remove(filePath) 135 } 136 } 137 138 // FileWalkFunc 只有指定的ext为合法文件 139 func FileWalkFunc(files *[]string, ext ...string) filepath.WalkFunc { 140 return func(path string, info os.FileInfo, err error) error { 141 if len(ext) > 0 { 142 if xslice.StringsContain(ext, filepath.Ext(path)) { 143 *files = append(*files, path) 144 } 145 } else { 146 *files = append(*files, path) 147 } 148 return err 149 } 150 } 151 152 // FileWalkFuncWithIncludeFilter filepath.WalkFunc 通过include过滤合法的文件,如指定了ext则只有ext扩展类型的文件合法 153 func FileWalkFuncWithIncludeFilter(files *[]string, include func(f string) bool, ext ...string) filepath.WalkFunc { 154 return func(path string, info os.FileInfo, err error) error { 155 if err != nil { 156 return err 157 } 158 if info.IsDir() { 159 return err 160 } 161 if include == nil || include(path) { 162 if len(ext) > 0 { 163 if xslice.StringsContain(ext, filepath.Ext(path)) { 164 *files = append(*files, path) 165 } 166 } else { 167 *files = append(*files, path) 168 } 169 } 170 return err 171 } 172 } 173 174 // FileWalkFuncWithExcludeFilter filepath.WalkFunc 通过excluded过滤不合法文件,如指定了ext则只有ext扩展类型的文件合法 175 func FileWalkFuncWithExcludeFilter(files *[]string, excluded func(f string) bool, ext ...string) filepath.WalkFunc { 176 return func(path string, info os.FileInfo, err error) error { 177 if excluded != nil && excluded(path) { 178 return err 179 } 180 if len(ext) > 0 { 181 if xslice.StringsContain(ext, filepath.Ext(path)) { 182 *files = append(*files, path) 183 } 184 } else { 185 *files = append(*files, path) 186 } 187 return err 188 } 189 } 190 191 // FilePathWalkFollowLink 遍历目录 192 func FilePathWalkFollowLink(root string, walkFn filepath.WalkFunc) error { 193 dir, err := GetActuallyDir(root) 194 if err != nil { 195 return err 196 } 197 return filepath.Walk(dir, walkFn) 198 } 199 200 // GetActuallyDir 获取真实目录,如果root为一个link则寻找link的真实目录 201 func GetActuallyDir(root string) (string, error) { 202 dirInfo, err := os.Lstat(root) 203 if err != nil { 204 return root, err 205 } 206 if dirInfo.Mode()&os.ModeSymlink != 0 { 207 dirLinkTo, err0 := os.Readlink(root) 208 if err0 != nil { 209 return root, err0 210 } 211 return dirLinkTo, nil 212 } 213 return root, nil 214 } 215 216 // ReadDir returns the filenames in the given directory in sorted order. 217 func ReadDir(root string) ([]string, error) { 218 return ReadDirWithExt(root, "") 219 } 220 221 // ReadDirWithExt returns the filenames in the given directory in sorted order. 222 func ReadDirWithExt(root, ext string) ([]string, error) { 223 dir, err := os.Open(root) 224 if err != nil { 225 return nil, err 226 } 227 defer func() { 228 boost.LogErrorAndEatError(dir.Close()) 229 }() 230 names, err0 := dir.Readdirnames(-1) 231 if err0 != nil { 232 return nil, err0 233 } 234 sort.Strings(names) 235 236 if len(ext) > 0 { 237 tss := make([]string, 0, len(names)) 238 for _, v := range names { 239 if filepath.Ext(v) == ext { 240 tss = append(tss, v) 241 } 242 } 243 names = tss 244 } 245 return names, nil 246 } 247 248 // IsDirWriteable checks if dir is writable by writing and removing a file 249 // to dir. It returns nil if dir is writable. 250 func IsDirWriteable(dir string) error { 251 f := filepath.Join(dir, ".touch") 252 if err := ioutil.WriteFile(f, []byte(""), 0600); err != nil { 253 return err 254 } 255 return os.Remove(f) 256 } 257 258 // TouchDirAll is similar to os.MkdirAll. It creates directories with 0700 permission if any directory 259 // does not exists. TouchDirAll also ensures the given directory is writable. 260 func TouchDirAll(dir string) error { 261 // If path is already a directory, MkdirAll does nothing 262 // and returns nil. 263 err := os.MkdirAll(dir, 0700) 264 if err != nil { 265 // if mkdirAll("a/text") and "text" is not 266 // a directory, this will return syscall.ENOTDIR 267 return err 268 } 269 return IsDirWriteable(dir) 270 } 271 272 // CreateDirAll is similar to TouchDirAll but returns error 273 // if the deepest directory was not empty. 274 func CreateDirAll(dir string) error { 275 err := TouchDirAll(dir) 276 if err == nil { 277 var ns []string 278 ns, err = ReadDir(dir) 279 if err != nil { 280 return err 281 } 282 if len(ns) != 0 { 283 err = xerror.NewText("expected %q to be empty, got %q", dir, ns) 284 } 285 } 286 return err 287 }