mirror of
https://bitbucket.org/s_l_teichmann/mtsatellite
synced 2024-11-18 08:00:26 +01:00
188 lines
3.7 KiB
Go
188 lines
3.7 KiB
Go
// Copyright 2014, 2015 by Sascha L. Teichmann
|
|
// Use of this source code is governed by the MIT license
|
|
// that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"html/template"
|
|
"log"
|
|
"math"
|
|
"net/http"
|
|
"os"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"bitbucket.org/s_l_teichmann/mtsatellite/common"
|
|
)
|
|
|
|
const (
|
|
sleepInterval = time.Second * 5
|
|
sleepPG = time.Second
|
|
)
|
|
|
|
var geoJSONTmpl = template.Must(template.New("geojson").Parse(
|
|
`{ "type": "Feature",
|
|
"geometry": {
|
|
"type": "Point",
|
|
"coordinates": [{{.Z}}, {{.X}}]
|
|
},
|
|
"properties": {
|
|
"name": "{{.Name | html }}"
|
|
}
|
|
}`))
|
|
|
|
type player struct {
|
|
X float64 `json:"x"`
|
|
Y float64 `json:"y"`
|
|
Z float64 `json:"z"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
type players struct {
|
|
fifo string
|
|
wsf *websocketForwarder
|
|
pls []*player
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func newPlayers(fifo string, wsf *websocketForwarder) *players {
|
|
return &players{fifo: fifo, wsf: wsf, pls: []*player{}}
|
|
}
|
|
|
|
func (p *player) MarshalJSON() ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
if err := geoJSONTmpl.Execute(&buf, p); err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func (p *player) same(o *player) bool {
|
|
return p.Name == o.Name &&
|
|
math.Abs(p.X-o.X) < 0.000001 &&
|
|
math.Abs(p.Y-o.Y) < 0.000001 &&
|
|
math.Abs(p.Z-o.Z) < 0.000001
|
|
}
|
|
|
|
const selectPlayersSQL = `
|
|
SELECT posx/10.0, posy/10.0, posz/10.0, name
|
|
FROM player
|
|
WHERE modification_date > now() - '2m'::interval`
|
|
|
|
func playersFromPostgreSQL(connS string) ([]*player, error) {
|
|
time.Sleep(sleepPG)
|
|
db, err := sql.Open("pgx", connS)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer db.Close()
|
|
rows, err := db.QueryContext(context.Background(), selectPlayersSQL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var pls []*player
|
|
for rows.Next() {
|
|
var p player
|
|
if err := rows.Scan(&p.X, &p.Y, &p.Z, &p.Name); err != nil {
|
|
return nil, err
|
|
}
|
|
pls = append(pls, &p)
|
|
}
|
|
return pls, rows.Err()
|
|
}
|
|
|
|
func (ps *players) readFromFIFO() ([]*player, error) {
|
|
if host, ok := common.IsPostgreSQL(ps.fifo); ok {
|
|
return playersFromPostgreSQL(host)
|
|
}
|
|
|
|
file, err := os.Open(ps.fifo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
reader := bufio.NewReader(file)
|
|
decoder := json.NewDecoder(reader)
|
|
var pls []*player
|
|
|
|
if err = decoder.Decode(&pls); err != nil {
|
|
return nil, err
|
|
}
|
|
return pls, nil
|
|
}
|
|
|
|
func samePlayers(a, b []*player) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i, p := range a {
|
|
if !p.same(b[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (ps *players) run() {
|
|
for {
|
|
empty := len(ps.current()) == 0
|
|
|
|
pls, err := ps.readFromFIFO()
|
|
if err != nil {
|
|
//log.Printf("err: %s\n", err)
|
|
time.Sleep(sleepInterval)
|
|
continue
|
|
}
|
|
if empty && pls == nil {
|
|
//log.Println("no players")
|
|
continue
|
|
}
|
|
//log.Printf("%+q\n", pls)
|
|
sort.Slice(pls, func(i, j int) bool {
|
|
return pls[i].Name < pls[j].Name
|
|
})
|
|
var change bool
|
|
ps.mu.Lock()
|
|
//log.Printf("%+q\n", pls)
|
|
//log.Printf("%+q\n", ps.pls)
|
|
if change = !samePlayers(pls, ps.pls); change {
|
|
ps.pls = pls
|
|
}
|
|
ps.mu.Unlock()
|
|
if change && ps.wsf != nil {
|
|
if pls == nil {
|
|
pls = []*player{}
|
|
}
|
|
// TODO: Throttle this!
|
|
ps.wsf.BroadcastPlayers(pls)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ps *players) current() []*player {
|
|
ps.mu.RLock()
|
|
defer ps.mu.RUnlock()
|
|
return ps.pls
|
|
}
|
|
|
|
func (ps *players) initConnection(wsf *websocketForwarder, c *connection) {
|
|
wsf.singleSend(c, &plsMsg{Pls: ps.current()})
|
|
}
|
|
|
|
func (ps *players) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|
rw.Header().Set("Content-Type", "application/json")
|
|
pls := ps.current()
|
|
encoder := json.NewEncoder(rw)
|
|
if err := encoder.Encode(pls); err != nil {
|
|
log.Printf("error: sending JSON failed: %s\n", err)
|
|
}
|
|
}
|