diff --git a/Makefile b/Makefile index cf476cd..d1d00a2 100644 --- a/Makefile +++ b/Makefile @@ -7,9 +7,51 @@ # goosList = "android darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris windows" # goarchList = "386 amd64 amd64p32 arm arm64 ppc64 ppc64le mips mipsle mips64 mips64le mips64p32 mips64p32leppc s390 s390x sparc sparc64" -include make/Makefile.common # Windows needs the .exe extension. ifeq ($(OS),Windows_NT) -EXT=.exe +EXT=.exe endif + +TAGS= + +.PHONY: fmt vet get clean dev setdev test ServerMovieNight + +all: fmt vet test MovieNight static/main.wasm settings.json + +server: ServerMovieNight static/main.wasm + +ServerMovieNight: *.go common/*.go + GOOS=${TARGET} GOARCH=${ARCH} go$(GO_VERSION) build -o MovieNight $(TAGS) + +setdev: + $(eval export TAGS=-tags "dev") + +dev: setdev all + +MovieNight: *.go common/*.go + GOOS=${TARGET} GOARCH=${ARCH} go$(GO_VERSION) build -o MovieNight${EXT} $(TAGS) + +static/js/wasm_exec.js: + cp $$(go$(GO_VERSION) env GOROOT)/misc/wasm/wasm_exec.js $@ + +static/main.wasm: static/js/wasm_exec.js wasm/*.go common/*.go + GOOS=js GOARCH=wasm go$(GO_VERSION) build -o $@ $(TAGS) wasm/*.go + +clean: + -rm MovieNight${EXT} ./static/main.wasm ./static/js/wasm_exec.js + +fmt: + gofmt -w . + +vet: + go$(GO_VERSION) vet $(TAGS) ./... + GOOS=js GOARCH=wasm go$(GO_VERSION) vet $(TAGS) ./... + +test: + go$(GO_VERSION) test $(TAGS) ./... + +# Do not put settings_example.json here as a prereq to avoid overwriting +# the settings if the example is updated. +settings.json: + cp settings_example.json settings.json diff --git a/Makefile.BSD b/Makefile.BSD deleted file mode 100644 index f49a3f2..0000000 --- a/Makefile.BSD +++ /dev/null @@ -1,12 +0,0 @@ -# If a different version of Go is installed (via `go get`) set the GO_VERSION -# environment variable to that version. For example, setting it to "1.13.7" -# will run `go1.13.7 build [...]` instead of `go build [...]`. -# -# For info on installing extra versions, see this page: -# https://golang.org/doc/install#extra_versions - -# goosList = "android darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris windows" -# goarchList = "386 amd64 amd64p32 arm arm64 ppc64 ppc64le mips mipsle mips64 mips64le mips64p32 mips64p32leppc s390 s390x sparc sparc64" -include make/Makefile.common - -GOOS=freebsd diff --git a/chatclient.go b/chatclient.go index b843859..cfc6ac3 100644 --- a/chatclient.go +++ b/chatclient.go @@ -91,17 +91,20 @@ func (cl *Client) NewMsg(data common.ClientData) { // because the /me command outputs to the messages msg = addSpoilerTags(msg) + msgLen := len(msg) + // Don't send zero-length messages - if len(msg) == 0 { + if msgLen == 0 { return } if strings.HasPrefix(msg, "/") { // is a command - msg = msg[1:len(msg)] + msg = msg[1:msgLen] fullcmd := strings.Split(msg, " ") cmd := strings.ToLower(fullcmd[0]) - args := fullcmd[1:len(fullcmd)] + fullcmdLen := len(fullcmd) + args := fullcmd[1:fullcmdLen] response, err := commands.RunCommand(cmd, args, cl) if response != "" || err != nil { @@ -136,7 +139,7 @@ func (cl *Client) NewMsg(data common.ClientData) { } // Trim long messages - if len(msg) > 400 { + if msgLen > 400 { msg = msg[0:400] } diff --git a/chatcommands.go b/chatcommands.go index d7de741..0be866f 100644 --- a/chatcommands.go +++ b/chatcommands.go @@ -277,11 +277,13 @@ var commands = &CommandControl{ cl.belongsTo.clientsMtx.Unlock() // Just print max users and time alive here - return fmt.Sprintf("Current users in chat: %d
Max users in chat: %d
Server uptime: %s
Stream uptime: %s", + return fmt.Sprintf("Current users in chat: %d
Max users in chat: %d
Server uptime: %s
Stream uptime: %s
Viewers: %d
Max Viewers: %d", users, stats.getMaxUsers(), time.Since(stats.start), stats.getStreamLength(), + stats.getViewerCount(), + stats.getMaxViewerCount(), ), nil }, }, diff --git a/common/utils.go b/common/utils.go index f84900c..dbb011b 100644 --- a/common/utils.go +++ b/common/utils.go @@ -3,6 +3,7 @@ package common // Misc utils import ( + "net/http" "os" "path/filepath" "regexp" @@ -41,3 +42,20 @@ func Substr(input string, start int, length int) string { return string(asRunes[start : start+length]) } + +// Return the value of "Forwarded" or "X-Forwarded-For", +// if "Forwarded" & "X-Forwarded-For" are present then "Forwarded" value is returned. +// Return "" if "Forwarded" and "X-Forwarded-For" are absent. +func ExtractForwarded(r *http.Request) string { + f := r.Header.Get("Forwarded") + if f != "" { + return f + } + + xff := r.Header.Get("X-Forwarded-For") + if xff != "" { + return xff + } + + return "" +} diff --git a/handlers.go b/handlers.go index 8b3ae68..e98b225 100644 --- a/handlers.go +++ b/handlers.go @@ -104,7 +104,7 @@ func wsHandler(w http.ResponseWriter, r *http.Request) { Conn: conn, // If the server is behind a reverse proxy (eg, Nginx), look // for this header to get the real IP address of the client. - forwardedFor: r.Header.Get("X-Forwarded-For"), + forwardedFor: common.ExtractForwarded(r), } go func() { @@ -424,9 +424,14 @@ func handleLive(w http.ResponseWriter, r *http.Request) { muxer := flv.NewMuxerWriteFlusher(writeFlusher{httpflusher: flusher, Writer: w}) cursor := ch.que.Latest() + session, _ := sstore.Get(r, "moviesession") + stats.addViewer(session.ID) avutil.CopyFile(muxer, cursor) + stats.removeViewer(session.ID) } else { - + // Maybe HTTP_204 is better than HTTP_404 + w.WriteHeader(http.StatusNoContent) + stats.resetViewers() } } diff --git a/make/Makefile.common b/make/Makefile.common deleted file mode 100755 index e179728..0000000 --- a/make/Makefile.common +++ /dev/null @@ -1,42 +0,0 @@ -TAGS= - -.PHONY: fmt vet get clean dev setdev test ServerMovieNight - -all: fmt vet test MovieNight static/main.wasm settings.json - -server: ServerMovieNight static/main.wasm - -ServerMovieNight: *.go common/*.go - GOOS=${TARGET} GOARCH=${ARCH} go$(GO_VERSION) build -o MovieNight $(TAGS) - -setdev: - $(eval export TAGS=-tags "dev") - -dev: setdev all - -MovieNight: *.go common/*.go - GOOS=${TARGET} GOARCH=${ARCH} go$(GO_VERSION) build -o MovieNight${EXT} $(TAGS) - -static/js/wasm_exec.js: - cp $$(go$(GO_VERSION) env GOROOT)/misc/wasm/wasm_exec.js $@ - -static/main.wasm: static/js/wasm_exec.js wasm/*.go common/*.go - GOOS=js GOARCH=wasm go$(GO_VERSION) build -o $@ $(TAGS) wasm/*.go - -clean: - -rm MovieNight${EXT} ./static/main.wasm ./static/js/wasm_exec.js - -fmt: - gofmt -w . - -vet: - go$(GO_VERSION) vet $(TAGS) ./... - GOOS=js GOARCH=wasm go$(GO_VERSION) vet $(TAGS) ./... - -test: - go$(GO_VERSION) test $(TAGS) ./... - -# Do not put settings_example.json here as a prereq to avoid overwriting -# the settings if the example is updated. -settings.json: - cp settings_example.json settings.json diff --git a/readme.md b/readme.md index e51601f..44359b9 100644 --- a/readme.md +++ b/readme.md @@ -37,15 +37,14 @@ You have to : - download `git clone https://github.com/zorchenhimer/MovieNight`, go into the source directory `cd MovieNight`; - choose your `TARGET` oneof "android darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris windows"; - choose your `ARCH` oneof "386 amd64 amd64p32 arm arm64 ppc64 ppc64le mips mipsle mips64 mips64le mips64p32 mips64p32leppc s390 s390x sparc sparc64"; -- build `make TARGET=plan9 ARCH=arm64`; +- build `make TARGET=plan9 ARCH=arm64` (On BSD systems use `gmake`); - and run `./MovieNight`; Example : -```bash +```shell $ git clone https://git.mentality.rip/sjw/MovieNight.git $ cd MovieNight $ make TARGET=linux ARCH=amd64 -$ ./MovieNight ``` ### Docker build diff --git a/stats.go b/stats.go index 4fc8b26..fc67098 100644 --- a/stats.go +++ b/stats.go @@ -8,18 +8,50 @@ import ( ) type streamStats struct { - messageIn int - messageOut int - maxUsers int - start time.Time - mutex sync.Mutex - + messageIn int + messageOut int + maxUsers int + start time.Time + mutex sync.Mutex streamStart time.Time streamLive bool // True if live + viewers map[string]int + maxViewers int +} + +func (s *streamStats) addViewer(id string) { + s.mutex.Lock() + s.viewers[id] = len(s.viewers) + size := len(s.viewers) + s.updateMaxViewers(size) + s.mutex.Unlock() + + common.LogDebugf("[stats] %d viewer(s) connected\n", size) +} +func (s *streamStats) removeViewer(id string) { + s.mutex.Lock() + delete(s.viewers, id) + s.mutex.Unlock() + + common.LogDebugf("[stats] One viewer left the stream\n") +} + +func (s *streamStats) updateMaxViewers(size int) { + if s.maxViewers < size { + s.maxViewers = size + } +} + +func (s *streamStats) resetViewers() { + s.viewers = sessionsMapNew() +} + +func sessionsMapNew() map[string]int { + return make(map[string]int) } func newStreamStats() streamStats { - return streamStats{start: time.Now(), streamLive: false} + return streamStats{start: time.Now(), streamLive: false, viewers: sessionsMapNew()} } func (s *streamStats) msgInInc() { @@ -53,10 +85,11 @@ func (s *streamStats) Print() { s.mutex.Lock() defer s.mutex.Unlock() - common.LogInfof("Messages In: %d\n", s.messageIn) - common.LogInfof("Messages Out: %d\n", s.messageOut) - common.LogInfof("Max users in chat: %d\n", s.maxUsers) - common.LogInfof("Total Time: %s\n", time.Since(s.start)) + common.LogInfof("[stats] Messages In: %d\n", s.messageIn) + common.LogInfof("[stats] Messages Out: %d\n", s.messageOut) + common.LogInfof("[stats] Max users in chat: %d\n", s.maxUsers) + common.LogInfof("[stats] Total Time: %s\n", time.Since(s.start)) + common.LogInfof("[stats] Max Stream Viewer: %d\n", s.maxViewers) } func (s *streamStats) startStream() { @@ -83,3 +116,24 @@ func (s *streamStats) getStreamLength() time.Duration { } return time.Since(s.streamStart) } + +func (s *streamStats) getViewerCount() int { + s.mutex.Lock() + defer s.mutex.Unlock() + + return len(s.viewers) +} + +func (s *streamStats) getMaxViewerCount() int { + s.mutex.Lock() + defer s.mutex.Unlock() + + return s.maxViewers +} + +func (s *streamStats) getViewers() map[string]int { + s.mutex.Lock() + defer s.mutex.Unlock() + + return s.viewers +}