github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/utils/filesys/localfs.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package filesys 16 17 import ( 18 "bytes" 19 "errors" 20 "fmt" 21 "io" 22 "os" 23 "path/filepath" 24 "time" 25 26 "github.com/dolthub/dolt/go/libraries/utils/file" 27 ) 28 29 // LocalFS is the machines local filesystem 30 var LocalFS = &localFS{} 31 32 type localFS struct { 33 cwd string 34 } 35 36 // LocalFilesysWithWorkingDir returns a new Filesys implementation backed by the local filesystem with the supplied 37 // working directory. Path relative operations occur relative to this directory. 38 func LocalFilesysWithWorkingDir(cwd string) (Filesys, error) { 39 absCWD, err := filepath.Abs(cwd) 40 if err != nil { 41 return nil, err 42 } 43 44 // We're going to turn this into a URL, so we need to make sure that windows separators are converted to / 45 absCWD = filepath.ToSlash(absCWD) 46 47 stat, err := os.Stat(absCWD) 48 49 if err != nil { 50 return nil, err 51 } else if !stat.IsDir() { 52 return nil, fmt.Errorf("'%s' is not a valid directory", absCWD) 53 } 54 55 return &localFS{absCWD}, nil 56 } 57 58 // Exists will tell you if a file or directory with a given path already exists, and if it does is it a directory 59 func (fs *localFS) Exists(path string) (exists bool, isDir bool) { 60 var err error 61 path, err = fs.Abs(path) 62 63 if err != nil { 64 return false, false 65 } 66 67 stat, err := os.Stat(path) 68 69 if err != nil { 70 return false, false 71 } 72 73 return true, stat.IsDir() 74 } 75 76 // WithWorkingDir returns a copy of this file system with a new working dir as given. 77 func (fs localFS) WithWorkingDir(path string) (Filesys, error) { 78 abs, err := fs.Abs(path) 79 if err != nil { 80 return nil, err 81 } 82 83 fs.cwd = abs 84 return &fs, nil 85 } 86 87 var errStopMarker = errors.New("stop") 88 89 // Iter iterates over the files and subdirectories within a given directory (Optionally recursively). 90 func (fs *localFS) Iter(path string, recursive bool, cb FSIterCB) error { 91 var err error 92 path, err = fs.Abs(path) 93 94 if err != nil { 95 return err 96 } 97 98 if !recursive { 99 dirEntries, err := os.ReadDir(path) 100 101 if err != nil { 102 return err 103 } 104 105 for _, entry := range dirEntries { 106 fi, err := entry.Info() 107 if err != nil { 108 return err 109 } 110 111 stop := cb(filepath.Join(path, fi.Name()), fi.Size(), fi.IsDir()) 112 113 if stop { 114 return nil 115 } 116 } 117 118 return nil 119 } 120 121 return fs.iter(path, cb) 122 } 123 124 func (fs *localFS) iter(dir string, cb FSIterCB) error { 125 var err error 126 dir, err = fs.Abs(dir) 127 128 if err != nil { 129 return err 130 } 131 132 err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 133 if dir != path { 134 stop := cb(path, info.Size(), info.IsDir()) 135 136 if stop { 137 return errStopMarker 138 } 139 } 140 return nil 141 }) 142 143 if err == errStopMarker { 144 return nil 145 } 146 147 return err 148 } 149 150 // OpenForRead opens a file for reading 151 func (fs *localFS) OpenForRead(fp string) (io.ReadCloser, error) { 152 var err error 153 fp, err = fs.Abs(fp) 154 155 if err != nil { 156 return nil, err 157 } 158 159 if exists, isDir := fs.Exists(fp); !exists { 160 return nil, os.ErrNotExist 161 } else if isDir { 162 return nil, ErrIsDir 163 } 164 165 return os.Open(fp) 166 } 167 168 // ReadFile reads the entire contents of a file 169 func (fs *localFS) ReadFile(fp string) ([]byte, error) { 170 var err error 171 fp, err = fs.Abs(fp) 172 173 if err != nil { 174 return nil, err 175 } 176 177 return os.ReadFile(fp) 178 } 179 180 // OpenForWrite opens a file for writing. The file will be created if it does not exist, and if it does exist 181 // it will be overwritten. 182 func (fs *localFS) OpenForWrite(fp string, perm os.FileMode) (io.WriteCloser, error) { 183 var err error 184 fp, err = fs.Abs(fp) 185 186 if err != nil { 187 return nil, err 188 } 189 190 return os.OpenFile(fp, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm) 191 } 192 193 // OpenForWriteAppend opens a file for writing. The file will be created if it does not exist, and it will 194 // append only to that new file. If file exists, it will append to existing file. 195 func (fs *localFS) OpenForWriteAppend(fp string, perm os.FileMode) (io.WriteCloser, error) { 196 var err error 197 fp, err = fs.Abs(fp) 198 199 if err != nil { 200 return nil, err 201 } 202 203 return os.OpenFile(fp, os.O_CREATE|os.O_APPEND|os.O_WRONLY, perm) 204 } 205 206 // WriteFile writes the entire data buffer to a given file. The file will be created if it does not exist, 207 // and if it does exist it will be overwritten. 208 func (fs *localFS) WriteFile(fp string, data []byte, perms os.FileMode) error { 209 abs, err := fs.Abs(fp) 210 if err != nil { 211 return err 212 } 213 return file.WriteFileAtomically(abs, bytes.NewReader(data), perms) 214 } 215 216 // MkDirs creates a folder and all the parent folders that are necessary to create it. 217 func (fs *localFS) MkDirs(path string) error { 218 var err error 219 path, err = fs.Abs(path) 220 221 if err != nil { 222 return err 223 } 224 225 _, err = os.Stat(path) 226 227 if err != nil { 228 return os.MkdirAll(path, os.ModePerm) 229 } 230 231 return nil 232 } 233 234 // DeleteFile will delete a file at the given path 235 func (fs *localFS) DeleteFile(path string) error { 236 var err error 237 path, err = fs.Abs(path) 238 239 if err != nil { 240 return err 241 } 242 243 if exists, isDir := fs.Exists(path); exists && !isDir { 244 if isDir { 245 return ErrIsDir 246 } 247 248 return file.Remove(path) 249 } 250 251 return os.ErrNotExist 252 } 253 254 // Delete will delete an empty directory, or a file. If trying delete a directory that is not empty you can set force to 255 // true in order to delete the dir and all of it's contents 256 func (fs *localFS) Delete(path string, force bool) error { 257 var err error 258 path, err = fs.Abs(path) 259 260 if err != nil { 261 return err 262 } 263 264 if !force { 265 return file.Remove(path) 266 } else { 267 return file.RemoveAll(path) 268 } 269 } 270 271 // MoveFile will move a file from the srcPath in the filesystem to the destPath 272 func (fs *localFS) MoveFile(srcPath, destPath string) (err error) { 273 srcPath, err = fs.Abs(srcPath) 274 275 if err != nil { 276 return err 277 } 278 279 destPath, err = fs.Abs(destPath) 280 281 if err != nil { 282 return err 283 } 284 285 return file.Rename(srcPath, destPath) 286 } 287 288 func (fs *localFS) MoveDir(srcPath, destPath string) (err error) { 289 srcPath, err = fs.Abs(srcPath) 290 if err != nil { 291 return err 292 } 293 294 destPath, err = fs.Abs(destPath) 295 if err != nil { 296 return err 297 } 298 299 return file.Rename(srcPath, destPath) 300 } 301 302 // converts a path to an absolute path. If it's already an absolute path the input path will be returned unaltered 303 func (fs *localFS) Abs(path string) (string, error) { 304 if filepath.IsAbs(path) { 305 return path, nil 306 } 307 308 if fs.cwd == "" { 309 return filepath.Abs(path) 310 } else { 311 return filepath.Join(fs.cwd, path), nil 312 } 313 } 314 315 // LastModified gets the last modified timestamp for a file or directory at a given path 316 func (fs *localFS) LastModified(path string) (t time.Time, exists bool) { 317 var err error 318 path, err = fs.Abs(path) 319 320 if err != nil { 321 return time.Time{}, false 322 } 323 324 stat, err := os.Stat(path) 325 326 if err != nil { 327 return time.Time{}, false 328 } 329 330 return stat.ModTime(), true 331 } 332 333 func (fs *localFS) TempDir() string { 334 return os.TempDir() 335 }