github.com/databricks/cli@v0.203.0/libs/filer/local_client.go (about)

     1  package filer
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"io/fs"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"golang.org/x/exp/slices"
    11  )
    12  
    13  // LocalClient implements the [Filer] interface for the local filesystem.
    14  type LocalClient struct {
    15  	// File operations will be relative to this path.
    16  	root localRootPath
    17  }
    18  
    19  func NewLocalClient(root string) (Filer, error) {
    20  	return &LocalClient{
    21  		root: NewLocalRootPath(root),
    22  	}, nil
    23  }
    24  
    25  func (w *LocalClient) Write(ctx context.Context, name string, reader io.Reader, mode ...WriteMode) error {
    26  	absPath, err := w.root.Join(name)
    27  	if err != nil {
    28  		return err
    29  	}
    30  
    31  	flags := os.O_WRONLY | os.O_CREATE
    32  	if slices.Contains(mode, OverwriteIfExists) {
    33  		flags |= os.O_TRUNC
    34  	} else {
    35  		flags |= os.O_EXCL
    36  	}
    37  
    38  	absPath = filepath.FromSlash(absPath)
    39  	f, err := os.OpenFile(absPath, flags, 0644)
    40  	if os.IsNotExist(err) && slices.Contains(mode, CreateParentDirectories) {
    41  		// Create parent directories if they don't exist.
    42  		err = os.MkdirAll(filepath.Dir(absPath), 0755)
    43  		if err != nil {
    44  			return err
    45  		}
    46  		// Try again.
    47  		f, err = os.OpenFile(absPath, flags, 0644)
    48  	}
    49  
    50  	if err != nil {
    51  		switch {
    52  		case os.IsNotExist(err):
    53  			return NoSuchDirectoryError{path: absPath}
    54  		case os.IsExist(err):
    55  			return FileAlreadyExistsError{path: absPath}
    56  		default:
    57  			return err
    58  		}
    59  	}
    60  
    61  	_, err = io.Copy(f, reader)
    62  	cerr := f.Close()
    63  	if err == nil {
    64  		err = cerr
    65  	}
    66  
    67  	return err
    68  
    69  }
    70  
    71  func (w *LocalClient) Read(ctx context.Context, name string) (io.ReadCloser, error) {
    72  	absPath, err := w.root.Join(name)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	// This stat call serves two purposes:
    78  	// 1. Checks file at path exists, and throws an error if it does not
    79  	// 2. Allows us to error out if the path is a directory
    80  	absPath = filepath.FromSlash(absPath)
    81  	stat, err := os.Stat(absPath)
    82  	if err != nil {
    83  		if os.IsNotExist(err) {
    84  			return nil, FileDoesNotExistError{path: absPath}
    85  		}
    86  		return nil, err
    87  	}
    88  
    89  	if stat.IsDir() {
    90  		return nil, NotAFile{path: absPath}
    91  	}
    92  
    93  	return os.Open(absPath)
    94  }
    95  
    96  func (w *LocalClient) Delete(ctx context.Context, name string, mode ...DeleteMode) error {
    97  	absPath, err := w.root.Join(name)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	// Illegal to delete the root path.
   103  	if absPath == w.root.rootPath {
   104  		return CannotDeleteRootError{}
   105  	}
   106  
   107  	absPath = filepath.FromSlash(absPath)
   108  	err = os.Remove(absPath)
   109  
   110  	// Return early on success.
   111  	if err == nil {
   112  		return nil
   113  	}
   114  
   115  	if os.IsNotExist(err) {
   116  		return FileDoesNotExistError{path: absPath}
   117  	}
   118  
   119  	if os.IsExist(err) {
   120  		if slices.Contains(mode, DeleteRecursively) {
   121  			return os.RemoveAll(absPath)
   122  		}
   123  		return DirectoryNotEmptyError{path: absPath}
   124  	}
   125  
   126  	return err
   127  }
   128  
   129  func (w *LocalClient) ReadDir(ctx context.Context, name string) ([]fs.DirEntry, error) {
   130  	absPath, err := w.root.Join(name)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	absPath = filepath.FromSlash(absPath)
   136  	stat, err := os.Stat(absPath)
   137  	if err != nil {
   138  		if os.IsNotExist(err) {
   139  			return nil, NoSuchDirectoryError{path: absPath}
   140  		}
   141  		return nil, err
   142  	}
   143  
   144  	if !stat.IsDir() {
   145  		return nil, NotADirectory{path: absPath}
   146  	}
   147  
   148  	return os.ReadDir(absPath)
   149  }
   150  
   151  func (w *LocalClient) Mkdir(ctx context.Context, name string) error {
   152  	dirPath, err := w.root.Join(name)
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	dirPath = filepath.FromSlash(dirPath)
   158  	return os.MkdirAll(dirPath, 0755)
   159  }
   160  
   161  func (w *LocalClient) Stat(ctx context.Context, name string) (fs.FileInfo, error) {
   162  	absPath, err := w.root.Join(name)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	absPath = filepath.FromSlash(absPath)
   168  	stat, err := os.Stat(absPath)
   169  	if os.IsNotExist(err) {
   170  		return nil, FileDoesNotExistError{path: absPath}
   171  	}
   172  	return stat, err
   173  }