github.com/mkasner/goat@v0.0.0-20190419083224-77b17249a8e3/context/watcher.go (about)

     1  package context
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"log"
     7  	"os"
     8  	"strings"
     9  	"time"
    10  )
    11  
    12  // Watcher represents a file watcher.
    13  type Watcher struct {
    14  	Extension string   `json:"extension" yaml:"extension"`
    15  	Directory string   `json:"directory" yaml:"directory"`
    16  	Excludes  []string `json:"excludes" yaml:"excludes"`
    17  	Tasks     []*Task  `json:"tasks" yaml:"tasks"`
    18  	JobsC     chan<- Job
    19  	Targets   map[string]map[string]os.FileInfo
    20  }
    21  
    22  // launch launches the watcher's process.
    23  func (w *Watcher) Launch(ctx *Context, jobsC chan<- Job) {
    24  	w.JobsC = jobsC
    25  	w.Targets = make(map[string]map[string]os.FileInfo)
    26  	watchDir := ctx.Wd
    27  	if w.Directory != "" {
    28  		watchDir = watchDir + "/" + w.Directory
    29  	}
    30  	w.readDir(watchDir, true)
    31  	w.Printf("%s", "Watching...")
    32  	for {
    33  		time.Sleep(time.Duration(ctx.Interval) * time.Millisecond)
    34  		w.readDir(watchDir, false)
    35  	}
    36  }
    37  
    38  // readDir reads the directory named by dirname.
    39  func (w *Watcher) readDir(dirname string, init bool) error {
    40  	if w.excludeDir(dirname) {
    41  		return nil
    42  	}
    43  	fileInfos, err := ioutil.ReadDir(dirname)
    44  	if err != nil {
    45  		return err
    46  	}
    47  	for _, fileInfo := range fileInfos {
    48  		name := fileInfo.Name()
    49  		switch {
    50  		case strings.HasPrefix(name, "."):
    51  		case fileInfo.IsDir():
    52  			if err := w.readDir(dirname+"/"+name, init); err != nil {
    53  				return err
    54  			}
    55  		case w.exclude(name):
    56  		case strings.HasSuffix(name, "."+w.Extension):
    57  			_, prs := w.Targets[dirname]
    58  			if !prs {
    59  				w.Targets[dirname] = make(map[string]os.FileInfo)
    60  			}
    61  			if init {
    62  				w.Targets[dirname][name] = fileInfo
    63  			} else {
    64  				preservedFileInfo, prs := w.Targets[dirname][name]
    65  				if !prs || preservedFileInfo.ModTime() != fileInfo.ModTime() {
    66  					w.Targets[dirname][name] = fileInfo
    67  					var action string
    68  					if !prs {
    69  						action = "created"
    70  					} else {
    71  						action = "updated"
    72  					}
    73  					w.sendJob(dirname, name, action)
    74  				}
    75  			}
    76  		}
    77  	}
    78  	if !init {
    79  		preservedFileInfos, prs := w.Targets[dirname]
    80  		if prs {
    81  			for name, _ := range preservedFileInfos {
    82  				exist := false
    83  				for _, fileInfo := range fileInfos {
    84  					if name == fileInfo.Name() {
    85  						exist = true
    86  						break
    87  					}
    88  				}
    89  				if !exist {
    90  					delete(w.Targets[dirname], name)
    91  					w.sendJob(dirname, name, "deleted")
    92  				}
    93  			}
    94  		}
    95  	}
    96  	return nil
    97  }
    98  
    99  // sendJob sends a job to the channel.
   100  func (w *Watcher) sendJob(dirname, name, action string) {
   101  	message := fmt.Sprintf("%s was %s.", dirname+"/"+name, action)
   102  	w.JobsC <- Job{Watcher: w, Message: message}
   103  }
   104  
   105  // Printf calls log.Printf.
   106  func (w *Watcher) Printf(format string, v ...interface{}) {
   107  	watchDir := "project root"
   108  	if w.Directory != "" {
   109  		watchDir = w.Directory
   110  	}
   111  	log.Printf("[Watcher for "+w.Extension+" files under "+watchDir+"] "+format, v...)
   112  }
   113  
   114  // exclude returns true if the file should be not checked.
   115  func (w *Watcher) exclude(filename string) bool {
   116  	for _, excludeFilename := range w.Excludes {
   117  		if filename == excludeFilename {
   118  			return true
   119  		}
   120  	}
   121  	return false
   122  }
   123  
   124  // excludeDir returns true if the dir should be not watched.
   125  func (w *Watcher) excludeDir(dirname string) bool {
   126  	for _, excludeFilename := range w.Excludes {
   127  		excludeFilename = strings.TrimRight(excludeFilename, "*/")
   128  		if strings.Contains(dirname, excludeFilename) {
   129  			return true
   130  		}
   131  	}
   132  	return false
   133  }