github.com/argoproj/argo-events@v1.9.1/eventsources/common/naivewatcher/watcher.go (about)

     1  package naivewatcher
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/argoproj/argo-events/eventsources/common/fsevent"
    11  )
    12  
    13  const (
    14  	eventsQueueSize = 100
    15  	errorsQueueSize = 100
    16  )
    17  
    18  // Watchable is an interface of FS which can be watched by this watcher
    19  type Watchable interface {
    20  	Walk(root string, walkFn filepath.WalkFunc) error
    21  	GetFileID(fi os.FileInfo) interface{}
    22  }
    23  
    24  // filePathAndInfo is an internal struct to manage path and file info
    25  type filePathAndInfo struct {
    26  	path  string
    27  	info  os.FileInfo
    28  	found bool
    29  }
    30  
    31  // Watcher is a file watcher
    32  type Watcher struct {
    33  	watchable Watchable
    34  
    35  	// manage target directories
    36  	watchList  map[string]map[interface{}]*filePathAndInfo
    37  	mWatchList *sync.RWMutex
    38  
    39  	// internal use
    40  	mCheck       *Mutex
    41  	mutexRunning *Mutex
    42  	Events       chan fsevent.Event
    43  	Errors       chan error
    44  	stop         chan struct{}
    45  	stopResp     chan struct{}
    46  }
    47  
    48  // NewWatcher is the initializer of watcher struct
    49  func NewWatcher(watchable Watchable) (*Watcher, error) {
    50  	watcher := &Watcher{
    51  		watchable:    watchable,
    52  		mWatchList:   new(sync.RWMutex),
    53  		watchList:    map[string]map[interface{}]*filePathAndInfo{},
    54  		mCheck:       new(Mutex),
    55  		mutexRunning: new(Mutex),
    56  		Events:       make(chan fsevent.Event, eventsQueueSize),
    57  		Errors:       make(chan error, errorsQueueSize),
    58  		stop:         make(chan struct{}),
    59  		stopResp:     make(chan struct{}),
    60  	}
    61  	return watcher, nil
    62  }
    63  
    64  // Add adds a directory into the watch list
    65  func (w *Watcher) Add(dir string) error {
    66  	w.mWatchList.Lock()
    67  	defer w.mWatchList.Unlock()
    68  	w.watchList[dir] = nil
    69  	return nil
    70  }
    71  
    72  // Remove removes a directory from the watch list
    73  func (w *Watcher) Remove(dir string) error {
    74  	w.mWatchList.Lock()
    75  	defer w.mWatchList.Unlock()
    76  	delete(w.watchList, dir)
    77  	return nil
    78  }
    79  
    80  // WatchList returns target directories
    81  func (w *Watcher) WatchList() []string {
    82  	w.mWatchList.RLock()
    83  	defer w.mWatchList.RUnlock()
    84  	dirs := []string{}
    85  	for dir := range w.watchList {
    86  		dirs = append(dirs, dir)
    87  	}
    88  	return dirs
    89  }
    90  
    91  // Close cleans up the watcher
    92  func (w *Watcher) Close() error {
    93  	_ = w.Stop()
    94  	close(w.Events)
    95  	close(w.Errors)
    96  	return nil
    97  }
    98  
    99  // Start starts the watcher
   100  func (w *Watcher) Start(interval time.Duration) error {
   101  	if !w.mutexRunning.TryLock() {
   102  		return fmt.Errorf("watcher has already started")
   103  	}
   104  	// run initial check
   105  	err := w.Check()
   106  	if err != nil {
   107  		return err
   108  	}
   109  	go func() {
   110  		defer w.mutexRunning.Unlock()
   111  		defer close(w.stopResp)
   112  		for {
   113  			select {
   114  			case <-time.After(interval):
   115  				err := w.Check()
   116  				if err != nil {
   117  					w.Errors <- err
   118  				}
   119  			case <-w.stop:
   120  				return
   121  			}
   122  		}
   123  	}()
   124  	return nil
   125  }
   126  
   127  // Stop stops the watcher
   128  func (w *Watcher) Stop() error {
   129  	if !w.mutexRunning.IsLocked() {
   130  		return fmt.Errorf("watcher is not started")
   131  	}
   132  	select {
   133  	case <-w.stop:
   134  	default:
   135  		close(w.stop)
   136  	}
   137  	<-w.stopResp
   138  	return nil
   139  }
   140  
   141  // Check checks the state of target directories
   142  func (w *Watcher) Check() error {
   143  	if !w.mCheck.TryLock() {
   144  		return fmt.Errorf("another check is still running")
   145  	}
   146  	defer w.mCheck.Unlock()
   147  
   148  	w.mWatchList.Lock()
   149  	defer w.mWatchList.Unlock()
   150  
   151  	for dir, walkedFiles := range w.watchList {
   152  		if walkedFiles == nil {
   153  			walkedFiles = make(map[interface{}]*filePathAndInfo)
   154  			w.watchList[dir] = walkedFiles
   155  		}
   156  		for fileID := range walkedFiles {
   157  			walkedFiles[fileID].found = false
   158  		}
   159  		walkFn := func(path string, info os.FileInfo, err error) error {
   160  			if err != nil {
   161  				w.Errors <- err
   162  				return nil
   163  			}
   164  			// Ignore walk root
   165  			if dir == path {
   166  				return nil
   167  			}
   168  			fileID := w.watchable.GetFileID(info)
   169  			lastPathAndInfo := walkedFiles[fileID]
   170  			if lastPathAndInfo == nil {
   171  				w.Events <- fsevent.Event{Op: fsevent.Create, Name: path}
   172  				walkedFiles[fileID] = &filePathAndInfo{path, info, true}
   173  			} else {
   174  				lastInfo := lastPathAndInfo.info
   175  				var op fsevent.Op
   176  				if path != lastPathAndInfo.path {
   177  					op |= fsevent.Rename
   178  				}
   179  				if !info.ModTime().Equal(lastInfo.ModTime()) || info.Size() != lastInfo.Size() {
   180  					op |= fsevent.Write
   181  				}
   182  				if info.Mode() != lastInfo.Mode() {
   183  					op |= fsevent.Chmod
   184  				}
   185  				if op != 0 {
   186  					w.Events <- fsevent.Event{Op: op, Name: path}
   187  				}
   188  				walkedFiles[fileID].path = path
   189  				walkedFiles[fileID].info = info
   190  				walkedFiles[fileID].found = true
   191  			}
   192  			return nil
   193  		}
   194  
   195  		err := w.watchable.Walk(dir, walkFn)
   196  		if err != nil {
   197  			return err
   198  		}
   199  
   200  		for fileID, pathAndInfo := range walkedFiles {
   201  			if !pathAndInfo.found {
   202  				w.Events <- fsevent.Event{Op: fsevent.Remove, Name: pathAndInfo.path}
   203  				delete(walkedFiles, fileID)
   204  			}
   205  		}
   206  	}
   207  
   208  	return nil
   209  }