github.com/wtsi-ssg/wrstat/v3@v3.2.3/watch/watch.go (about) 1 /******************************************************************************* 2 * Copyright (c) 2022 Genome Research Ltd. 3 * 4 * Author: Sendu Bala <sb10@sanger.ac.uk> 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining 7 * a copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sublicense, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be included 15 * in all copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 ******************************************************************************/ 25 26 // package watch is used to watch single files for changes to their mtimes, for 27 // filesystems that don't support inotify. 28 29 package watch 30 31 import ( 32 "os" 33 "sync" 34 "time" 35 ) 36 37 // WatcherCallback, once supplied to New(), will be called each time your 38 // Watcher path changes (that is, the file's mtime changes), and is supplied the 39 // new mtime of that path. 40 type WatcherCallback func(mtime time.Time) 41 42 // Watcher is used to watch a file on a filesystem, and let you do something 43 // whenever it's mtime changes. 44 type Watcher struct { 45 path string 46 cb WatcherCallback 47 pollFrequency time.Duration 48 previous time.Time 49 stop chan bool 50 stopped bool 51 sync.RWMutex 52 } 53 54 // New returns a new Watcher that will call your cb with path's mtime whenever 55 // its mtime changes in the future. 56 // 57 // It also immediately gets the path's mtime, available via Mtime(). 58 // 59 // This is intended for use on filesystems that don't support inotify, so the 60 // watcher will check for changes to path's mtime every pollFrequency. 61 func New(path string, cb WatcherCallback, pollFrequency time.Duration) (*Watcher, error) { 62 mtime, err := getFileMtime(path) 63 if err != nil { 64 return nil, err 65 } 66 67 w := &Watcher{ 68 path: path, 69 cb: cb, 70 pollFrequency: pollFrequency, 71 previous: mtime, 72 } 73 74 w.startWatching() 75 76 return w, nil 77 } 78 79 // getFileMtime returns the mtime of the given file. 80 func getFileMtime(path string) (time.Time, error) { 81 info, err := os.Stat(path) 82 if err != nil { 83 return time.Time{}, err 84 } 85 86 return info.ModTime(), nil 87 } 88 89 // startWatching will start ticking at our pollFrequency, and call our cb if 90 // our path's mtime changes. 91 func (w *Watcher) startWatching() { 92 ticker := time.NewTicker(w.pollFrequency) 93 94 stopTicking := make(chan bool) 95 96 w.stop = stopTicking 97 98 go func() { 99 defer ticker.Stop() 100 101 for { 102 select { 103 case <-stopTicking: 104 return 105 case <-ticker.C: 106 w.callCBIfMtimeChanged() 107 } 108 } 109 }() 110 } 111 112 // callCBIfMtimeChanged calls our cb if the mtime of our path has changed since 113 // the last time this method was called. Errors in trying to get the mtime are 114 // ignored, in the hopes a future attempt will succeed. 115 func (w *Watcher) callCBIfMtimeChanged() { 116 w.Lock() 117 defer w.Unlock() 118 119 mtime, err := getFileMtime(w.path) 120 if err != nil || mtime == w.previous { 121 return 122 } 123 124 w.cb(mtime) 125 126 w.previous = mtime 127 } 128 129 // Mtime returns the latest mtime of our path, captured during New() or the last 130 // time we polled. 131 func (w *Watcher) Mtime() time.Time { 132 w.RLock() 133 defer w.RUnlock() 134 135 return w.previous 136 } 137 138 // Stop will stop watching our path for changes. 139 func (w *Watcher) Stop() { 140 w.Lock() 141 defer w.Unlock() 142 143 if w.stopped { 144 return 145 } 146 147 close(w.stop) 148 w.stopped = true 149 }