laserproxy: init

Change-Id: I1900be6eea2d702548279176d796c58c34952dc8
diff --git a/hswaw/laserproxy/main.go b/hswaw/laserproxy/main.go
new file mode 100644
index 0000000..07d7bcd
--- /dev/null
+++ b/hswaw/laserproxy/main.go
@@ -0,0 +1,205 @@
+package main
+
+import (
+	"context"
+	"flag"
+	"fmt"
+	"html/template"
+	"net"
+	"net/http"
+	"math/rand"
+	"strings"
+	"time"
+
+	"github.com/golang/glog"
+
+	mirko "code.hackerspace.pl/hscloud/go/mirko"
+	"code.hackerspace.pl/hscloud/hswaw/laserproxy/tpl"
+)
+
+var (
+	flagLaserAddress = "10.11.0.10"
+	flagLaserNetworkAddress = "10.11.0.1"
+	flagClientNetworkAddress = "10.8.1.2"
+	flagWebAddress = "127.0.0.1:8080"
+
+	tplIndex = template.Must(template.New("index.html").Parse(tpl.MustAssetString("index.html")))
+)
+
+type service struct {
+	lockCtrl chan *lockCtrl
+}
+
+func main() {
+	flag.StringVar(&flagLaserAddress, "laser_address", flagLaserAddress, "Address of Ruida controller on laser network")
+	flag.StringVar(&flagLaserNetworkAddress, "laser_network", flagLaserNetworkAddress, "Address of proxy on laser network")
+	flag.StringVar(&flagClientNetworkAddress, "client_network", flagClientNetworkAddress, "Address of proxy on client network")
+	flag.StringVar(&flagWebAddress, "web_address", flagWebAddress, "Address and port to listen on for public web connections")
+
+	flag.Parse()
+	m := mirko.New()
+	if err := m.Listen(); err != nil {
+		glog.Exitf("Could not listen: %v", err)
+	}
+
+	lisLaser, err := net.ListenPacket("udp", fmt.Sprintf("%s:40200", flagLaserNetworkAddress))
+	if err != nil {
+		glog.Fatalf("could not listen on laser network: %v", err)
+	}
+	defer lisLaser.Close()
+
+	lisClient, err := net.ListenPacket("udp", fmt.Sprintf("%s:50200", flagClientNetworkAddress))
+	if err != nil {
+		glog.Fatalf("could not listen on client network: %v", err)
+	}
+	defer lisClient.Close()
+
+	laserIP := net.ParseIP(flagLaserAddress)
+	if laserIP == nil {
+		glog.Fatalf("could not parse laser IP address %q", flagLaserAddress)
+	}
+	laserAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:50200", laserIP.String()))
+	if err != nil {
+		glog.Fatalf("could not make laser UDP address: %v", err)
+	}
+
+	ctx := m.Context()
+	s := &service{
+		lockCtrl: make(chan *lockCtrl),
+	}
+	updates := make(chan *lockUpdate)
+	go s.runProxy(ctx, updates, laserAddr, lisLaser, lisClient)
+	go s.runLocker(ctx, s.lockCtrl)
+	s.lockCtrl <- &lockCtrl{
+		subscribe: &lockCtrlSubscribe{
+			subscriber: updates,
+		},
+	}
+
+	mux := http.NewServeMux()
+	mux.HandleFunc("/", s.handlerIndex)
+	mux.HandleFunc("/take", s.handlerTake)
+	mux.HandleFunc("/release", s.handlerRelease)
+	mux.HandleFunc("/force", s.handlerForce)
+	httpSrv := &http.Server{Addr: flagWebAddress, Handler: mux}
+
+	glog.Infof("Listening for web connections on %q...", flagWebAddress)
+	go func() {
+		if err := httpSrv.ListenAndServe(); err != nil {
+			glog.Error(err)
+		}
+	}()
+
+	if err := m.Serve(); err != nil {
+		glog.Exitf("Could not run: %v", err)
+	}
+
+	glog.Info("Running.")
+	<-m.Done()
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+	httpSrv.Shutdown(ctx)
+}
+
+var (
+	sampleNames = []string{
+		"enleth", "radex", "qdot", "hans acker", "makłowicz",
+	}
+)
+
+func remoteAddr(r *http.Request) string {
+	if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" {
+		return strings.Split(fwd, ":")[0]
+	}
+	return strings.Split(r.RemoteAddr, ":")[0]
+}
+
+func (s *service) handlerIndex(w http.ResponseWriter, r *http.Request) {
+	res := make(chan *lockResCurrent)
+	s.lockCtrl <- &lockCtrl{
+		getCurrent: &lockCtrlGetCurrent{
+			res: res,
+		},
+	}
+	cur := <-res
+
+	err := tplIndex.Execute(w, struct {
+		You bool
+		CurrentAddress string
+		CurrentNote string
+		CurrentDeadline string
+		SampleName string
+	}{
+		You: cur.addr == remoteAddr(r),
+		CurrentAddress: cur.addr,
+		CurrentNote: cur.note,
+		CurrentDeadline: fmt.Sprintf("%d minute(s)", int(cur.deadline.Sub(time.Now()).Minutes())),
+		SampleName: sampleNames[rand.Intn(len(sampleNames))],
+	})
+	if err != nil {
+		glog.Errorf("rendering template: %v", err)
+	}
+}
+
+func (s *service) handlerTake(w http.ResponseWriter, r *http.Request) {
+	if r.Method != "POST" {
+		return
+	}
+	r.ParseForm()
+	who := r.Form.Get("who")
+	prev := r.Form.Get("prev")
+	if who == "" {
+		fmt.Fprintf(w, "sorry, who are you? please specify a name")
+		return
+	}
+
+	res := make(chan bool)
+	take := &lockCtrlTake{
+		note: who,
+		addr: remoteAddr(r),
+		prev: prev,
+		res: res,
+	}
+	s.lockCtrl <- &lockCtrl{
+		take: take,
+	}
+	won := <-res
+
+	if won {
+		http.Redirect(w, r, "/", 302)
+	} else {
+		fmt.Fprintf(w, "lock not taken")
+	}
+}
+
+func (s *service) handlerRelease(w http.ResponseWriter, r *http.Request) {
+	if r.Method != "POST" {
+		return
+	}
+	res := make(chan struct{})
+	s.lockCtrl <- &lockCtrl{
+		release: &lockCtrlRelease{
+			addr: remoteAddr(r),
+			res: res,
+		},
+	}
+	<-res
+
+	http.Redirect(w, r, "/", 302)
+}
+
+func (s *service) handlerForce(w http.ResponseWriter, r *http.Request) {
+	if r.Method != "POST" {
+		return
+	}
+	res := make(chan struct{})
+	s.lockCtrl <- &lockCtrl{
+		release: &lockCtrlRelease{
+			force: true,
+			res: res,
+		},
+	}
+	<-res
+
+	http.Redirect(w, r, "/", 302)
+}