github.com/diadata-org/diadata@v1.4.593/internal/pkg/ratescrapers/RateScraper.go (about) 1 package ratescrapers 2 3 import ( 4 "errors" 5 "sync" 6 "time" 7 8 models "github.com/diadata-org/diadata/pkg/model" 9 log "github.com/sirupsen/logrus" 10 ) 11 12 const ( 13 // Determine frequency of scraping 14 refreshDelay = time.Second * 10 * 1 15 ) 16 17 type nothing struct{} 18 19 type RateScraper struct { 20 // signaling channels 21 shutdown chan nothing 22 shutdownDone chan nothing 23 24 // error handling; to read error or closed, first acquire read lock 25 // only cleanup method should hold write lock 26 errorLock sync.RWMutex 27 error error 28 closed bool 29 ticker *time.Ticker 30 datastore models.Datastore 31 chanInterestRate chan *models.InterestRate 32 } 33 34 // SpawnRateScraper returns a new RateScraper initialized with default values. 35 // The instance is asynchronously scraping as soon as it is created. 36 func SpawnRateScraper(datastore models.Datastore, rateType string) *RateScraper { 37 s := &RateScraper{ 38 shutdown: make(chan nothing), 39 shutdownDone: make(chan nothing), 40 error: nil, 41 ticker: time.NewTicker(refreshDelay), 42 datastore: datastore, 43 chanInterestRate: make(chan *models.InterestRate), 44 } 45 46 log.Info("Rate scraper is built and triggered") 47 go s.mainLoop(rateType) 48 return s 49 } 50 51 // mainLoop runs in a goroutine until channel s is closed. 52 func (s *RateScraper) mainLoop(rateType string) { 53 for { 54 select { 55 case <-s.ticker.C: 56 err := s.Update(rateType) 57 if err != nil { 58 log.Error(err) 59 } 60 case <-s.shutdown: // user requested shutdown 61 log.Println("RateScraper shutting down") 62 s.cleanup(nil) 63 return 64 } 65 } 66 } 67 68 // closes all connected Scrapers. Must only be called from mainLoop 69 func (s *RateScraper) cleanup(err error) { 70 71 s.errorLock.Lock() 72 defer s.errorLock.Unlock() 73 74 s.ticker.Stop() 75 76 if err != nil { 77 s.error = err 78 } 79 s.closed = true 80 81 close(s.shutdownDone) // signal that shutdown is complete 82 } 83 84 // Close closes any existing API connections 85 func (s *RateScraper) Close() error { 86 if s.closed { 87 return errors.New("RateScraper: Already closed") 88 } 89 close(s.shutdown) 90 <-s.shutdownDone 91 s.errorLock.RLock() 92 defer s.errorLock.RUnlock() 93 return s.error 94 } 95 96 // Channel returns a channel that can be used to receive rate information 97 func (s *RateScraper) Channel() chan *models.InterestRate { 98 return s.chanInterestRate 99 } 100 101 // Update calls the appropriate function corresponding to the rate type. 102 func (s *RateScraper) Update(rateType string) error { 103 switch rateType { 104 case "ESTER": 105 return s.UpdateESTER() 106 case "SOFR": 107 return s.UpdateSOFR() 108 case "SAFR": 109 return s.UpdateSAFR() 110 case "SAFR-AVGS": 111 // This method/flag comprises the rates SAFR30, SAFR90 and SAFR180. 112 return s.UpdateSAFRAvgs() 113 case "SONIA": 114 return s.UpdateSonia() 115 } 116 return errors.New("Error: " + rateType + " does not exist in database") 117 }