github.com/blend/go-sdk@v1.20220411.3/sourceutil/copy_all.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package sourceutil
     9  
    10  import (
    11  	"io"
    12  	"os"
    13  	"path/filepath"
    14  
    15  	"github.com/blend/go-sdk/stringutil"
    16  )
    17  
    18  // CopyAll copies all files and directories from a source path to a destination path recurrsively.
    19  func CopyAll(destination, source string, opts ...CopyAllOption) error {
    20  	info, err := os.Lstat(source)
    21  	if err != nil {
    22  		return err
    23  	}
    24  	var finalOptions CopyAllOptions
    25  	for _, opt := range opts {
    26  		opt(&finalOptions)
    27  	}
    28  	return copyAll(destination, source, info, finalOptions)
    29  }
    30  
    31  // OptCopyAllSymlinkMode sets the symlink mode.
    32  func OptCopyAllSymlinkMode(mode CopyAllSymlinkMode) CopyAllOption {
    33  	return func(cop *CopyAllOptions) { cop.SymlinkMode = mode }
    34  }
    35  
    36  // OptCopyAllSkipGlobs sets the skip provider to a glob matcher based on a given set of glob(s).
    37  func OptCopyAllSkipGlobs(globs ...string) CopyAllOption {
    38  	return func(cop *CopyAllOptions) {
    39  		cop.SkipProvider = func(fileInfo os.FileInfo) bool {
    40  			for _, glob := range globs {
    41  				if stringutil.Glob(fileInfo.Name(), glob) {
    42  					return true
    43  				}
    44  			}
    45  			return false
    46  		}
    47  	}
    48  }
    49  
    50  // CopyAllOptions are the options for copy all.
    51  type CopyAllOptions struct {
    52  	SymlinkMode  CopyAllSymlinkMode
    53  	SkipProvider func(os.FileInfo) bool
    54  }
    55  
    56  // CopyAllOption is a mutator for copy all options
    57  type CopyAllOption func(*CopyAllOptions)
    58  
    59  // CopyAllSymlinkMode is how symlinks should be handled
    60  type CopyAllSymlinkMode int
    61  
    62  // CopyAllSymlinkMode(s)
    63  var (
    64  	// CopyAllSymlinkModeShallow will copy links from the source to the destination as links.
    65  	CopyAllSymlinkModeShallow CopyAllSymlinkMode = 0
    66  	// CopyAllSymlinkModeDeep will traverse into the link destination and copy any files recursively.
    67  	CopyAllSymlinkModeDeep CopyAllSymlinkMode = 1
    68  	// CopyAllSymlinkModeSkip will skip any links discovered.
    69  	CopyAllSymlinkModeSkip CopyAllSymlinkMode = 2
    70  )
    71  
    72  // copyAll switches proper copy functions regarding file type, etc...
    73  // If there would be anything else here, add a case to this switchboard.
    74  func copyAll(destination, source string, info os.FileInfo, opts CopyAllOptions) error {
    75  	if opts.SkipProvider != nil {
    76  		if opts.SkipProvider(info) {
    77  			return nil
    78  		}
    79  	}
    80  
    81  	switch {
    82  	case info.Mode()&os.ModeSymlink != 0:
    83  		return symCopy(destination, source, info, opts)
    84  	case info.IsDir():
    85  		return dirCopy(destination, source, info, opts)
    86  	default:
    87  		return fileCopy(destination, source, info, opts)
    88  	}
    89  }
    90  
    91  func symCopy(destination, source string, sourceDirInfo os.FileInfo, opts CopyAllOptions) error {
    92  	switch opts.SymlinkMode {
    93  	case CopyAllSymlinkModeShallow:
    94  		return linkCopy(destination, source)
    95  	case CopyAllSymlinkModeDeep:
    96  		orig, err := os.Readlink(source)
    97  		if err != nil {
    98  			return err
    99  		}
   100  		originalSourceDirInfo, err := os.Lstat(orig)
   101  		if err != nil {
   102  			return err
   103  		}
   104  		return copyAll(destination, orig, originalSourceDirInfo, opts)
   105  	case CopyAllSymlinkModeSkip:
   106  		fallthrough
   107  	default:
   108  		return nil // do nothing
   109  	}
   110  }
   111  
   112  // linkCopy copies a symlink.
   113  func linkCopy(destination, source string) error {
   114  	src, err := os.Readlink(source)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	return os.Symlink(src, destination)
   119  }
   120  
   121  // dirCopy copies a directory recursively
   122  func dirCopy(destination, source string, sourceDirInfo os.FileInfo, opts CopyAllOptions) (err error) {
   123  	if _, statErr := os.Stat(destination); statErr != nil {
   124  		// create the destination with the source permissions
   125  		if err = os.MkdirAll(destination, sourceDirInfo.Mode()); err != nil {
   126  			return
   127  		}
   128  	}
   129  
   130  	var sourceDirContents []os.DirEntry
   131  	sourceDirContents, err = os.ReadDir(source)
   132  	if err != nil {
   133  		return
   134  	}
   135  	for _, sourceDirItem := range sourceDirContents {
   136  		destinationName := filepath.Join(destination, sourceDirItem.Name())
   137  		sourceName := filepath.Join(source, sourceDirItem.Name())
   138  		var sourceDirItemInfo os.FileInfo
   139  		sourceDirItemInfo, err = sourceDirItem.Info()
   140  		if err != nil {
   141  			return
   142  		}
   143  
   144  		if _, statErr := os.Stat(destinationName); statErr != nil {
   145  			if err = copyAll(destinationName, sourceName, sourceDirItemInfo, opts); err != nil {
   146  				return
   147  			}
   148  		}
   149  	}
   150  	return
   151  }
   152  
   153  // fileCopy copies a single file
   154  func fileCopy(destination, source string, sourceInfo os.FileInfo, opts CopyAllOptions) (err error) {
   155  	destinationDir := filepath.Dir(destination)
   156  	if _, statErr := os.Stat(destinationDir); statErr != nil {
   157  		if err = os.MkdirAll(destinationDir, sourceInfo.Mode()); err != nil {
   158  			return
   159  		}
   160  	}
   161  
   162  	f, err := os.Create(destination)
   163  	if err != nil {
   164  		return
   165  	}
   166  	defer fclose(f, &err)
   167  
   168  	s, err := os.Open(source)
   169  	if err != nil {
   170  		return
   171  	}
   172  	defer fclose(s, &err)
   173  
   174  	if _, err = io.Copy(f, s); err != nil {
   175  		return
   176  	}
   177  	return
   178  }
   179  
   180  // fclose ANYHOW closes file,
   181  // with asiging error raised during Close,
   182  // BUT respecting the error already reported.
   183  func fclose(f *os.File, reported *error) {
   184  	if err := f.Close(); *reported == nil {
   185  		*reported = err
   186  	}
   187  }