package uncanny import ( "log" "fmt" "net/http" "path" "strconv" ) const ( // highest slot number (range: 0..maxSlot) maxSlot = 4 // highest display line number (range: 0..maxLine) maxLine = 1 ) type Server struct { *http.ServeMux srv *http.Server can *Can } func NewServer(can *Can, addr string) *Server { ret := &Server{ ServeMux: http.NewServeMux(), can: can, srv: &http.Server{ Addr: addr, }, } ret.registerHandlers() ret.srv.Handler = ret return ret } func (s *Server) registerHandlers() { s.HandleFunc("/", http.NotFound) s.HandleFunc("/dispense/", func(rw http.ResponseWriter, r *http.Request) { if matched, _ := path.Match("/dispense/[0-9]*", r.URL.Path); matched { slot, err := strconv.ParseUint(path.Base(r.URL.Path), 10, 32) if err != nil { log.Printf("Error decoding slot number: %v", err) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } else if slot < 0 || slot > maxSlot { log.Printf("Invalid slot number: %d", slot) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } else { s.can.Dispense(int(slot)) rw.Header().Set("Content-Type", "text/plain") rw.WriteHeader(http.StatusOK) fmt.Fprintf(rw, "%d dispensing", slot) } } else { log.Printf("Mismatched path: %s", r.URL.Path) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } }) s.HandleFunc("/stop", func(rw http.ResponseWriter, r *http.Request) { s.can.Cancel() rw.Header().Set("Content-Type", "text/plain") rw.WriteHeader(http.StatusOK) fmt.Fprintf(rw, "stopping") }) s.HandleFunc("/level/", func(rw http.ResponseWriter, r *http.Request) { if matched, _ := path.Match("/level/[0-9]*", r.URL.Path); matched { slot, err := strconv.ParseUint(path.Base(r.URL.Path), 10, 32) if err != nil { log.Printf("Error decoding slot number: %v", err) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } else if slot < 0 || slot > maxSlot { log.Printf("Invalid slot number: %d", slot) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } else { rw.Header().Set("Content-Type", "text/plain") rw.WriteHeader(http.StatusOK) if s.can.IsEmpty(int(slot)) { fmt.Fprintf(rw, "%d empty", slot) } else { fmt.Fprintf(rw, "%d full", slot) } } } else { log.Printf("Mismatched path: %s", r.URL.Path) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } }) s.HandleFunc("/active/", func(rw http.ResponseWriter, r *http.Request) { if matched, _ := path.Match("/active/[0-9]*", r.URL.Path); matched { slot, err := strconv.ParseUint(path.Base(r.URL.Path), 10, 32) if err != nil { log.Printf("Error decoding slot number: %v", err) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } else if slot < 0 || slot > maxSlot { log.Printf("Invalid slot number: %d", slot) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } else { rw.Header().Set("Content-Type", "text/plain") rw.WriteHeader(http.StatusOK) if s.can.IsDispensing(int(slot)) { fmt.Fprintf(rw, "%d dispensing", slot) } else { fmt.Fprintf(rw, "%d off", slot) } } } else { log.Printf("Mismatched path: %s", r.URL.Path) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } }) s.HandleFunc("/display/", func(rw http.ResponseWriter, r *http.Request) { if matched, _ := path.Match("/display/[0-9]*", r.URL.Path); matched { line, err := strconv.ParseUint(path.Base(r.URL.Path), 10, 32) if err != nil { log.Printf("Error decoding line number: %v", err) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } else if line < 0 || line > maxLine { log.Printf("Invalid line number: %d", line) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } else { query := r.URL.Query() if _, ok := query["text"]; ok { text := query.Get("text") s.can.DisplayPrint(int(line), text) rw.Header().Set("Content-Type", "text/plain") rw.WriteHeader(http.StatusOK) fmt.Fprintf(rw, "Printed on line %d: '%s'", int(line), text) } else { log.Printf("Invalid query, missing text") http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } } } else { log.Printf("Mismatched path: %s", r.URL.Path) http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } }) } func (s *Server) Start() error { log.Printf("Listening on %v", s.srv.Addr) return s.srv.ListenAndServe() } func (s *Server) Stop() error { return s.srv.Close() }