// StreamListings takes a path and generates a assets.DirListing struct when it receives any signal, it will go through all the files within each listings. func StreamListings(config ListingConfig) (flux.Reactor, error) { dir, err := assets.DirListings(config.Path, config.Validator, config.Mux) if err != nil { return nil, err } return flux.FlatSimple(func(root flux.Reactor, data interface{}) { if err := dir.Reload(); err != nil { root.ReplyError(err) return } // no error occured reloading, so we stream out the directory, list dir.Listings.Wo.RLock() for _, files := range dir.Listings.Tree { if config.DirAlso { if !config.UseRelative { root.Reply(files.AbsDir) } else { root.Reply(filepath.ToSlash(files.Dir)) } } files.Tree.Each(func(mod, real string) { if !config.UseRelative { rel, err := filepath.Abs(real) if err != nil { rel = real } // log.Printf("Sending %s -> %s -> %s", files.AbsDir, real, rel) root.Reply(rel) } else { root.Reply(filepath.Join(files.Dir, real)) } }) } dir.Listings.Wo.RUnlock() }), nil }
// Watch returns a task handler that watches a path for changes and passes down the file which changed func Watch(m WatchConfig) flux.Reactor { var running bool mo := flux.Reactive(func(root flux.Reactor, err error, _ interface{}) { if err != nil { root.ReplyError(err) return } if running { return } stat, err := os.Stat(m.Path) if err != nil { root.ReplyError(err) go root.Close() return } running = true if !stat.IsDir() { flux.GoDefer("Watch", func() { defer root.Close() for { wo, err := fsnotify.NewWatcher() if err != nil { root.ReplyError(err) break } if err := wo.Add(m.Path); err != nil { wo.Close() break } select { case ev, ok := <-wo.Events: if ok { root.Reply(ev) } case erx, ok := <-wo.Errors: if ok { root.ReplyError(erx) } case <-root.CloseNotify(): wo.Close() break } wo.Close() } }) return } dir, err := assets.DirListings(m.Path, m.Validator, m.Mux) if err != nil { root.ReplyError(err) go root.Close() return } flux.GoDefer("Watch", func() { defer root.Close() for { wo, err := fsnotify.NewWatcher() if err != nil { root.ReplyError(err) break } dir.Listings.Wo.RLock() for _, files := range dir.Listings.Tree { wo.Add(files.AbsDir) files.Tree.Each(func(mod, real string) { rel, _ := filepath.Abs(real) wo.Add(rel) // wo.Add(filepath.Join(files.AbsDir, real)) }) } dir.Listings.Wo.RUnlock() select { case <-root.CloseNotify(): wo.Close() break case ev, ok := <-wo.Events: if ok { file := filepath.Clean(ev.Name) // stat, _ := os.Stat(file) if (&m).Validator != nil { if (&m).Validator(file, nil) { root.Reply(ev) } } else { root.Reply(ev) } } case erx, ok := <-wo.Errors: if ok { root.ReplyError(erx) } } wo.Close() if err = dir.Reload(); err != nil { root.ReplyError(err) } } }) }) mo.Send(true) return mo }
// WatchSet unlike Watch is not set for only working with one directory, by providing a WatchSetConfig you can supply multiple directories and files which will be sorted and watch if all paths were found to be invalid then the watcher will be closed and so will the task, an invalid file error will be forwarded down the reactor chain func WatchSet(m WatchSetConfig) flux.Reactor { var running bool mo := flux.Reactive(func(root flux.Reactor, err error, _ interface{}) { if err != nil { root.ReplyError(err) return } if running { return } running = true var dirlistings []*assets.DirListing var files []string var dirsAdded = make(map[string]bool) for _, path := range m.Path { if dirsAdded[path] { continue } stat, err := os.Stat(path) if err != nil { // log.Printf("stat error: %s", err) root.ReplyError(err) continue } if stat.IsDir() { if dir, err := assets.DirListings(path, m.Validator, m.Mux); err == nil { dirsAdded[path] = true dirlistings = append(dirlistings, dir) } else { root.ReplyError(err) } } else { if !dirsAdded[filepath.Dir(path)] { files = append(files, path) } } } if len(dirlistings) <= 0 && len(files) <= 0 { log.Printf("no dirlistings, will close") go root.Close() log.Printf("no dirlistings, will close") return } flux.GoDefer("Watch", func() { defer root.Close() for { wo, err := fsnotify.NewWatcher() if err != nil { root.ReplyError(err) break } // var watched = make(map[string]bool) //reload all concerned directories into watcher for _, dir := range dirlistings { dir.Listings.Wo.RLock() for _, files := range dir.Listings.Tree { // log.Printf("Checking folder: %s", files.Dir) // if !watched[files.AbsDir] { // watched[files.AbsDir] = true wo.Add(files.AbsDir) // } files.Tree.Each(func(mod, real string) { // if watched[real] { // log.Printf("duplicate found %s -> %s -> %s", mod, real, files.AbsDir) // return // } // watched[real] = true rel, _ := filepath.Abs(real) wo.Add(rel) // if err != nil { // rel = real // } // wo.Add(filepath.Join(files.Dir, real)) // wo.Add(filepath.Join(files.AbsDir, real)) }) } dir.Listings.Wo.RUnlock() } //reload all concerned files found in the path for _, file := range files { wo.Add(file) } select { case <-root.CloseNotify(): break case ev, ok := <-wo.Events: if ok { if (&m).Validator != nil { file := filepath.Clean(ev.Name) // log.Printf("checking file: %s", file) if (&m).Validator(file, nil) { // log.Printf("passed file: %s", file) root.Reply(ev) } } else { // log.Printf("backdrop file: %s", ev) root.Reply(ev) } } case erx, ok := <-wo.Errors: if ok { root.ReplyError(erx) } } wo.Close() //reload all concerned directories for _, dir := range dirlistings { dir.Reload() } } }) }) mo.Send(true) return mo }