package main import ( "flag" "fmt" "log" "os" "os/signal" "path/filepath" "regexp" "sync" "syscall" "github.com/fsnotify/fsnotify" "github.com/ipsusila/opt" "oslog.id/putu/jyoti/utility" _ "github.com/mattn/go-sqlite3" ) type watcherOptions struct { watchPaths []string maxLastFile int pattern string verbose bool catchSignal bool regPattern *regexp.Regexp } type node struct { name string fullName string next *node } type list struct { sync.Mutex numItems int head *node tail *node } func (l *list) clear() { l.head = nil l.tail = nil l.numItems = 0 } func (l *list) count() int { l.Lock() defer l.Unlock() return l.numItems } func (l *list) newNode(name string) (*node, error) { fullName, err := filepath.Abs(name) if err != nil { return nil, err } nd := &node{ name: name, fullName: fullName, } return nd, nil } func (l *list) push(name string) error { l.Lock() defer l.Unlock() nd, err := l.newNode(name) if err != nil { return err } if l.tail == nil { l.head = nd l.tail = nd } else { l.tail.next = nd l.tail = nd } l.numItems++ return nil } func (l *list) peek() *node { l.Lock() defer l.Unlock() if l.head == nil { return nil } return l.head } func (l *list) safePop() *node { if l.head == nil { return nil } nd := l.head l.head = nd.next if l.head == nil { l.tail = nil } l.numItems-- return nd } func (l *list) pop() *node { l.Lock() defer l.Unlock() return l.safePop() } func (l *list) remove(name string) *node { l.Lock() defer l.Unlock() //create item item, err := l.newNode(name) if err != nil { return nil } //empty if l.head == nil || item == nil { return nil } else if l.head.fullName == item.fullName { return l.safePop() } //node current and next nd := l.head next := l.head.next for next != nil { if next.fullName == item.fullName { nextNext := next.next nd.next = nextNext l.numItems-- if next == l.tail { l.tail = nd } return next } //next items nd = next next = nd.next } return nil } func (l *list) iterate(fn func(nd *node)) { l.Lock() defer l.Unlock() nd := l.head for nd != nil { fn(nd) nd = nd.next } } func startWatching(options *watcherOptions) { //file items items := &list{} errItems := &list{} watcher, err := fsnotify.NewWatcher() if err != nil { log.Fatal(err) } done := make(chan bool, 1) mustExit := make(chan bool, 1) // Process events go func() { defer func() { close(done) }() for { select { case ev := <-watcher.Events: isCreate := ev.Op&fsnotify.Create == fsnotify.Create isDelete := ev.Op&fsnotify.Remove == fsnotify.Remove if isCreate { //match pattern //when new items created, delete old items //if number of items exceed threshold if options.regPattern == nil || options.regPattern.MatchString(ev.Name) { items.push(ev.Name) if options.verbose { log.Printf("Item %v ADDED", ev.Name) } if items.count() > options.maxLastFile { node := items.pop() err := os.Remove(node.fullName) if err != nil { errItems.push(node.name) log.Printf("Error %v while deleting %v", err, node.fullName) } } } } else if isDelete { if options.verbose { log.Printf("Item %v REMOVED", ev.Name) } //if manually deleted: remove from list items.remove(ev.Name) } case err := <-watcher.Errors: log.Println("Watcher error:", err) case <-mustExit: return } } }() //Start watching items nwatched := 0 for _, sPath := range options.watchPaths { err = watcher.Add(sPath) if err != nil { log.Printf("Error watching `%s` -> %v", sPath, err) } else { nwatched++ if options.verbose { log.Printf("Watching %s...", sPath) } } } if nwatched == 0 { close(mustExit) } else { //catch signal if options.catchSignal { signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) go func() { for _ = range signalChan { fmt.Println("Received an interrupt, stopping watcher...") close(mustExit) return } }() } else { //wait for q/q buf := make([]byte, 1) for { //wait input n, err := os.Stdin.Read(buf) if err != nil { log.Println(err) } else if n > 0 && buf[0] == 'q' || buf[0] == 'Q' { close(mustExit) break } } } } // Hang so program doesn't exit <-done watcher.Close() //erritems if errItems.count() > 0 { log.Printf("The following item(s) were not removed do to error") errItems.iterate(func(nd *node) { log.Printf("%v", nd.fullName) }) } log.Printf("Watcher %d DONE", items.count()) } func main() { //parse configurationf file cfgFile := flag.String("conf", "config.hjson", "Configuration file") flag.Parse() //load options config, err := opt.FromFile(*cfgFile, opt.FormatAuto) if err != nil { log.Printf("Error while loading configuration file %v -> %v\n", *cfgFile, err) return } //display server ID (displayed in console) log.Printf("Application ID: %v\n", config.GetString("id", "undefined (check configuration)")) //configure log writer lw, err := utility.NewLogWriter(config) if err != nil { log.Printf("Error while setup logging: %v\n", err) return } log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime) log.SetOutput(lw) defer lw.Close() //options /* watchPaths []string maxLastFile int pattern string verbose bool catchSignal bool */ options := &watcherOptions{} wcfg := config.Get("watch") options.watchPaths = wcfg.GetStringArray("paths") options.maxLastFile = wcfg.GetInt("maxFile", 50) options.pattern = wcfg.GetString("pattern", "") options.verbose = wcfg.GetBool("verbose", false) options.catchSignal = wcfg.GetBool("catchSignal", false) //verbose? if options.verbose { log.Printf("%s", config.AsJSON()) } //compile regex if len(options.pattern) > 0 { options.regPattern = regexp.MustCompile(options.pattern) } startWatching(options) }