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 }