Commit ea77c794 authored by Simon Kirsten's avatar Simon Kirsten
Browse files

Improved and simplified code

parent 508ce4fa
......@@ -22,9 +22,9 @@ func main() {
app := cli.NewApp()
var port int
var browser bool
var local bool
var help bool
var flagBrowser bool
var flagLocal bool
var flagHelp bool
app.Usage = "Stream TV Server " + docURL
app.HideHelp = true
......@@ -43,70 +43,65 @@ func main() {
cli.BoolFlag{
Name: "browser, b",
Usage: "automatically open the default browser",
Destination: &browser,
Destination: &flagBrowser,
},
cli.BoolFlag{
Name: "local, l",
Usage: "only listen on local interfaces (see doc)",
Destination: &local,
Usage: "only listen on 127.0.0.1 (see doc)",
Destination: &flagLocal,
},
cli.BoolFlag{
Name: "help, h",
Usage: "show help",
Destination: &help,
Destination: &flagHelp,
},
}
app.Action = func(c *cli.Context) error {
if help {
if flagHelp {
cli.ShowAppHelp(c)
return nil
}
if len(c.Args()) != 0 {
if len(c.Args()) != 0 { // we had arguments. TODO: can this be disabled somehow in cli?
return fmt.Errorf("Unknown arguments: %v", c.Args())
}
mux := http.NewServeMux()
localhost := fmt.Sprintf("127.0.0.1:%d", port)
all := fmt.Sprintf("0.0.0.0:%d", port)
outbound := localhost
mux.Handle("/", website.Handler())
mux.Handle("/twitch/", http.StripPrefix("/twitch", twitch.Handler()))
mux.Handle("/tv/", http.StripPrefix("/tv", tv.Handler()))
var listenAddr string
if local {
listenAddr = fmt.Sprintf("127.0.0.1:%d", port)
} else {
listenAddr = fmt.Sprintf("0.0.0.0:%d", port)
if outboundIP, err := util.GetOutboundIP(); err == nil {
outbound = fmt.Sprintf("%s:%d", outboundIP, port)
}
fmt.Printf("Starting Stream TV Server on %s\n\n", listenAddr)
fmt.Printf("Listening on:\n")
var listenAddr string
if flagLocal {
listenAddr = localhost
listeningAddresses, err := util.GetListeningAddresses()
if err != nil {
// do nothing. range listeningAddresses is okay with nil
}
for _, laddr := range listeningAddresses {
addrStr := fmt.Sprintf("%s:%d", laddr.IP.String(), port)
if local && !laddr.IsLoopback {
continue
}
fmt.Printf("Serving on http://%s\n", localhost)
} else {
listenAddr = all
fmt.Printf(" http://%-21s %s\n", addrStr, laddr.InterfaceName)
fmt.Printf("Serving on\n http://%s\n http://%s\n", localhost, outbound)
}
fmt.Printf("\nRead the documentation at %s on how to use this server\n", docURL)
fmt.Printf("Stop with Ctrl-C or close this terminal\n")
if browser {
err := util.OpenBrowser(fmt.Sprintf("http://127.0.0.1:%d", port))
if flagBrowser {
err := util.OpenBrowser(fmt.Sprintf("http://%s", localhost))
if err != nil {
fmt.Printf("Could not automatically open browser: %v\n", err)
}
}
fmt.Printf("Read the documentation at %susage/ on how to use this server.\n", docURL)
fmt.Printf("Stop with Ctrl-C or close this terminal.\n")
mux := http.NewServeMux() // new router
mux.Handle("/", website.Handler())
mux.Handle("/twitch/", twitch.Handler())
mux.Handle("/tv/", tv.Handler())
return http.ListenAndServe(listenAddr, mux)
}
......
......@@ -2,5 +2,6 @@ module code.fbi.h-da.de/simons-nzse-2/stream-tv
require (
github.com/JamesStewy/sse v0.3.0
github.com/hashicorp/go-multierror v1.0.0
github.com/urfave/cli v1.21.0
)
package tv
import (
"encoding/json"
"log"
"net/http"
"strconv"
"github.com/JamesStewy/sse"
"github.com/hashicorp/go-multierror"
"code.fbi.h-da.de/simons-nzse-2/stream-tv/internal/util"
)
......@@ -30,22 +30,12 @@ var state = tvState{
ShowChat: false,
}
// stateHandleFunc just serves the state as JSON encoded
func stateHandleFunc(w http.ResponseWriter, r *http.Request) {
_, err := util.ServeJSON(w, &state)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Println(err)
return
}
}
// clients holds the clients that are connected to the event handler. It is used to broadcast state changes to all SSE (Server-Sent Events) clients.
// Note: the only reason we use a sse.Client => bool map is that we can call *delete* with the client as key. The actual bool value that is stored holds no significance whatsoever.
// This is basically a *set*.
var clients map[*sse.Client]bool = make(map[*sse.Client]bool)
// updateHandleFunc updated the state based on the query string.
// stateHandleFunc updated the state based on the query string.
// For example
// /tv/update?large_channel=asdf&small_channel=null&volume=&small_scale=0.25&show_chat=true
// will
......@@ -56,14 +46,14 @@ var clients map[*sse.Client]bool = make(map[*sse.Client]bool)
// - set show_chat to true
// If the floats (volume and small_scale) or the boolean (show_chat) could not be parsed no changes are made at all and an error gets returned.
func updateHandleFunc(w http.ResponseWriter, r *http.Request) {
func stateHandleFunc(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
// the new state we will replace the current state with IF everything parses correctly.
newState := state
// we save the error messages of the 3 parsing steps here.
var errors []string
var errs error
if q := query.Get("large_channel"); q != "" {
if q == "null" {
......@@ -84,7 +74,7 @@ func updateHandleFunc(w http.ResponseWriter, r *http.Request) {
if q := query.Get("show_chat"); q != "" {
newShowChat, err := strconv.ParseBool(q)
if err != nil {
errors = append(errors, err.Error())
errs = multierror.Append(errs, err)
} else {
newState.ShowChat = newShowChat
}
......@@ -93,7 +83,7 @@ func updateHandleFunc(w http.ResponseWriter, r *http.Request) {
if q := query.Get("volume"); q != "" {
newVolume, err := strconv.ParseFloat(q, 32)
if err != nil {
errors = append(errors, err.Error())
errs = multierror.Append(errs, err)
} else {
newState.Volume = float32(newVolume)
}
......@@ -102,52 +92,41 @@ func updateHandleFunc(w http.ResponseWriter, r *http.Request) {
if q := query.Get("small_scale"); q != "" {
newSmallScale, err := strconv.ParseFloat(q, 32)
if err != nil {
errors = append(errors, err.Error())
errs = multierror.Append(errs, err)
} else {
newState.SmallScale = float32(newSmallScale)
}
}
// TODO: redo this error output
if len(errors) != 0 { // we had errors
_, err := util.ServeJSONWithStatus(w, errors, http.StatusBadRequest)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Println(err)
return
}
if errs != nil { // we had errors
log.Printf("Error(s) while parsing update query: %v\n", errors)
return
}
state = newState
http.Error(w, errs.Error(), http.StatusBadRequest)
body, err := util.ServeJSON(w, state)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Println(err)
log.Printf("Error(s) while parsing update query: %v\n", errs)
return
}
log.Printf("Updated state (%s):\n%s\n", r.URL.RawQuery, string(body))
response, err := json.Marshal(&state)
body, err := util.ServeJSON(w, newState)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Println(err)
return
}
// craft the SSE message
msg := sse.Msg{
Data: string(response),
}
if state != newState {
state = newState
log.Printf("Updated state (%s):\n%s\n", r.URL.RawQuery, string(body))
// send it to all clients
for client := range clients {
client.Send(msg)
// craft the SSE message
msg := sse.Msg{
Data: string(body),
}
// send it to all clients
for client := range clients {
client.Send(msg)
}
}
}
......@@ -175,10 +154,8 @@ func eventsHandleFunc(w http.ResponseWriter, r *http.Request) {
func Handler() http.Handler {
mux := http.NewServeMux()
mux.Handle("/", http.NotFoundHandler()) // if the others don't match, return not found
mux.HandleFunc("/state", stateHandleFunc)
mux.HandleFunc("/update", updateHandleFunc)
mux.HandleFunc("/events", eventsHandleFunc)
mux.HandleFunc("/tv/state", stateHandleFunc)
mux.HandleFunc("/tv/events", eventsHandleFunc)
return mux
}
......@@ -194,7 +194,7 @@ func streamsFeaturedHandleFunc(w http.ResponseWriter, r *http.Request) {
return
}
var featuredStreamsResponse []Stream
featuredStreamsResponse := make([]Stream, 0)
for _, featured := range twitchFeaturedStreamsResponse.Featured {
if featured.Stream.Game != "" {
......@@ -252,7 +252,7 @@ func streamsTopHandleFunc(w http.ResponseWriter, r *http.Request) {
return
}
var topStreamsResponse []Stream
topStreamsResponse := make([]Stream, 0)
for _, stream := range twitchStreamsResponse.Streams {
if stream.Game != "" { // For some reason sometimes the game is empty.
......@@ -281,7 +281,7 @@ func gamesTopHandleFunc(w http.ResponseWriter, r *http.Request) {
return
}
var topGamesResponse []Game
topGamesResponse := make([]Game, 0)
for _, game := range twitchTopGamesResponse.Top {
topGamesResponse = append(topGamesResponse, game.toSimplified())
......@@ -299,10 +299,10 @@ func gamesTopHandleFunc(w http.ResponseWriter, r *http.Request) {
func Handler() http.Handler {
mux := http.NewServeMux()
mux.Handle("/", http.NotFoundHandler()) // if the others don't match, return not found
mux.HandleFunc("/games/top", gamesTopHandleFunc)
mux.HandleFunc("/streams/top", streamsTopHandleFunc)
mux.HandleFunc("/streams/featured", streamsFeaturedHandleFunc)
// mux.Handle("/", http.NotFoundHandler()) // if the others don't match, return not found
mux.HandleFunc("/twitch/games/top", gamesTopHandleFunc)
mux.HandleFunc("/twitch/streams/top", streamsTopHandleFunc)
mux.HandleFunc("/twitch/streams/featured", streamsFeaturedHandleFunc)
return mux
}
......@@ -85,6 +85,23 @@ func GetListeningAddresses() (addresses []ListeningAddress, err error) {
return
}
// GetOutboundIP returns the outbound IP of this device.
// It is adapted from https://stackoverflow.com/a/37382208/2607571
// It works by creating an UDP socket to an arbitrary IP and port.
// As UDP is connection-less it does not send any packets until a Send call.
// This way we can get the local address without creating any connections.
func GetOutboundIP() (net.IP, error) {
conn, err := net.Dial("udp", "1.1.1.1:80")
if err != nil {
return nil, err
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP, nil
}
// OpenBrowser opens the system's default browser to the given url.
// Currently supported: linux, windows and darwin (macOS).
// From https://gist.github.com/hyg/9c4afcd91fe24316cbf0
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment