diff --git a/net/geoip/databases.go b/net/geoip/databases.go index 7a918a3..caa22f0 100644 --- a/net/geoip/databases.go +++ b/net/geoip/databases.go @@ -9,6 +9,7 @@ import ( "net/netip" "os" "path/filepath" + "slices" "strings" "github.com/oschwald/geoip2-golang" @@ -20,15 +21,26 @@ type Databases struct { ASN *geoip2.Reader } -// Open reads /_LATEST.tar.gz for City and ASN editions, -// extracts the .mmdb entry from each archive in memory, and returns open -// readers. No .mmdb files are written to disk. +// Open loads the City and ASN editions from dir. For each edition it +// prefers _LATEST.tar.gz and falls back to the +// lexicographically greatest _*.tar.gz match (MaxMind's +// Content-Disposition names sort chronologically by release date). +// Archives are extracted in memory — no .mmdb files are written to disk. func Open(dir string) (*Databases, error) { - city, err := openMMDBTarGz(filepath.Join(dir, TarGzName(CityEdition))) + cityPath, err := FindTarGz(dir, CityEdition) if err != nil { return nil, fmt.Errorf("city: %w", err) } - asn, err := openMMDBTarGz(filepath.Join(dir, TarGzName(ASNEdition))) + city, err := openMMDBTarGz(cityPath) + if err != nil { + return nil, fmt.Errorf("city: %w", err) + } + asnPath, err := FindTarGz(dir, ASNEdition) + if err != nil { + _ = city.Close() + return nil, fmt.Errorf("asn: %w", err) + } + asn, err := openMMDBTarGz(asnPath) if err != nil { _ = city.Close() return nil, fmt.Errorf("asn: %w", err) @@ -36,6 +48,25 @@ func Open(dir string) (*Databases, error) { return &Databases{City: city, ASN: asn}, nil } +// FindTarGz resolves the cached tarball path for edition inside dir, +// preferring _LATEST.tar.gz and falling back to the +// lexicographically greatest _*.tar.gz match. +func FindTarGz(dir, edition string) (string, error) { + preferred := filepath.Join(dir, TarGzName(edition)) + if _, err := os.Stat(preferred); err == nil { + return preferred, nil + } + matches, err := filepath.Glob(filepath.Join(dir, edition+"_*.tar.gz")) + if err != nil { + return "", err + } + if len(matches) == 0 { + return "", fmt.Errorf("no %s_*.tar.gz in %s", edition, dir) + } + slices.Sort(matches) + return matches[len(matches)-1], nil +} + func openMMDBTarGz(path string) (*geoip2.Reader, error) { f, err := os.Open(path) if err != nil {