// // rhrdtime // // The Radio Helsinki Rivendell Time Websocket Server // // // Copyright (C) 2015 Christian Pointner // // This file is part of rhrdtime. // // rhrdtime is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // rhrdtime is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with rhrdtime. If not, see . // package main import ( "fmt" "time" "math" "flag" "net/http" "encoding/json" "github.com/codegangsta/martini" "github.com/gorilla/websocket" "github.com/tuxychandru/pubsub" ) type timeUpdate struct { Timestamp int64 `json:"timestamp"` TZOffset int `json:"tz-offset"` Week uint8 `json:"week"` } func getTimeUpdate() []byte { // // This computes the current Rivendell Week based on the number // of weeks since epoch. // // Explanation: // epoch was at 01.01.1970 UTC which was a Thursday. // Monday in that week is (s-from-epoch + 3*24*60*60) seconds ago. // This needs to be adjusted by the timezone offset for Europe/Vienna // which is of course not constant (damn you daylight savings time) // Divide this by (7*24*60*60) and you get the number of // weeks since the Monday in the week of epoch adjusted for timezone offsets. // This week had week number 3 so add an offset of 2 and // get the modulo of 4. This rounded down gives you the current week // with 0 meaning Week 1. So add 1 to that number and you will get // the current RD week. // loc, err := time.LoadLocation("Europe/Vienna"); if err != nil { fmt.Println(err) return []byte{'{', '}'} } now := time.Now().In(loc); _, offset := now.Zone() sEpoch := now.Unix() + int64(offset) week := uint8(math.Floor(math.Mod((((float64(sEpoch) + 259200)/604800) + 2), 4)) + 1) update := timeUpdate{now.Unix(), offset, week} update_json, err := json.Marshal(update) if err != nil { fmt.Println(err) return []byte{'{', '}'} } return update_json } func goTalkWithClient(w http.ResponseWriter, r *http.Request, ps *pubsub.PubSub) { ws, err := websocket.Upgrade(w, r, nil, 1024, 1024) if _, ok := err.(websocket.HandshakeError); ok { http.Error(w, "Not a websocket handshake", 400) return } else if err != nil { fmt.Println(err) return } fmt.Println("Client connected", ws.RemoteAddr()) timeupdate_chan := ps.Sub("timeupdate") defer ps.Unsub(timeupdate_chan, "timeupdate") update_json := getTimeUpdate() if err := ws.WriteMessage(websocket.TextMessage, update_json); err != nil { return } for jsonupdate := range timeupdate_chan { if err := ws.WriteMessage(websocket.TextMessage, jsonupdate.([]byte)); err != nil { return } } } func RunMartini(ps *pubsub.PubSub, addr string) { m := martini.Classic() m.Get("/time", func(w http.ResponseWriter, r *http.Request) { goTalkWithClient(w, r, ps) }) m.RunOnAddr(addr) } func main() { interval_s := flag.String("interval", "15s", "the interval between updates, default: '15s'") addr_s := flag.String("addr", ":3000", "addr:port to listen on, default: ':3000'") help := flag.Bool("help", false, "show usage") flag.Parse() if *help { flag.Usage() return } interval, err := time.ParseDuration(*interval_s) if err != nil { fmt.Println(err) return } ps := pubsub.New(1) ticker := time.NewTicker(interval) go func() { for _ = range ticker.C { ps.Pub(getTimeUpdate(), "timeupdate") } }() RunMartini(ps, *addr_s) }