wttr.in/internal/geo/ip/ip.go
2022-12-04 16:48:04 +01:00

99 lines
2.1 KiB
Go

package ip
import (
"errors"
"os"
"path"
"strconv"
"strings"
"github.com/chubin/wttr.in/internal/config"
)
var (
ErrNotFound = errors.New("cache entry not found")
ErrInvalidCacheEntry = errors.New("invalid cache entry format")
)
// Location information.
type Location struct {
CountryCode string
Country string
Region string
City string
Latitude float64
Longitude float64
}
// Cache provides access to the IP Geodata cache.
type Cache struct {
config *config.Config
}
// NewCache returns new cache reader for the specified config.
func NewCache(config *config.Config) *Cache {
return &Cache{
config: config,
}
}
// Read returns location information from the cache, if found,
// or ErrNotFound if not found. If the entry is found, but its format
// is invalid, ErrInvalidCacheEntry is returned.
//
// Format:
//
// [CountryCode];Country;Region;City;[Latitude];[Longitude]
//
// Example:
//
// DE;Germany;Free and Hanseatic City of Hamburg;Hamburg;53.5736;9.9782
//
func (c *Cache) Read(addr string) (*Location, error) {
bytes, err := os.ReadFile(c.cacheFile(addr))
if err != nil {
return nil, ErrNotFound
}
return parseCacheEntry(string(bytes))
}
// cacheFile retuns path to the cache entry for addr.
func (c *Cache) cacheFile(addr string) string {
return path.Join(c.config.Geo.IPCache, addr)
}
// parseCacheEntry parses the location cache entry s,
// and return location, or error, if the cache entry is invalid.
func parseCacheEntry(s string) (*Location, error) {
var (
lat float64 = -1000
long float64 = -1000
err error
)
parts := strings.Split(s, ";")
if len(parts) < 4 {
return nil, ErrInvalidCacheEntry
}
if len(parts) >= 6 {
lat, err = strconv.ParseFloat(parts[4], 64)
if err != nil {
return nil, ErrInvalidCacheEntry
}
long, err = strconv.ParseFloat(parts[5], 64)
if err != nil {
return nil, ErrInvalidCacheEntry
}
}
return &Location{
CountryCode: parts[0],
Country: parts[1],
Region: parts[2],
City: parts[3],
Latitude: lat,
Longitude: long,
}, nil
}