summaryrefslogtreecommitdiff
path: root/service/reload/watcher.go
diff options
context:
space:
mode:
Diffstat (limited to 'service/reload/watcher.go')
-rw-r--r--service/reload/watcher.go409
1 files changed, 0 insertions, 409 deletions
diff --git a/service/reload/watcher.go b/service/reload/watcher.go
deleted file mode 100644
index 027d2d0d..00000000
--- a/service/reload/watcher.go
+++ /dev/null
@@ -1,409 +0,0 @@
-package reload
-
-import (
- "errors"
- "io/ioutil"
- "os"
- "path/filepath"
- "sync"
- "time"
-)
-
-var ErrorSkip = errors.New("file is skipped")
-var NoWalkerConfig = errors.New("should add at least one walker config, when reload is set to true")
-
-// SimpleHook is used to filter by simple criteria, CONTAINS
-type SimpleHook func(filename string, pattern []string) error
-
-// An Event describes an event that is received when files or directory
-// changes occur. It includes the os.FileInfo of the changed file or
-// directory and the type of event that's occurred and the full path of the file.
-type Event struct {
- path string
- info os.FileInfo
-
- service string // type of service, http, grpc, etc...
-}
-
-type WatcherConfig struct {
- // service name
- serviceName string
-
- // recursive or just add by singe directory
- recursive bool
-
- // directories used per-service
- directories []string
-
- // simple hook, just CONTAINS
- filterHooks func(filename string, pattern []string) error
-
- // path to file with files
- files map[string]os.FileInfo
-
- // ignored directories, used map for O(1) amortized get
- ignored map[string]struct{}
-
- // filePatterns to ignore
- filePatterns []string
-}
-
-type Watcher struct {
- // main event channel
- Event chan Event
- close chan struct{}
-
- //=============================
- mu *sync.Mutex
-
- // indicates is walker started or not
- started bool
-
- // config for each service
- // need pointer here to assign files
- watcherConfigs map[string]WatcherConfig
-}
-
-// Options is used to set Watcher Options
-type Options func(*Watcher)
-
-// NewWatcher returns new instance of File Watcher
-func NewWatcher(configs []WatcherConfig, options ...Options) (*Watcher, error) {
- w := &Watcher{
- Event: make(chan Event),
- mu: &sync.Mutex{},
-
- close: make(chan struct{}),
-
- //workingDir: workDir,
- watcherConfigs: make(map[string]WatcherConfig),
- }
-
- // add watcherConfigs by service names
- for _, v := range configs {
- w.watcherConfigs[v.serviceName] = v
- }
-
- // apply options
- for _, option := range options {
- option(w)
- }
- err := w.initFs()
- if err != nil {
- return nil, err
- }
-
- return w, nil
-}
-
-// initFs makes initial map with files
-func (w *Watcher) initFs() error {
- for srvName, config := range w.watcherConfigs {
- fileList, err := w.retrieveFileList(srvName, config)
- if err != nil {
- return err
- }
- // workaround. in golang you can't assign to map in struct field
- tmp := w.watcherConfigs[srvName]
- tmp.files = fileList
- w.watcherConfigs[srvName] = tmp
- }
- return nil
-}
-
-// ConvertIgnored is used to convert slice to map with ignored files
-func ConvertIgnored(ignored []string) (map[string]struct{}, error) {
- if len(ignored) == 0 {
- return nil, nil
- }
-
- ign := make(map[string]struct{}, len(ignored))
- for i := 0; i < len(ignored); i++ {
- abs, err := filepath.Abs(ignored[i])
- if err != nil {
- return nil, err
- }
- ign[abs] = struct{}{}
- }
-
- return ign, nil
-
-}
-
-// GetAllFiles returns all files initialized for particular company
-func (w *Watcher) GetAllFiles(serviceName string) []os.FileInfo {
- var ret []os.FileInfo
-
- for _, v := range w.watcherConfigs[serviceName].files {
- ret = append(ret, v)
- }
-
- return ret
-}
-
-// https://en.wikipedia.org/wiki/Inotify
-// SetMaxFileEvents sets max file notify events for Watcher
-// In case of file watch errors, this value can be increased system-wide
-// For linux: set --> fs.inotify.max_user_watches = 600000 (under /etc/<choose_name_here>.conf)
-// Add apply: sudo sysctl -p --system
-//func SetMaxFileEvents(events int) Options {
-// return func(watcher *Watcher) {
-// watcher.maxFileWatchEvents = events
-// }
-//
-//}
-
-// pass map from outside
-func (w *Watcher) retrieveFilesSingle(serviceName, path string) (map[string]os.FileInfo, error) {
- stat, err := os.Stat(path)
- if err != nil {
- return nil, err
- }
-
- filesList := make(map[string]os.FileInfo, 10)
- filesList[path] = stat
-
- // if it's not a dir, return
- if !stat.IsDir() {
- return filesList, nil
- }
-
- fileInfoList, err := ioutil.ReadDir(path)
- if err != nil {
- return nil, err
- }
-
- // recursive calls are slow in compare to goto
- // so, we will add files with goto pattern
-outer:
- for i := 0; i < len(fileInfoList); i++ {
- var pathToFile string
- // BCE check elimination
- // https://go101.org/article/bounds-check-elimination.html
- if len(fileInfoList) != 0 && len(fileInfoList) >= i {
- pathToFile = filepath.Join(pathToFile, fileInfoList[i].Name())
- } else {
- return nil, errors.New("file info list len")
- }
-
- // if file in ignored --> continue
- if _, ignored := w.watcherConfigs[serviceName].ignored[path]; ignored {
- continue
- }
-
- // if filename does not contain pattern --> ignore that file
- if w.watcherConfigs[serviceName].filePatterns != nil && w.watcherConfigs[serviceName].filterHooks != nil {
- err = w.watcherConfigs[serviceName].filterHooks(fileInfoList[i].Name(), w.watcherConfigs[serviceName].filePatterns)
- if err == ErrorSkip {
- continue outer
- }
- }
-
- filesList[pathToFile] = fileInfoList[i]
- }
-
- return filesList, nil
-}
-
-func (w *Watcher) StartPolling(duration time.Duration) error {
- w.mu.Lock()
- if w.started {
- w.mu.Unlock()
- return errors.New("already started")
- }
-
- w.started = true
- w.mu.Unlock()
-
- return w.waitEvent(duration)
-}
-
-// this is blocking operation
-func (w *Watcher) waitEvent(d time.Duration) error {
- ticker := time.NewTicker(d)
- for {
- select {
- case <-w.close:
- ticker.Stop()
- // just exit
- // no matter for the pollEvents
- return nil
- case <-ticker.C:
- // this is not very effective way
- // because we have to wait on Lock
- // better is to listen files in parallel, but, since that would be used in debug... TODO
- for serviceName, config := range w.watcherConfigs {
- go func(sn string, c WatcherConfig) {
- fileList, _ := w.retrieveFileList(sn, c)
- w.pollEvents(c.serviceName, fileList)
- }(serviceName, config)
- }
- }
- }
-
-}
-
-// retrieveFileList get file list for service
-func (w *Watcher) retrieveFileList(serviceName string, config WatcherConfig) (map[string]os.FileInfo, error) {
- w.mu.Lock()
- defer w.mu.Unlock()
- fileList := make(map[string]os.FileInfo)
- if config.recursive {
- // walk through directories recursively
- for _, dir := range config.directories {
- // full path is workdir/relative_path
- fullPath, err := filepath.Abs(dir)
- if err != nil {
- return nil, err
- }
- list, err := w.retrieveFilesRecursive(serviceName, fullPath)
- if err != nil {
- return nil, err
- }
-
- for k, v := range list {
- fileList[k] = v
- }
- }
- return fileList, nil
- }
-
- for _, dir := range config.directories {
- // full path is workdir/relative_path
- fullPath, err := filepath.Abs(dir)
- if err != nil {
- return nil, err
- }
-
- // list is pathToFiles with files
- list, err := w.retrieveFilesSingle(serviceName, fullPath)
- if err != nil {
- return nil, err
- }
-
- for pathToFile, file := range list {
- fileList[pathToFile] = file
- }
- }
-
- return fileList, nil
-}
-
-func (w *Watcher) retrieveFilesRecursive(serviceName, root string) (map[string]os.FileInfo, error) {
- fileList := make(map[string]os.FileInfo)
-
- return fileList, filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
-
- // If path is ignored and it's a directory, skip the directory. If it's
- // ignored and it's a single file, skip the file.
- _, ignored := w.watcherConfigs[serviceName].ignored[path]
- if ignored {
- if info.IsDir() {
- // if it's dir, ignore whole
- return filepath.SkipDir
- }
- return nil
- }
-
- // if filename does not contain pattern --> ignore that file
- err = w.watcherConfigs[serviceName].filterHooks(info.Name(), w.watcherConfigs[serviceName].filePatterns)
- if err == ErrorSkip {
- return nil
- }
-
- // Add the path and it's info to the file list.
- fileList[path] = info
- return nil
- })
-}
-
-func (w *Watcher) pollEvents(serviceName string, files map[string]os.FileInfo) {
- w.mu.Lock()
- defer w.mu.Unlock()
-
- // Store create and remove events for use to check for rename events.
- creates := make(map[string]os.FileInfo)
- removes := make(map[string]os.FileInfo)
-
- // Check for removed files.
- for pth, info := range w.watcherConfigs[serviceName].files {
- if _, found := files[pth]; !found {
- removes[pth] = info
- }
- }
-
- // Check for created files, writes and chmods.
- for pth, info := range files {
- if info.IsDir() {
- continue
- }
- oldInfo, found := w.watcherConfigs[serviceName].files[pth]
- if !found {
- // A file was created.
- creates[pth] = info
- continue
- }
- if oldInfo.ModTime() != info.ModTime() {
- w.watcherConfigs[serviceName].files[pth] = info
- w.Event <- Event{
- path: pth,
- info: info,
- service: serviceName,
- }
- }
- if oldInfo.Mode() != info.Mode() {
- w.watcherConfigs[serviceName].files[pth] = info
- w.Event <- Event{
- path: pth,
- info: info,
- service: serviceName,
- }
- }
- }
-
- //Check for renames and moves.
- for path1, info1 := range removes {
- for path2, info2 := range creates {
- if sameFile(info1, info2) {
- e := Event{
- path: path2,
- info: info2,
- service: serviceName,
- }
-
- // remove initial path
- delete(w.watcherConfigs[serviceName].files, path1)
- // update with new
- w.watcherConfigs[serviceName].files[path2] = info2
-
-
- w.Event <- e
- }
- }
- }
-
- //Send all the remaining create and remove events.
- for pth, info := range creates {
- w.watcherConfigs[serviceName].files[pth] = info
- w.Event <- Event{
- path: pth,
- info: info,
- service: serviceName,
- }
- }
- for pth, info := range removes {
- delete(w.watcherConfigs[serviceName].files, pth)
- w.Event <- Event{
- path: pth,
- info: info,
- service: serviceName,
- }
- }
-}
-
-func (w *Watcher) Stop() {
- w.close <- struct{}{}
-}