pax_global_header00006660000000000000000000000064120634672550014524gustar00rootroot0000000000000052 comment=1dbdf8320a690537582afe0f45c947f501adeaad golang-github-gokyle-fswatch-0.0~git20121217.1dbdf83/000077500000000000000000000000001206346725500217135ustar00rootroot00000000000000golang-github-gokyle-fswatch-0.0~git20121217.1dbdf83/ISSUES000066400000000000000000000001141206346725500226450ustar00rootroot00000000000000* directory deletions not recognised as events * wi should use filepath.Abs golang-github-gokyle-fswatch-0.0~git20121217.1dbdf83/LICENSE000066400000000000000000000013511206346725500227200ustar00rootroot00000000000000Copyright (c) 2012 Kyle Isom Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. golang-github-gokyle-fswatch-0.0~git20121217.1dbdf83/README.md000066400000000000000000000035001206346725500231700ustar00rootroot00000000000000# fswatch ## go library for simple UNIX file system watching fswatch provides simple UNIX file system watching in Go. It is based around the Watcher struct, which should be initialised with either NewWatcher or NewAutoWatcher. Both functions accept a variable number of string arguments specfying the paths to be loaded, which may be globbed, and return a pointer to a Watcher. This value can be started and stopped with the Start() and Stop() methods. The Watcher will automatically stop if all the files it is watching have been deleted. The Start() method returns a read-only channel that receives Notification values. The Stop() method closes the channel, and no files will be watched from that point. The list of files being watched may be retrieved with the Watch() method and the current state of the files being watched may be retrieved with the State() method. See the go docs for more information. In synchronous mode (i.e. Watchers obtained from NewWatcher()), deleted files will not be removed from the watch list, allowing the user to watch for files that might be created at a future time, or to allow notification of files that are deleted and then recreated. The auto-watching mode (i.e. from NewAutoWatcher()) will remove deleted files from the watch list, as it automatically adds new files to the watch list. ## Usage There are two types of Watchers: * static watchers watch a limited set of files; they do not purge deleted files from the watch list. * auto watchers watch a set of files and directories; directories are watched for new files. New files are automatically added, and deleted files are removed from the watch list. Take a look at the provided `clinotify/clinotify.go` for an example; the package is also well-documented. See the godocs for more specifics. ## License `fswatch` is licensed under the ISC license. golang-github-gokyle-fswatch-0.0~git20121217.1dbdf83/clinotify/000077500000000000000000000000001206346725500237135ustar00rootroot00000000000000golang-github-gokyle-fswatch-0.0~git20121217.1dbdf83/clinotify/clinotify.go000066400000000000000000000031261206346725500262440ustar00rootroot00000000000000// clinotify provides an example file system watching command line app. It // scans the file system, and every 15 seconds prints out the files being // watched and their current state. package main import ( "flag" "fmt" "github.com/gokyle/fswatch" "os" "time" ) var dur time.Duration func init() { if len(os.Args) == 1 { fmt.Println("[+] not watching anything, exiting.") os.Exit(1) } dur, _ = time.ParseDuration("15s") } func main() { var w *fswatch.Watcher auto_watch := flag.Bool("a", false, "auto add new files in directories") flag.Parse() paths := flag.Args() if *auto_watch { w = fswatch.NewAutoWatcher(paths...) } else { w = fswatch.NewWatcher(paths...) } fmt.Println("[+] listening...") l := w.Start() go func() { for { n, ok := <-l if !ok { return } var status_text string switch n.Event { case fswatch.CREATED: status_text = "was created" case fswatch.DELETED: status_text = "was deleted" case fswatch.MODIFIED: status_text = "was modified" case fswatch.PERM: status_text = "permissions changed" case fswatch.NOEXIST: status_text = "doesn't exist" case fswatch.NOPERM: status_text = "has invalid permissions" case fswatch.INVALID: status_text = "is invalid" } fmt.Printf("[+] %s %s\n", n.Path, status_text) } }() go func() { for { <-time.After(dur) if !w.Active() { fmt.Println("[!] not watching anything") os.Exit(1) } fmt.Printf("[-] watching: %+v\n", w.State()) } }() time.Sleep(60 * time.Second) fmt.Println("[+] stopping...") w.Stop() time.Sleep(5 * time.Second) } golang-github-gokyle-fswatch-0.0~git20121217.1dbdf83/doc.go000066400000000000000000000045271206346725500230170ustar00rootroot00000000000000/* Copyright (c) 2012 Kyle Isom Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* fswatch provides simple UNIX file system watching in Go. It is based around the Watcher struct, which should be initialised with either NewWatcher or NewAutoWatcher. Both functions accept a variable number of string arguments specfying the paths to be loaded, which may be globbed, and return a pointer to a Watcher. This value can be started and stopped with the Start() and Stop() methods. The Watcher will automatically stop if all the files it is watching have been deleted. The Start() method returns a read-only channel that receives Notification values. The Stop() method closes the channel, and no files will be watched from that point. The list of files being watched may be retrieved with the Watch() method and the current state of the files being watched may be retrieved with the State() method. See the go docs for more information. In synchronous mode (i.e. Watchers obtained from NewWatcher()), deleted files will not be removed from the watch list, allowing the user to watch for files that might be created at a future time, or to allow notification of files that are deleted and then recreated. The auto-watching mode (i.e. from NewAutoWatcher()) will remove deleted files from the watch list, as it automatically adds new files to the watch list. If "." is not specified explicitly in the list of files to watch, new directories created in the current directory will not be seen (as per the behaviour of filepath.Match); any directories being watched will, however. If you wish to watch for changes in the current directory, be sure to specify ".". */ package fswatch golang-github-gokyle-fswatch-0.0~git20121217.1dbdf83/fswatch.go000066400000000000000000000021021206346725500236740ustar00rootroot00000000000000package fswatch import ( "time" ) // These values represent the events fswatch knows about. fswatch uses a // stat(2) call to look up file information; a file will only have a NOPERM // event if the parent directory has no search permission (i.e. parent // directory doesn't have executable permissions for the current user). const ( NONE = iota // No event, initial state. CREATED // File was created. DELETED // File was deleted. MODIFIED // File was modified. PERM // Changed permissions NOEXIST // File does not exist. NOPERM // No permissions for the file (see const block comment). INVALID // Any type of error not represented above. ) // NotificationBufLen is the number of notifications that should be buffered // in the channel. var NotificationBufLen = 16 // WatchDelay is the duration between path scans. It defaults to 100ms. var WatchDelay time.Duration func init() { del, err := time.ParseDuration("100ms") if err != nil { panic("couldn't set up fswatch: " + err.Error()) } WatchDelay = del } golang-github-gokyle-fswatch-0.0~git20121217.1dbdf83/watch_item.go000066400000000000000000000033601206346725500243700ustar00rootroot00000000000000package fswatch import "os" type watchItem struct { Path string StatInfo os.FileInfo LastEvent int } func watchPath(path string) (wi *watchItem) { wi = new(watchItem) wi.Path = path wi.LastEvent = NONE fi, err := os.Stat(path) if err == nil { wi.StatInfo = fi } else if os.IsNotExist(err) { wi.LastEvent = NOEXIST } else if os.IsPermission(err) { wi.LastEvent = NOPERM } else { wi.LastEvent = INVALID } return } func (wi *watchItem) Update() bool { fi, err := os.Stat(wi.Path) if err != nil { if os.IsNotExist(err) { if wi.LastEvent == NOEXIST { return false } else if wi.LastEvent == DELETED { wi.LastEvent = NOEXIST return false } else { wi.LastEvent = DELETED return true } } else if os.IsPermission(err) { if wi.LastEvent == NOPERM { return false } else { wi.LastEvent = NOPERM return true } } else { wi.LastEvent = INVALID return false } } if wi.LastEvent == NOEXIST { wi.LastEvent = CREATED wi.StatInfo = fi return true } else if fi.ModTime().After(wi.StatInfo.ModTime()) { wi.StatInfo = fi switch wi.LastEvent { case NONE, CREATED, NOPERM, INVALID: wi.LastEvent = MODIFIED case DELETED, NOEXIST: wi.LastEvent = CREATED } return true } else if fi.Mode() != wi.StatInfo.Mode() { wi.LastEvent = PERM wi.StatInfo = fi return true } return false } // Type Notification represents a file state change. The Path field indicates // the file that was changed, while last event corresponds to one of the // event type constants. type Notification struct { Path string Event int } // Notification returns a notification from a watchItem. func (wi *watchItem) Notification() *Notification { return &Notification{wi.Path, wi.LastEvent} } golang-github-gokyle-fswatch-0.0~git20121217.1dbdf83/watcher.go000066400000000000000000000131401206346725500236760ustar00rootroot00000000000000package fswatch import ( "os" "path/filepath" "time" ) // Type Watcher represents a file system watcher. It should be initialised // with NewWatcher or NewAutoWatcher, and started with Watcher.Start(). type Watcher struct { paths map[string]*watchItem notify_chan chan *Notification add_chan chan *watchItem auto_watch bool } // newWatcher is the internal function for properly setting up a new watcher. func newWatcher(dir_notify bool, initpaths ...string) (w *Watcher) { w = new(Watcher) w.auto_watch = dir_notify w.paths = make(map[string]*watchItem, 0) var paths []string for _, path := range initpaths { matches, err := filepath.Glob(path) if err != nil { continue } paths = append(paths, matches...) } if dir_notify { w.syncAddPaths(paths...) } else { for _, path := range paths { w.paths[path] = watchPath(path) } } return } // NewWatcher initialises a new Watcher with an initial set of paths. It // does not start listening, and this Watcher will not automatically add // files created under any directories it is watching. func NewWatcher(paths ...string) *Watcher { return newWatcher(false, paths...) } // NewAutoWatcher initialises a new Watcher with an initial set of paths. // It behaves the same as NewWatcher, except it will automatically add // files created in directories it is watching, including adding any // subdirectories. func NewAutoWatcher(paths ...string) *Watcher { return newWatcher(true, paths...) } // Start begins watching the files, sending notifications when files change. // It returns a channel that notifications are sent on. func (w *Watcher) Start() <-chan *Notification { if w.notify_chan != nil { return w.notify_chan } if w.auto_watch { w.add_chan = make(chan *watchItem, NotificationBufLen) go w.watchItemListener() } w.notify_chan = make(chan *Notification, NotificationBufLen) go w.watch(w.notify_chan) return w.notify_chan } // Stop listening for changes to the files. func (w *Watcher) Stop() { if w.notify_chan != nil { close(w.notify_chan) } if w.add_chan != nil { close(w.add_chan) } } // Returns true if the Watcher is actively looking for changes. func (w *Watcher) Active() bool { return w.paths != nil && len(w.paths) > 0 } // The Add method takes a variable number of string arguments and adds those // files to the watch list, returning the number of files added. func (w *Watcher) Add(inpaths ...string) { var paths []string for _, path := range inpaths { matches, err := filepath.Glob(path) if err != nil { continue } paths = append(paths, matches...) } if w.auto_watch && w.notify_chan != nil { for _, path := range paths { wi := watchPath(path) w.addPaths(wi) } } else if w.auto_watch { w.syncAddPaths(paths...) } else { for _, path := range paths { w.paths[path] = watchPath(path) } } } // goroutine that cycles through the list of paths and checks for updates. func (w *Watcher) watch(sndch chan<- *Notification) { defer func() { recover() }() for { <-time.After(WatchDelay) for _, wi := range w.paths { if wi.Update() && w.shouldNotify(wi) { sndch <- wi.Notification() } if wi.LastEvent == NOEXIST && w.auto_watch { delete(w.paths, wi.Path) } if len(w.paths) == 0 { w.Stop() } } } } func (w *Watcher) shouldNotify(wi *watchItem) bool { if w.auto_watch && wi.StatInfo.IsDir() && !(wi.LastEvent == DELETED || wi.LastEvent == NOEXIST) { go w.addPaths(wi) return false } return true } func (w *Watcher) addPaths(wi *watchItem) { walker := getWalker(w, wi.Path, w.add_chan) go filepath.Walk(wi.Path, walker) } func (w *Watcher) watchItemListener() { defer func() { recover() }() for { wi := <-w.add_chan if wi == nil { continue } else if _, watching := w.paths[wi.Path]; watching { continue } w.paths[wi.Path] = wi } } func getWalker(w *Watcher, root string, addch chan<- *watchItem) func(string, os.FileInfo, error) error { walker := func(path string, info os.FileInfo, err error) error { if err != nil { return err } if path == root { return nil } wi := watchPath(path) if wi == nil { return nil } else if _, watching := w.paths[wi.Path]; !watching { wi.LastEvent = CREATED w.notify_chan <- wi.Notification() addch <- wi if !wi.StatInfo.IsDir() { return nil } w.addPaths(wi) } return nil } return walker } func (w *Watcher) syncAddPaths(paths ...string) { for _, path := range paths { wi := watchPath(path) if wi == nil { continue } else if wi.LastEvent == NOEXIST { continue } else if _, watching := w.paths[wi.Path]; watching { continue } w.paths[wi.Path] = wi if wi.StatInfo.IsDir() { w.syncAddDir(wi) } } } func (w *Watcher) syncAddDir(wi *watchItem) { walker := func(path string, info os.FileInfo, err error) error { if err != nil { return err } if path == wi.Path { return nil } new_wi := watchPath(path) if new_wi != nil { w.paths[path] = new_wi if !new_wi.StatInfo.IsDir() { return nil } if _, watching := w.paths[new_wi.Path]; !watching { w.syncAddDir(new_wi) } } return nil } filepath.Walk(wi.Path, walker) } // Watching returns a list of the files being watched. func (w *Watcher) Watching() (paths []string) { paths = make([]string, 0) for path, _ := range w.paths { paths = append(paths, path) } return } // State returns a slice of Notifications representing the files being watched // and their last event. func (w *Watcher) State() (state []Notification) { state = make([]Notification, 0) if w.paths == nil { return } for _, wi := range w.paths { state = append(state, *wi.Notification()) } return }