// 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 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 } type sortPlayersByName []*player func (pls sortPlayersByName) Len() int { return len(pls) } func (pls sortPlayersByName) Less(i, j int) bool { return pls[i].Name < pls[j].Name } func (pls sortPlayersByName) Swap(i, j int) { pls[i], pls[j] = pls[j], pls[i] } const selectPlayersSQL = ` SELECT posx, posy, posz, name FROM player WHERE modification_date > now() - '2m'::interval` func playersFromPostgreSQL(connS string) ([]*player, error) { 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.Y, &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 { pls, err := ps.readFromFIFO() if err != nil { //log.Printf("err: %s\n", err) time.Sleep(sleepInterval) continue } if pls == nil { //log.Println("no players") continue } //log.Printf("%+q\n", pls) sort.Sort(sortPlayersByName(pls)) 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 { // TODO: Throttle this! ps.wsf.BroadcastPlayers(pls) } } } func (ps *players) ServeHTTP(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "application/json") var pls []*player ps.mu.RLock() pls = ps.pls ps.mu.RUnlock() encoder := json.NewEncoder(rw) if err := encoder.Encode(pls); err != nil { log.Printf("error: sending JSON failed: %s\n", err) } }