github.com/gofunct/common@v0.0.0-20190131174352-fd058c7fbf22/pkg/fs/watcher/watcher.go (about)

     1  package watcher
     2  
     3  import (
     4  	//"fmt"
     5  
     6  	"github.com/gofunct/common/pkg/fs/watcher/fswatch"
     7  	"github.com/mgutz/str"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  )
    14  
    15  const (
    16  	// IgnoreThresholdRange is the amount of time in ns to ignore when
    17  	// receiving watch events for the same file
    18  	IgnoreThresholdRange = 50 * 1000000 // convert to ms
    19  )
    20  
    21  // SetWatchDelay sets the watch delay
    22  func SetWatchDelay(delay time.Duration) {
    23  	fswatch.WatchDelay = delay
    24  }
    25  
    26  // Watcher is a wrapper around which adds some additional features:
    27  //
    28  // - recursive directory watch
    29  // - buffer to even chan
    30  // - even time
    31  //
    32  // Original work from https://github.com/bronze1man/kmg
    33  type Watcher struct {
    34  	*fswatch.Watcher
    35  	Event chan *FileEvent
    36  	Error chan error
    37  	//default ignore all file start with "."
    38  	IgnorePathFn func(path string) bool
    39  	//default is nil,if is nil ,error send through Error chan,if is not nil,error handle by this func
    40  	ErrorHandler func(err error)
    41  	isClosed     bool
    42  	quit         chan bool
    43  	cache        map[string]*os.FileInfo
    44  	mu           sync.Mutex
    45  }
    46  
    47  // NewWatcher creates an instance of watcher.
    48  func NewWatcher(bufferSize int) (watcher *Watcher, err error) {
    49  
    50  	fswatcher := fswatch.NewAutoWatcher()
    51  
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	watcher = &Watcher{
    56  		Watcher:      fswatcher,
    57  		Error:        make(chan error, 10),
    58  		Event:        make(chan *FileEvent, bufferSize),
    59  		IgnorePathFn: DefaultIgnorePathFn,
    60  		cache:        map[string]*os.FileInfo{},
    61  	}
    62  	return
    63  }
    64  
    65  // Close closes the watcher channels.
    66  func (w *Watcher) Close() error {
    67  	if w.isClosed {
    68  		return nil
    69  	}
    70  	w.Watcher.Stop()
    71  	w.quit <- true
    72  	w.isClosed = true
    73  	return nil
    74  }
    75  
    76  func (w *Watcher) eventLoop() {
    77  	// cache := map[string]*os.FileInfo{}
    78  	// mu := &sync.Mutex{}
    79  
    80  	coutput := w.Watcher.Start()
    81  	for {
    82  		event, ok := <-coutput
    83  		if !ok {
    84  			return
    85  		}
    86  
    87  		// fmt.Printf("event %+v\n", event)
    88  		if w.IgnorePathFn(event.Path) {
    89  			continue
    90  		}
    91  
    92  		// you can not stat a delete file...
    93  		if event.Event == fswatch.DELETED || event.Event == fswatch.NOEXIST {
    94  			// adjust with arbitrary value because it was deleted
    95  			// before it got here
    96  			//fmt.Println("sending fi wevent", event)
    97  			w.Event <- newFileEvent(event.Event, event.Path, time.Now().UnixNano()-10)
    98  			continue
    99  		}
   100  
   101  		fi, err := os.Stat(event.Path)
   102  		if os.IsNotExist(err) {
   103  			//fmt.Println("not exists", event)
   104  			continue
   105  		}
   106  
   107  		// fsnotify is sending multiple MODIFY events for the same
   108  		// file which is likely OS related. The solution here is to
   109  		// compare the current stats of a file against its last stats
   110  		// (if any) and if it falls within a nanoseconds threshold,
   111  		// ignore it.
   112  		w.mu.Lock()
   113  		oldFI := w.cache[event.Path]
   114  		w.cache[event.Path] = &fi
   115  
   116  		// if oldFI != nil {
   117  		// 	fmt.Println("new FI", fi.ModTime().UnixNano())
   118  		// 	fmt.Println("old FI", (*oldFI).ModTime().UnixNano()+IgnoreThresholdRange)
   119  		// }
   120  
   121  		if oldFI != nil && fi.ModTime().UnixNano() < (*oldFI).ModTime().UnixNano()+IgnoreThresholdRange {
   122  			w.mu.Unlock()
   123  			continue
   124  		}
   125  		w.mu.Unlock()
   126  
   127  		//fmt.Println("sending Event", fi.Name())
   128  
   129  		//fmt.Println("sending fi wevent", event)
   130  		w.Event <- newFileEvent(event.Event, event.Path, fi.ModTime().UnixNano())
   131  
   132  		if err != nil {
   133  			//rename send two events,one old file,one new file,here ignore old one
   134  			if os.IsNotExist(err) {
   135  				continue
   136  			}
   137  			w.errorHandle(err)
   138  			continue
   139  		}
   140  		// case err := <-w.Watcher.Errors:
   141  		// 	w.errorHandle(err)
   142  		// case _ = <-w.quit:
   143  		// 	break
   144  		// }
   145  	}
   146  }
   147  func (w *Watcher) errorHandle(err error) {
   148  	if w.ErrorHandler == nil {
   149  		w.Error <- err
   150  		return
   151  	}
   152  	w.ErrorHandler(err)
   153  }
   154  
   155  // GetErrorChan gets error chan.
   156  func (w *Watcher) GetErrorChan() chan error {
   157  	return w.Error
   158  }
   159  
   160  // GetEventChan gets event chan.
   161  func (w *Watcher) GetEventChan() chan *FileEvent {
   162  	return w.Event
   163  }
   164  
   165  // WatchRecursive watches a directory recursively. If a dir is created
   166  // within directory it is also watched.
   167  func (w *Watcher) WatchRecursive(path string) error {
   168  	path, err := filepath.Abs(path)
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	_, err = os.Stat(path)
   174  	if err != nil {
   175  		return err
   176  	}
   177  
   178  	w.Watcher.Add(path)
   179  
   180  	//util.Debug("watcher", "watching %s %s\n", path, time.Now())
   181  	return nil
   182  }
   183  
   184  // Start starts the watcher
   185  func (w *Watcher) Start() {
   186  	go w.eventLoop()
   187  }
   188  
   189  // func (w *Watcher) getSubFolders(path string) (paths []string, err error) {
   190  // 	err = filepath.Walk(path, func(newPath string, info os.FileInfo, err error) error {
   191  // 		if err != nil {
   192  // 			return err
   193  // 		}
   194  
   195  // 		if !info.IsDir() {
   196  // 			return nil
   197  // 		}
   198  // 		if w.IgnorePathFn(newPath) {
   199  // 			return filepath.SkipDir
   200  // 		}
   201  // 		paths = append(paths, newPath)
   202  // 		return nil
   203  // 	})
   204  // 	return paths, err
   205  // }
   206  
   207  // DefaultIgnorePathFn checks whether a path is ignored. Currently defaults
   208  // to hidden files on *nix systems, ie they start with a ".".
   209  func DefaultIgnorePathFn(path string) bool {
   210  	if strings.HasPrefix(path, ".") || strings.Contains(path, "/.") {
   211  		return true
   212  	}
   213  
   214  	// ignore node
   215  	if strings.HasPrefix(path, "node_modules") || strings.Contains(path, "/node_modules") {
   216  		return true
   217  	}
   218  
   219  	// vim creates random numeric files
   220  	base := filepath.Base(path)
   221  	if str.IsNumeric(base) {
   222  		return true
   223  	}
   224  	return false
   225  }
   226  
   227  // SetIgnorePathFn sets the function which determines if a path should be
   228  // skipped when watching.
   229  func (w *Watcher) SetIgnorePathFn(fn func(string) bool) {
   230  	w.Watcher.IgnorePathFn = fn
   231  }