1package dependency 2 3import ( 4 "fmt" 5 "io/ioutil" 6 "log" 7 "os" 8 "strings" 9 "time" 10 11 "github.com/pkg/errors" 12) 13 14var ( 15 // Ensure implements 16 _ Dependency = (*FileQuery)(nil) 17 18 // FileQuerySleepTime is the amount of time to sleep between queries, since 19 // the fsnotify library is not compatible with solaris and other OSes yet. 20 FileQuerySleepTime = 2 * time.Second 21) 22 23// FileQuery represents a local file dependency. 24type FileQuery struct { 25 stopCh chan struct{} 26 27 path string 28 stat os.FileInfo 29} 30 31// NewFileQuery creates a file dependency from the given path. 32func NewFileQuery(s string) (*FileQuery, error) { 33 s = strings.TrimSpace(s) 34 if s == "" { 35 return nil, fmt.Errorf("file: invalid format: %q", s) 36 } 37 38 return &FileQuery{ 39 stopCh: make(chan struct{}, 1), 40 path: s, 41 }, nil 42} 43 44// Fetch retrieves this dependency and returns the result or any errors that 45// occur in the process. 46func (d *FileQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) { 47 log.Printf("[TRACE] %s: READ %s", d, d.path) 48 49 select { 50 case <-d.stopCh: 51 log.Printf("[TRACE] %s: stopped", d) 52 return "", nil, ErrStopped 53 case r := <-d.watch(d.stat): 54 if r.err != nil { 55 return "", nil, errors.Wrap(r.err, d.String()) 56 } 57 58 log.Printf("[TRACE] %s: reported change", d) 59 60 data, err := ioutil.ReadFile(d.path) 61 if err != nil { 62 return "", nil, errors.Wrap(err, d.String()) 63 } 64 65 d.stat = r.stat 66 return respWithMetadata(string(data)) 67 } 68} 69 70// CanShare returns a boolean if this dependency is shareable. 71func (d *FileQuery) CanShare() bool { 72 return false 73} 74 75// Stop halts the dependency's fetch function. 76func (d *FileQuery) Stop() { 77 close(d.stopCh) 78} 79 80// String returns the human-friendly version of this dependency. 81func (d *FileQuery) String() string { 82 return fmt.Sprintf("file(%s)", d.path) 83} 84 85// Type returns the type of this dependency. 86func (d *FileQuery) Type() Type { 87 return TypeLocal 88} 89 90type watchResult struct { 91 stat os.FileInfo 92 err error 93} 94 95// watch watchers the file for changes 96func (d *FileQuery) watch(lastStat os.FileInfo) <-chan *watchResult { 97 ch := make(chan *watchResult, 1) 98 99 go func(lastStat os.FileInfo) { 100 for { 101 stat, err := os.Stat(d.path) 102 if err != nil { 103 select { 104 case <-d.stopCh: 105 return 106 case ch <- &watchResult{err: err}: 107 return 108 } 109 } 110 111 changed := lastStat == nil || 112 lastStat.Size() != stat.Size() || 113 lastStat.ModTime() != stat.ModTime() 114 115 if changed { 116 select { 117 case <-d.stopCh: 118 return 119 case ch <- &watchResult{stat: stat}: 120 return 121 } 122 } 123 124 time.Sleep(FileQuerySleepTime) 125 } 126 }(lastStat) 127 128 return ch 129} 130