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 }