github.com/dnephin/dobi@v0.15.0/utils/fs/directory.go (about)

     1  package fs
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"time"
     8  
     9  	"github.com/docker/docker/pkg/fileutils"
    10  )
    11  
    12  // LastModifiedSearch provides the means by which to specify your search parameters when
    13  // finding the last modified file.
    14  type LastModifiedSearch struct {
    15  	// Root must be set to the absolute path of the directory to traverse. Any
    16  	// relative paths in Paths and Excludes will be considered relative to this
    17  	// root directory.
    18  	Root     string
    19  	Excludes []string
    20  	Paths    []string
    21  }
    22  
    23  // LastModified returns the latest modified time for all the files and
    24  // directories. The files in each directory are checked for their last modified
    25  // time.
    26  // TODO: use go routines to speed this up
    27  // nolint: gocyclo
    28  func LastModified(search *LastModifiedSearch) (time.Time, error) {
    29  	var latest time.Time
    30  	var err error
    31  
    32  	pm, err := fileutils.NewPatternMatcher(search.Excludes)
    33  	if err != nil {
    34  		return time.Time{}, err
    35  	}
    36  
    37  	isExcluded := func(path string) (bool, error) {
    38  		relPath, err := filepath.Rel(search.Root, path)
    39  		if err != nil {
    40  			return false, err
    41  		}
    42  		if relPath == "." {
    43  			// Don't let them exclude everything, kind of silly.
    44  			return false, nil
    45  		}
    46  		return pm.Matches(relPath)
    47  	}
    48  
    49  	walker := func(filePath string, info os.FileInfo, err error) error {
    50  		if err != nil {
    51  			if os.IsPermission(err) {
    52  				return fmt.Errorf("can't stat '%s'", filePath)
    53  			}
    54  			return err
    55  		}
    56  
    57  		skip, err := isExcluded(filePath)
    58  		switch {
    59  		case err != nil:
    60  			return err
    61  		case skip:
    62  			if info.IsDir() {
    63  				return filepath.SkipDir
    64  			}
    65  			return nil
    66  		}
    67  
    68  		if info.ModTime().After(latest) {
    69  			latest = info.ModTime()
    70  		}
    71  		return nil
    72  	}
    73  
    74  	for _, path := range search.Paths {
    75  		if !filepath.IsAbs(path) {
    76  			path = filepath.Join(search.Root, path)
    77  		}
    78  
    79  		info, err := os.Stat(path)
    80  		if err != nil {
    81  			return latest, fmt.Errorf("internal error: %w", err)
    82  		}
    83  		switch info.IsDir() {
    84  		case false:
    85  			skip, err := isExcluded(path)
    86  			switch {
    87  			case err != nil:
    88  				return time.Time{}, err
    89  			case skip:
    90  				continue
    91  			}
    92  
    93  			if info.ModTime().After(latest) {
    94  				latest = info.ModTime()
    95  				continue
    96  			}
    97  		default:
    98  			if err := filepath.Walk(path, walker); err != nil {
    99  				return latest, err
   100  			}
   101  		}
   102  	}
   103  	return latest, nil
   104  }