github.com/amazechain/amc@v0.1.3/accounts/keystore/watch.go (about) 1 // Copyright 2023 The AmazeChain Authors 2 // This file is part of the AmazeChain library. 3 // 4 // The AmazeChain library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The AmazeChain library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the AmazeChain library. If not, see <http://www.gnu.org/licenses/>. 16 17 //go:build (darwin && !ios && cgo) || freebsd || (linux && !arm64) || netbsd || solaris 18 // +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris 19 20 package keystore 21 22 import ( 23 "github.com/fsnotify/fsnotify" 24 "time" 25 26 "github.com/amazechain/amc/log" 27 ) 28 29 type watcher struct { 30 ac *accountCache 31 running bool // set to true when runloop begins 32 runEnded bool // set to true when runloop ends 33 starting bool // set to true prior to runloop starting 34 quit chan struct{} 35 } 36 37 func newWatcher(ac *accountCache) *watcher { 38 return &watcher{ 39 ac: ac, 40 quit: make(chan struct{}), 41 } 42 } 43 44 // enabled returns false on systems not supported. 45 func (*watcher) enabled() bool { return true } 46 47 // starts the watcher loop in the background. 48 // Start a watcher in the background if that's not already in progress. 49 // The caller must hold w.ac.mu. 50 func (w *watcher) start() { 51 if w.starting || w.running { 52 return 53 } 54 w.starting = true 55 go w.loop() 56 } 57 58 func (w *watcher) close() { 59 close(w.quit) 60 } 61 62 func (w *watcher) loop() { 63 defer func() { 64 w.ac.mu.Lock() 65 w.running = false 66 w.starting = false 67 w.runEnded = true 68 w.ac.mu.Unlock() 69 }() 70 logger := log.New("path", w.ac.keydir) 71 72 // Create new watcher. 73 watcher, err := fsnotify.NewWatcher() 74 if err != nil { 75 log.Error("Failed to start filesystem watcher", "err", err) 76 return 77 } 78 defer watcher.Close() 79 if err := watcher.Add(w.ac.keydir); err != nil { 80 logger.Warn("Failed to watch keystore folder", "err", err) 81 return 82 } 83 84 logger.Trace("Started watching keystore folder", "folder", w.ac.keydir) 85 defer logger.Trace("Stopped watching keystore folder") 86 87 w.ac.mu.Lock() 88 w.running = true 89 w.ac.mu.Unlock() 90 91 // Wait for file system events and reload. 92 // When an event occurs, the reload call is delayed a bit so that 93 // multiple events arriving quickly only cause a single reload. 94 var ( 95 debounceDuration = 500 * time.Millisecond 96 rescanTriggered = false 97 debounce = time.NewTimer(0) 98 ) 99 // Ignore initial trigger 100 if !debounce.Stop() { 101 <-debounce.C 102 } 103 defer debounce.Stop() 104 for { 105 select { 106 case <-w.quit: 107 return 108 case _, ok := <-watcher.Events: 109 if !ok { 110 return 111 } 112 // Trigger the scan (with delay), if not already triggered 113 if !rescanTriggered { 114 debounce.Reset(debounceDuration) 115 rescanTriggered = true 116 } 117 // The fsnotify library does provide more granular event-info, it 118 // would be possible to refresh individual affected files instead 119 // of scheduling a full rescan. For most cases though, the 120 // full rescan is quick and obviously simplest. 121 case err, ok := <-watcher.Errors: 122 if !ok { 123 return 124 } 125 log.Info("Filsystem watcher error", "err", err) 126 case <-debounce.C: 127 w.ac.scanAccounts() 128 rescanTriggered = false 129 } 130 } 131 }