github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/core/location/cache.go (about)

     1  /*
     2   * Copyright (C) 2017 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU 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   * This program 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 General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package location
    19  
    20  import (
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/rs/zerolog/log"
    25  
    26  	"github.com/mysteriumnetwork/node/core/connection/connectionstate"
    27  	"github.com/mysteriumnetwork/node/core/location/locationstate"
    28  	nodevent "github.com/mysteriumnetwork/node/core/node/event"
    29  )
    30  
    31  // Cache allows us to cache location resolution
    32  type Cache struct {
    33  	lastFetched      time.Time
    34  	locationDetector Resolver
    35  	location         locationstate.Location
    36  	origin           locationstate.Location
    37  	expiry           time.Duration
    38  	pub              publisher
    39  	lock             sync.Mutex
    40  }
    41  
    42  type publisher interface {
    43  	Publish(topic string, data interface{})
    44  }
    45  
    46  // LocUpdateEvent is the event type used to sending or receiving event updates
    47  const LocUpdateEvent string = "location-update-event"
    48  
    49  // NewCache returns a new instance of location cache
    50  func NewCache(resolver Resolver, pub publisher, expiry time.Duration) *Cache {
    51  	return &Cache{
    52  		locationDetector: resolver,
    53  		expiry:           expiry,
    54  		pub:              pub,
    55  	}
    56  }
    57  
    58  func (c *Cache) needsRefresh() bool {
    59  	return c.lastFetched.IsZero() || c.lastFetched.Before(time.Now().Add(-c.expiry))
    60  }
    61  
    62  func (c *Cache) fetchAndSave() (locationstate.Location, error) {
    63  	loc, err := c.locationDetector.DetectLocation()
    64  
    65  	// on successful fetch save the values for further use
    66  	if err == nil {
    67  		ip := loc.IP
    68  		// avoid printing IP address in logs
    69  		loc.IP = ""
    70  		c.pub.Publish(LocUpdateEvent, loc)
    71  		loc.IP = ip
    72  		c.location = loc
    73  		c.lastFetched = time.Now()
    74  	}
    75  
    76  	return loc, err
    77  }
    78  
    79  // GetOrigin returns the origin for the user - a location that's not modified by starting services.
    80  func (c *Cache) GetOrigin() locationstate.Location {
    81  	c.lock.Lock()
    82  	defer c.lock.Unlock()
    83  	return c.origin
    84  }
    85  
    86  // DetectLocation returns location from cache, or fetches it if needed
    87  func (c *Cache) DetectLocation() (locationstate.Location, error) {
    88  	c.lock.Lock()
    89  	defer c.lock.Unlock()
    90  
    91  	if !c.needsRefresh() {
    92  		return c.location, nil
    93  	}
    94  
    95  	return c.fetchAndSave()
    96  }
    97  
    98  // DetectProxyLocation returns the proxy location.
    99  func (c *Cache) DetectProxyLocation(proxyPort int) (locationstate.Location, error) {
   100  	return c.locationDetector.DetectProxyLocation(proxyPort)
   101  }
   102  
   103  // HandleConnectionEvent handles connection state change and fetches the location info accordingly.
   104  // On the consumer side, we'll need to re-fetch the location once the user is connected or disconnected from a service.
   105  func (c *Cache) HandleConnectionEvent(se connectionstate.AppEventConnectionState) {
   106  	c.lock.Lock()
   107  	defer c.lock.Unlock()
   108  	if se.State != connectionstate.Connected && se.State != connectionstate.NotConnected {
   109  		return
   110  	}
   111  
   112  	_, err := c.fetchAndSave()
   113  	if err != nil {
   114  		log.Error().Err(err).Msg("Location update failed")
   115  		// reset time so a fetch is tried the next time a get is called
   116  		c.lastFetched = time.Time{}
   117  	}
   118  }
   119  
   120  // HandleNodeEvent handles node state change and fetches the location info accordingly.
   121  func (c *Cache) HandleNodeEvent(se nodevent.Payload) {
   122  	c.lock.Lock()
   123  	defer c.lock.Unlock()
   124  	if se.Status != nodevent.StatusStarted {
   125  		return
   126  	}
   127  
   128  	var err error
   129  	c.origin, err = c.locationDetector.DetectLocation()
   130  	if err != nil {
   131  		log.Warn().Err(err).Msg("Failed to detect original location")
   132  	} else {
   133  		log.Debug().Msgf("original location detected: %s (%s)", c.origin.Country, c.origin.IPType)
   134  	}
   135  }