github.com/kaydxh/golang@v0.0.131/pkg/fsnotify/fsnotify.go (about)

     1  package fsnotify
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"sync"
     9  	"sync/atomic"
    10  
    11  	"github.com/fsnotify/fsnotify"
    12  	"github.com/kaydxh/golang/go/errors"
    13  	os_ "github.com/kaydxh/golang/go/os"
    14  	"github.com/sirupsen/logrus"
    15  )
    16  
    17  type EventCallbackFunc func(ctx context.Context, path string)
    18  
    19  type FsnotifyOptions struct {
    20  	CreateCallbackFunc EventCallbackFunc
    21  	WriteCallbackFunc  EventCallbackFunc
    22  	RemoveCallbackFunc EventCallbackFunc
    23  }
    24  
    25  type FsnotifyService struct {
    26  	watcher *fsnotify.Watcher
    27  	paths   []string
    28  
    29  	opts       FsnotifyOptions
    30  	inShutdown atomic.Bool
    31  	mu         sync.Mutex
    32  	cancel     func()
    33  }
    34  
    35  // paths can also be dir or file or both of them
    36  func NewFsnotifyService(paths []string, opts ...FsnotifyOption) (*FsnotifyService, error) {
    37  	watcher, err := fsnotify.NewWatcher()
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	if len(paths) == 0 {
    43  		return nil, fmt.Errorf("paths is empty")
    44  	}
    45  
    46  	for _, path := range paths {
    47  		ok, err := os_.PathExist(path)
    48  		if err != nil {
    49  			return nil, err
    50  		}
    51  		if !ok {
    52  			return nil, fmt.Errorf("%s is not exist", path)
    53  		}
    54  	}
    55  
    56  	fs := &FsnotifyService{
    57  		watcher: watcher,
    58  		paths:   paths,
    59  	}
    60  	fs.ApplyOptions(opts...)
    61  
    62  	return fs, nil
    63  }
    64  
    65  func (srv *FsnotifyService) logger() logrus.FieldLogger {
    66  	return logrus.WithField("module", "FsnotifyService")
    67  }
    68  
    69  func (srv *FsnotifyService) Run(ctx context.Context) error {
    70  	logger := srv.logger()
    71  	logger.Infoln("FsnotifyService Run")
    72  	if srv.inShutdown.Load() {
    73  		logger.Infoln("FsnotifyService Shutdown")
    74  		return fmt.Errorf("server closed")
    75  	}
    76  	go func() {
    77  		errors.HandleError(srv.Serve(ctx))
    78  	}()
    79  	return nil
    80  }
    81  
    82  func (srv *FsnotifyService) Serve(ctx context.Context) error {
    83  	logger := srv.logger()
    84  	logger.Infoln("FsnotifyService Serve")
    85  
    86  	if srv.inShutdown.Load() {
    87  		err := fmt.Errorf("server closed")
    88  		logger.WithError(err).Errorf("FsnotifyService Serve canceled")
    89  		return err
    90  	}
    91  
    92  	defer srv.inShutdown.Store(true)
    93  	ctx, cancel := context.WithCancel(ctx)
    94  	srv.mu.Lock()
    95  	srv.cancel = cancel
    96  	srv.mu.Unlock()
    97  
    98  	defer func() {
    99  		err := srv.watcher.Close()
   100  		if err != nil {
   101  			logger.WithError(err).Errorf("failed to close FsnotifyService watcher")
   102  		}
   103  	}()
   104  
   105  	err := srv.AddWatchPaths(false, srv.paths...)
   106  	if err != nil {
   107  		logger.WithError(err).Errorf("failed to add watcher for path: %v", srv.paths)
   108  		return err
   109  	}
   110  
   111  	for {
   112  		select {
   113  		case ev, ok := <-srv.watcher.Events:
   114  			if !ok {
   115  				continue
   116  			}
   117  
   118  			if ev.Op&fsnotify.Create != 0 {
   119  				logger.Infof("%s happen create event", ev.Name)
   120  				srv.AddWatchPaths(false, ev.Name)
   121  				if srv.opts.CreateCallbackFunc != nil {
   122  					srv.opts.CreateCallbackFunc(ctx, ev.Name)
   123  				}
   124  			}
   125  			if ev.Op&fsnotify.Write != 0 {
   126  				logger.Infof("%s happen write event", ev.Name)
   127  				srv.AddWatchPaths(false, ev.Name)
   128  				if srv.opts.WriteCallbackFunc != nil {
   129  					srv.opts.WriteCallbackFunc(ctx, ev.Name)
   130  				}
   131  			}
   132  			if ev.Op&fsnotify.Remove != 0 {
   133  				logger.Infof("%s happen remove event", ev.Name)
   134  				srv.AddWatchPaths(true, ev.Name)
   135  				if srv.opts.RemoveCallbackFunc != nil {
   136  					srv.opts.RemoveCallbackFunc(ctx, ev.Name)
   137  				}
   138  			}
   139  			if ev.Op&fsnotify.Rename != 0 {
   140  				logger.Infof("%s happen rename event", ev.Name)
   141  			}
   142  			if ev.Op&fsnotify.Chmod != 0 {
   143  				logger.Infof("%s happen chmod event", ev.Name)
   144  			}
   145  
   146  		case <-ctx.Done():
   147  			logger.WithError(ctx.Err()).Errorf("server canceld")
   148  			return ctx.Err()
   149  		}
   150  	}
   151  }
   152  
   153  // Add starts watching the named directory (support recursively).
   154  func (srv *FsnotifyService) AddWatchPath(unWatch bool, path string) error {
   155  	logger := srv.logger()
   156  
   157  	ok, err := os_.IsDir(path)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	if ok {
   163  		return filepath.Walk(path, func(walkPath string, fi os.FileInfo, err error) error {
   164  			if err != nil {
   165  				logger.WithError(err).Errorf("failed to walk dir: %v", walkPath)
   166  				return err
   167  			}
   168  
   169  			if fi.IsDir() {
   170  				return srv.Add(unWatch, walkPath)
   171  			}
   172  			return nil
   173  		})
   174  	} else {
   175  		return srv.Add(unWatch, path)
   176  	}
   177  }
   178  
   179  // Add starts watching the named directory (non-recursively).
   180  func (srv *FsnotifyService) Add(unWatch bool, path string) (err error) {
   181  	logger := srv.logger()
   182  	if unWatch {
   183  		err = srv.watcher.Remove(path)
   184  	} else {
   185  		err = srv.watcher.Add(path)
   186  	}
   187  	if err != nil {
   188  		logger.WithError(err).Errorf("failed to add watcher for path: %v, ", path)
   189  		return err
   190  	}
   191  	logger.Infof("add watcher for path: %v", path)
   192  
   193  	return nil
   194  }
   195  
   196  func (srv *FsnotifyService) AddWatchPaths(unWatch bool, paths ...string) error {
   197  	for _, path := range paths {
   198  		err := srv.AddWatchPath(unWatch, path)
   199  		if err != nil {
   200  			return err
   201  		}
   202  	}
   203  
   204  	return nil
   205  }
   206  
   207  func (srv *FsnotifyService) Shutdown() {
   208  	srv.inShutdown.Store(true)
   209  	srv.mu.Lock()
   210  	defer srv.mu.Unlock()
   211  	if srv.cancel != nil {
   212  		srv.cancel()
   213  	}
   214  }