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  }