mirror of
https://github.com/renorris/openfsd
synced 2026-03-22 14:35:36 +08:00
123 lines
2.7 KiB
Go
123 lines
2.7 KiB
Go
package fsd
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
type metarService struct {
|
|
numWorkers int
|
|
httpClient *http.Client
|
|
metarRequests chan metarRequest
|
|
}
|
|
|
|
type metarRequest struct {
|
|
client *Client
|
|
icaoCode string
|
|
}
|
|
|
|
func newMetarService(numWorkers int) *metarService {
|
|
return &metarService{
|
|
numWorkers: numWorkers,
|
|
httpClient: &http.Client{},
|
|
metarRequests: make(chan metarRequest, 128),
|
|
}
|
|
}
|
|
|
|
func (s *metarService) run(ctx context.Context) {
|
|
for range s.numWorkers {
|
|
go s.worker(ctx)
|
|
}
|
|
}
|
|
|
|
func (s *metarService) worker(ctx context.Context) {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case req := <-s.metarRequests:
|
|
s.handleMetarRequest(&req)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *metarService) handleMetarRequest(req *metarRequest) {
|
|
url := buildMetarRequestURL(req.icaoCode)
|
|
res, err := s.httpClient.Get(url)
|
|
if err != nil {
|
|
sendMetarServiceError(req)
|
|
return
|
|
}
|
|
if res.StatusCode != http.StatusOK {
|
|
sendMetarServiceError(req)
|
|
return
|
|
}
|
|
|
|
bufBytes := make([]byte, 4096)
|
|
buf := bytes.NewBuffer(bufBytes)
|
|
if _, err = io.Copy(buf, res.Body); err != nil {
|
|
sendMetarServiceError(req)
|
|
return
|
|
}
|
|
|
|
resBody := buf.Bytes()
|
|
|
|
if bytes.Count(resBody, []byte("\n")) != 2 {
|
|
fmt.Println("NOAA METAR response was invalid")
|
|
sendMetarServiceError(req)
|
|
return
|
|
}
|
|
|
|
// First line is timestamp
|
|
resBody = resBody[bytes.IndexByte(resBody, '\n')+1:]
|
|
|
|
// Second line is METAR and ends with \n
|
|
resBody = resBody[:bytes.IndexByte(resBody, '\n')+1]
|
|
|
|
packet := buildMetarResponsePacket(req.client.callsign, resBody)
|
|
req.client.send(packet)
|
|
}
|
|
|
|
func buildMetarResponsePacket(callsign string, metar []byte) string {
|
|
packet := strings.Builder{}
|
|
packet.WriteString("$ARSERVER:")
|
|
packet.WriteString(callsign)
|
|
packet.WriteString(":METAR:")
|
|
packet.Write(metar)
|
|
packet.WriteString("\r\n")
|
|
return packet.String()
|
|
}
|
|
|
|
func buildMetarRequestURL(icaoCode string) string {
|
|
url := strings.Builder{}
|
|
url.WriteString("https://tgftp.nws.noaa.gov/data/observations/metar/stations/")
|
|
url.WriteString(icaoCode)
|
|
url.WriteString(".TXT")
|
|
return url.String()
|
|
}
|
|
|
|
func sendMetarServiceError(req *metarRequest) {
|
|
req.client.sendError(NoWeatherProfileError, metarServiceErrString(req.icaoCode))
|
|
}
|
|
|
|
func metarServiceErrString(icaoCode string) string {
|
|
msg := strings.Builder{}
|
|
msg.WriteString("Error fetching METAR for ")
|
|
msg.WriteString(icaoCode)
|
|
|
|
return msg.String()
|
|
}
|
|
|
|
// fetchAndSendMetar fetches a METAR observation for a given ICAO code and sends it to the client once received.
|
|
// This function returns immediately once the request has been queued.
|
|
func (s *metarService) fetchAndSendMetar(ctx context.Context, client *Client, icaoCode string) {
|
|
select {
|
|
case <-ctx.Done():
|
|
case s.metarRequests <- metarRequest{client: client, icaoCode: icaoCode}:
|
|
}
|
|
}
|