skyview/internal/icao/database.go

119 lines
2.7 KiB
Go
Raw Normal View History

package icao
import (
"database/sql"
"embed"
"fmt"
"io"
"os"
"strconv"
_ "github.com/mattn/go-sqlite3"
)
//go:embed icao.db
var icaoFS embed.FS
// Database handles ICAO address to country lookups
type Database struct {
db *sql.DB
}
// CountryInfo represents country information for an aircraft
type CountryInfo struct {
Country string `json:"country"`
CountryCode string `json:"country_code"`
Flag string `json:"flag"`
}
// NewDatabase creates a new ICAO database connection
func NewDatabase() (*Database, error) {
// Extract embedded database to a temporary file
data, err := icaoFS.ReadFile("icao.db")
if err != nil {
return nil, fmt.Errorf("failed to read embedded ICAO database: %w", err)
}
// Create temporary file for the database
tmpFile, err := os.CreateTemp("", "icao-*.db")
if err != nil {
return nil, fmt.Errorf("failed to create temporary file: %w", err)
}
tmpPath := tmpFile.Name()
// Write database data to temporary file
if _, err := io.WriteString(tmpFile, string(data)); err != nil {
tmpFile.Close()
os.Remove(tmpPath)
return nil, fmt.Errorf("failed to write database to temp file: %w", err)
}
tmpFile.Close()
// Open SQLite database
db, err := sql.Open("sqlite3", tmpPath+"?mode=ro") // Read-only mode
if err != nil {
os.Remove(tmpPath)
return nil, fmt.Errorf("failed to open SQLite database: %w", err)
}
// Test the database connection
if err := db.Ping(); err != nil {
db.Close()
os.Remove(tmpPath)
return nil, fmt.Errorf("failed to ping database: %w", err)
}
return &Database{db: db}, nil
}
// LookupCountry returns country information for an ICAO address
func (d *Database) LookupCountry(icaoHex string) (*CountryInfo, error) {
if len(icaoHex) != 6 {
return &CountryInfo{
Country: "Unknown",
CountryCode: "XX",
Flag: "🏳️",
}, nil
}
// Convert hex string to integer
icaoInt, err := strconv.ParseInt(icaoHex, 16, 64)
if err != nil {
return &CountryInfo{
Country: "Unknown",
CountryCode: "XX",
Flag: "🏳️",
}, nil
}
var country, countryCode, flag string
query := `
SELECT country, country_code, flag
FROM icao_allocations
WHERE ? BETWEEN start_addr AND end_addr
LIMIT 1
`
err = d.db.QueryRow(query, icaoInt).Scan(&country, &countryCode, &flag)
if err != nil {
if err == sql.ErrNoRows {
return &CountryInfo{
Country: "Unknown",
CountryCode: "XX",
Flag: "🏳️",
}, nil
}
return nil, fmt.Errorf("database query failed: %w", err)
}
return &CountryInfo{
Country: country,
CountryCode: countryCode,
Flag: flag,
}, nil
}
// Close closes the database connection
func (d *Database) Close() error {
return d.db.Close()
}