From a713485599d5f7fc870538e8fbc6d205b35e7914 Mon Sep 17 00:00:00 2001 From: bbedward Date: Fri, 7 Aug 2026 19:54:21 -0400 Subject: [PATCH] fix(geolocation): fetch IP location lazily so startup never contacts ip-api.com fixes #3008 port 1.5 --- core/internal/geolocation/client.go | 32 +----------------- core/internal/geolocation/client_geoclue.go | 28 ++++++++++++---- core/internal/server/location/manager.go | 37 +++++++++++---------- core/internal/server/location/types.go | 3 +- 4 files changed, 45 insertions(+), 55 deletions(-) diff --git a/core/internal/geolocation/client.go b/core/internal/geolocation/client.go index 5b441215d..fbf04a935 100644 --- a/core/internal/geolocation/client.go +++ b/core/internal/geolocation/client.go @@ -6,37 +6,7 @@ func NewClient() Client { geoclueClient, err := newGeoClueClient() if err != nil { log.Warnf("GeoClue2 unavailable: %v", err) - return newSeededIpClient() + return newIpClient() } - - loc, _ := geoclueClient.GetLocation() - if loc.Latitude != 0 || loc.Longitude != 0 { - log.Info("Using GeoClue2 location") - return geoclueClient - } - - log.Info("GeoClue2 has no fix yet, seeding with IP location") - ipLoc, err := fetchIPLocation() - if err != nil { - log.Warnf("IP location seed failed: %v", err) - return geoclueClient - } - - log.Info("Seeded GeoClue2 with IP location") - geoclueClient.SeedLocation(Location{Latitude: ipLoc.Latitude, Longitude: ipLoc.Longitude}) return geoclueClient } - -func newSeededIpClient() *IpClient { - client := newIpClient() - ipLoc, err := fetchIPLocation() - if err != nil { - log.Warnf("IP location also failed: %v", err) - return client - } - - log.Info("Using IP location") - client.currLocation.Latitude = ipLoc.Latitude - client.currLocation.Longitude = ipLoc.Longitude - return client -} diff --git a/core/internal/geolocation/client_geoclue.go b/core/internal/geolocation/client_geoclue.go index b34c547c2..b6c2eaa72 100644 --- a/core/internal/geolocation/client_geoclue.go +++ b/core/internal/geolocation/client_geoclue.go @@ -34,6 +34,7 @@ const ( type GeoClueClient struct { currLocation *Location locationMutex sync.RWMutex + seedOnce sync.Once dbusConn *dbus.Conn clientPath dbus.ObjectPath @@ -230,14 +231,29 @@ func (c *GeoClueClient) SeedLocation(loc Location) { } func (c *GeoClueClient) GetLocation() (Location, error) { + loc := c.currentLocation() + if loc.Latitude != 0 || loc.Longitude != 0 { + return loc, nil + } + + c.seedOnce.Do(func() { + ipLoc, err := fetchIPLocation() + if err != nil { + log.Warnf("GeoClue2 has no fix, IP location seed failed: %v", err) + return + } + log.Info("Seeded GeoClue2 with IP location") + c.SeedLocation(Location{Latitude: ipLoc.Latitude, Longitude: ipLoc.Longitude}) + }) + + return c.currentLocation(), nil +} + +func (c *GeoClueClient) currentLocation() Location { c.locationMutex.RLock() defer c.locationMutex.RUnlock() if c.currLocation == nil { - return Location{ - Latitude: 0.0, - Longitude: 0.0, - }, nil + return Location{} } - stateCopy := *c.currLocation - return stateCopy, nil + return *c.currLocation } diff --git a/core/internal/server/location/manager.go b/core/internal/server/location/manager.go index 24bcd957f..6f9d33c1c 100644 --- a/core/internal/server/location/manager.go +++ b/core/internal/server/location/manager.go @@ -8,24 +8,11 @@ import ( ) func NewManager(client geolocation.Client) (*Manager, error) { - currLocation, err := client.GetLocation() - if err != nil { - log.Warnf("Failed to get initial location: %v", err) - } - m := &Manager{ client: client, dirty: make(chan struct{}), stopChan: make(chan struct{}), - - state: &State{ - Latitude: currLocation.Latitude, - Longitude: currLocation.Longitude, - }, - } - - if err := m.startSignalPump(); err != nil { - return nil, err + state: &State{}, } m.notifierWg.Add(1) @@ -34,6 +21,22 @@ func NewManager(client geolocation.Client) (*Manager, error) { return m, nil } +// The geolocation client may fetch IP location on first use, so nothing +// touches it until a consumer actually asks for location data. +func (m *Manager) ensureStarted() { + m.startOnce.Do(func() { + go func() { + currLocation, err := m.client.GetLocation() + if err != nil { + log.Warnf("Failed to get initial location: %v", err) + } else { + m.handleLocationChange(currLocation) + } + m.startSignalPump() + }() + }) +} + func (m *Manager) Close() { close(m.stopChan) m.notifierWg.Wait() @@ -48,6 +51,7 @@ func (m *Manager) Close() { } func (m *Manager) Subscribe(id string) chan State { + m.ensureStarted() ch := make(chan State, 64) m.subscribers.Store(id, ch) return ch @@ -59,7 +63,7 @@ func (m *Manager) Unsubscribe(id string) { } } -func (m *Manager) startSignalPump() error { +func (m *Manager) startSignalPump() { m.sigWG.Add(1) go func() { defer m.sigWG.Done() @@ -80,8 +84,6 @@ func (m *Manager) startSignalPump() error { } } }() - - return nil } func (m *Manager) handleLocationChange(location geolocation.Location) { @@ -102,6 +104,7 @@ func (m *Manager) notifySubscribers() { } func (m *Manager) GetState() State { + m.ensureStarted() m.stateMutex.RLock() defer m.stateMutex.RUnlock() if m.state == nil { diff --git a/core/internal/server/location/types.go b/core/internal/server/location/types.go index f18198738..c2407a7c3 100644 --- a/core/internal/server/location/types.go +++ b/core/internal/server/location/types.go @@ -16,7 +16,8 @@ type Manager struct { state *State stateMutex sync.RWMutex - client geolocation.Client + client geolocation.Client + startOnce sync.Once stopChan chan struct{} sigWG sync.WaitGroup