github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/client/cli/run/watcher.go (about)

     1  package runtime
     2  
     3  import (
     4  	"fmt"
     5  	"io/fs"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/fsnotify/fsnotify"
    12  
    13  	"github.com/tickoalcantara12/micro/v3/service/logger"
    14  )
    15  
    16  type CallbackFunc func() error
    17  
    18  type watcher struct {
    19  	root        string
    20  	watchDelay  time.Duration
    21  	fileWatcher *fsnotify.Watcher
    22  
    23  	eventsChan   chan string
    24  	callbackFunc CallbackFunc
    25  	stopChan     chan struct{}
    26  }
    27  
    28  func NewWatcher(root string, delay time.Duration, fn CallbackFunc) (*watcher, error) {
    29  	fileWatcher, err := fsnotify.NewWatcher()
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  
    34  	return &watcher{
    35  		root:         root,
    36  		watchDelay:   delay,
    37  		fileWatcher:  fileWatcher,
    38  		eventsChan:   make(chan string, 1000),
    39  		callbackFunc: fn,
    40  		stopChan:     make(chan struct{}),
    41  	}, nil
    42  }
    43  
    44  // Watch the file changes in specific directories
    45  func (w *watcher) Watch() error {
    46  	err := filepath.WalkDir(w.root, func(path string, info fs.DirEntry, err error) error {
    47  		if err != nil {
    48  			return err
    49  		}
    50  
    51  		if info != nil && !info.IsDir() {
    52  			return nil
    53  		}
    54  
    55  		return w.watchDirectory(path)
    56  	})
    57  
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	w.start()
    63  
    64  	return nil
    65  }
    66  
    67  // start the watching process
    68  func (w *watcher) start() {
    69  	for {
    70  		select {
    71  		case <-w.stopChan:
    72  			logger.Infof("Watcher is exiting...")
    73  			return
    74  		case <-w.eventsChan:
    75  
    76  			time.Sleep(w.watchDelay)
    77  			w.flushEvents()
    78  
    79  			if err := w.callbackFunc(); err != nil {
    80  				logger.Errorf("Watcher callback function execute error: %v", err)
    81  				break
    82  			}
    83  
    84  		}
    85  	}
    86  }
    87  
    88  // flushEvents flush the events in buffer channel
    89  func (w *watcher) flushEvents() {
    90  	for {
    91  		select {
    92  		case ev := <-w.eventsChan:
    93  			logger.Debugf("Watcher flush event: %v", ev)
    94  		default:
    95  			return
    96  		}
    97  	}
    98  }
    99  
   100  // validExtension checks the extension of file is valid to be watched
   101  func (w *watcher) validExtension(filepath string) bool {
   102  	if strings.HasSuffix(filepath, ".go") || strings.HasSuffix(filepath, ".proto") {
   103  		return true
   104  	}
   105  
   106  	return false
   107  }
   108  
   109  // watchDirectory watch all the files in the dir, recurse all the subdirectories
   110  func (w *watcher) watchDirectory(dir string) error {
   111  	logger.Infof("Watcher is watching path: %+v", dir)
   112  
   113  	err := w.fileWatcher.Add(dir)
   114  	if err != nil {
   115  		logger.Fatal(err)
   116  	}
   117  
   118  	go func() {
   119  		for {
   120  			select {
   121  			case <-w.stopChan:
   122  				return
   123  			case event, ok := <-w.fileWatcher.Events:
   124  				if !ok {
   125  					return
   126  				}
   127  				if event.Op&fsnotify.Write != fsnotify.Write {
   128  					break
   129  				}
   130  
   131  				eventPath := event.Name
   132  
   133  				if !w.validExtension(eventPath) {
   134  					break
   135  				}
   136  
   137  				f, err := os.Stat(eventPath)
   138  				if err != nil {
   139  					logger.Errorf("File get file info: %v", err)
   140  					break
   141  				}
   142  
   143  				if f.IsDir() {
   144  					err := w.watchDirectory(eventPath)
   145  					if err != nil {
   146  						logger.Errorf("Watching dir error: %v", err)
   147  					}
   148  					break
   149  				}
   150  
   151  				logger.Infof("%v has changed", eventPath)
   152  
   153  				w.eventsChan <- event.Name
   154  
   155  			case err, ok := <-w.fileWatcher.Errors:
   156  				if !ok {
   157  					return
   158  				}
   159  				fmt.Println("error:", err)
   160  			}
   161  		}
   162  	}()
   163  
   164  	return nil
   165  }
   166  
   167  // Stop the watching process
   168  func (w *watcher) Stop() {
   169  	close(w.stopChan)
   170  }