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  }