<commit_before>package server

import (
	"flag"
	"fmt"
	"image"
	"io"
	"log"
	"net/http"
	"runtime"
	"time"

	"github.com/gorilla/context"
	"github.com/gorilla/mux"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

//JpegMaximumQuality quality for jpeg compression
const JpegMaximumQuality = 100

//ImageCacheDuration caching time for images
const ImageCacheDuration = 315360000

//Connection is the mgo database connection
var Connection *mgo.Session

//Configuration is the images  configuration
var Configuration *Config

// VarsHandler is a simple wrapper so the request params can be injected into the main handler
type VarsHandler func(http.ResponseWriter, *http.Request, *ServerConfiguration)

// ServeHTTP wraps the imageHandler function and validates request parameters
// in order to create a ServerConfiguration object
func (h VarsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	requestConfig, validateError := CreateConfigurationFromVars(r, vars)

	if validateError != nil {
		log.Printf("%d invalid request parameters given.\n", http.StatusNotFound)
		w.WriteHeader(http.StatusNotFound)
		return
	}

	h(w, r, requestConfig)
}

// addImageMetaData adds data to the image the image
func addImageMetaData(targetImage *mgo.GridFile, imageData image.Image, imageFormat string, originalImage *mgo.GridFile, entry *Entry) {
	width := imageData.Bounds().Dx()
	height := imageData.Bounds().Dy()
	originalRef := mgo.DBRef{"fs.files", originalImage.Id(), ""}

	metadata := bson.M{
		"width":            width,
		"height":           height,
		"original":         originalRef,
		"originalFilename": originalImage.Name(),
		"resizeType":       entry.Type,
		"size":             fmt.Sprintf("%dx%d", entry.Width, entry.Height)}

	targetImage.SetContentType(fmt.Sprintf("image/%s", imageFormat))
	targetImage.SetMeta(metadata)
}

// isModified returns true if the file must be delivered, false otherwise.
func isModified(file *mgo.GridFile, header *http.Header) bool {
	md5 := file.MD5()
	modifiedHeader := header.Get("If-Modified-Since")
	modifiedTime := time.Now()

	if modifiedHeader != "" {
		modifiedTime, _ = time.Parse(time.RFC1123, modifiedHeader)
	}

	// normalize upload date to use the same format as the browser
	uploadDate, _ := time.Parse(time.RFC1123, file.UploadDate().Format(time.RFC1123))

	if header.Get("Cache-Control") == "no-cache" {
		log.Printf("Is modified, because caching not enabled.")
		return true
	}

	if uploadDate.After(modifiedTime) {
		log.Printf("Is modified, because upload date after modified date.\n")
		return true
	}

	if md5 != header.Get("If-None-Match") {
		log.Printf("Is modified, because md5 mismatch. %s != %s", md5, header.Get("If-None-Match"))
		return true
	}

	return false
}

// setCacheHeaders sets the cache headers into the http.ResponseWriter
func setCacheHeaders(file *mgo.GridFile, w http.ResponseWriter) {
	w.Header().Set("Etag", file.MD5())
	w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", ImageCacheDuration))
	d, _ := time.ParseDuration(fmt.Sprintf("%ds", ImageCacheDuration))

	expires := file.UploadDate().Add(d)

	w.Header().Set("Last-Modified", file.UploadDate().Format(time.RFC1123))
	w.Header().Set("Expires", expires.Format(time.RFC1123))
	w.Header().Set("Date", file.UploadDate().Format(time.RFC1123))
}

// imageHandler the main handler
func imageHandler(w http.ResponseWriter, r *http.Request, requestConfig *ServerConfiguration) {
	log.Printf("Request on %s", r.URL)

	defer func() {
		runtime.GC()
	}()

	if Connection == nil {
		log.Printf("Connection is not set.")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	if Configuration == nil {
		log.Printf("Configuration object is not set.")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	gridfs := Connection.DB(requestConfig.Database).GridFS("fs")

	resizeEntry, _ := Configuration.GetEntryByName(requestConfig.FormatName)

	var foundImage *mgo.GridFile

	if bson.IsObjectIdHex(requestConfig.Filename) {
		foundImage, _ = FindImageByParentId(requestConfig.Filename, resizeEntry, gridfs)
	} else {
		foundImage, _ = FindImageByParentFilename(requestConfig.Filename, resizeEntry, gridfs)
	}

	// case that we do not want resizing and did not find any image
	if foundImage == nil && resizeEntry == nil {
		log.Printf("%d invalid request parameters given.\n", http.StatusNotFound)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// we found a image but did not want resizing
	if foundImage != nil {
		if !isModified(foundImage, &r.Header) {
			w.WriteHeader(http.StatusNotModified)
			log.Printf("%d Returning cached image.\n", http.StatusNotModified)
			return
		}

		setCacheHeaders(foundImage, w)

		io.Copy(w, foundImage)
		foundImage.Close()
		log.Printf("%d Image found, no resizing.\n", http.StatusOK)
		return
	}

	if foundImage == nil && resizeEntry != nil {
		// generate new image
		if bson.IsObjectIdHex(requestConfig.Filename) {
			fmt.Printf(requestConfig.Filename)
			foundImage, _ = FindImageByParentId(requestConfig.Filename, nil, gridfs)
		} else {
			foundImage, _ = FindImageByParentFilename(requestConfig.Filename, nil, gridfs)
		}

		if foundImage == nil {
			log.Printf("%d Could not find original image.\n", http.StatusNotFound)
			w.WriteHeader(http.StatusNotFound)
			return
		}

		resizedImage, imageFormat, imageErr := ResizeImageFromGridfs(foundImage, resizeEntry)

		// in this case, resizing for this image does not work, therefore, we at least return the original image
		if imageErr != nil {

			// this might be a problem at the moment, go does not support interlaced pngs
			// http://code.google.com/p/go/issues/detail?id=6293
			// at the moment, we return a not found...
			log.Printf("%d image could not be decoded.\n", http.StatusNotFound)
			w.WriteHeader(http.StatusNotFound)
			return
		}

		// return the image to the client if all cache headers could be set
		targetfile, _ := gridfs.Create(GetRandomFilename(imageFormat))

		encodeErr := EncodeImage(targetfile, *resizedImage, imageFormat)

		if targetfile == nil {
			log.Printf("new gridfs file could not be created")
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		if encodeErr != nil {
			log.Fatalf(imageErr.Error())
			w.WriteHeader(http.StatusBadRequest)
			log.Printf("%d image could not be encoded.\n", http.StatusBadRequest)
			return
		}

		addImageMetaData(targetfile, *resizedImage, imageFormat, foundImage, resizeEntry)

		defer targetfile.Close()

		setCacheHeaders(targetfile, w)

		EncodeImage(w, *resizedImage, imageFormat)
		targetfile.Close()
		log.Printf("%d image succesfully resized and returned.\n", http.StatusOK)
	}
}

//Deliver is the startup method that parses configuration files and opens the mongo connection
func Deliver() int {
	configurationFilepath := flag.String("config", "configuration.json", "path to the configuration file")
	serverPort := flag.Int("port", 8000, "the server port where we will serve images")
	host := flag.String("host", "localhost:27017", "the database host with an optional port, localhost would suffice")

	flag.Parse()

	var err error

	Configuration, err = CreateConfigFromFile(*configurationFilepath)

	if err != nil {
		fmt.Printf("Error %s\n", err)
		return -2
	}

	fmt.Printf("Server started. Listening on %d database host is %s\n", *serverPort, *host)

	// in order to simple configure the image server in the proxy configuration of nginx
	// we will be getting every database variable from the request
	serverRoute := "/{database}/{filename}"

	Connection, err = mgo.Dial(*host)

	if err != nil {
		log.Fatal("Cannot connect to database")
		return -1
	}

	Connection.SetMode(mgo.Eventual, true)
	Connection.SetSyncTimeout(0)

	r := mux.NewRouter()
	r.HandleFunc("/", welcomeHandler)
	r.Handle(serverRoute, VarsHandler(imageHandler))

	http.Handle("/", r)

	err = http.ListenAndServe(fmt.Sprintf(":%d", *serverPort), context.ClearHandler(http.DefaultServeMux))

	if err != nil {
		log.Fatal(err)
		return -1
	}

	return 0
}

// just a static welcome handler
func welcomeHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "")
	fmt.Fprintf(w, "
Image Server.
")
	fmt.Fprintf(w, "")
}
<commit_msg>Newrelic license key as optional parameter<commit_after>package server

import (
	"flag"
	"fmt"
	"image"
	"io"
	"log"
	"net/http"
	"runtime"
	"time"

	"github.com/gorilla/context"
	"github.com/gorilla/mux"
	"github.com/yvasiyarov/gorelic"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

//JpegMaximumQuality quality for jpeg compression
const JpegMaximumQuality = 100

//ImageCacheDuration caching time for images
const ImageCacheDuration = 315360000

//Connection is the mgo database connection
var Connection *mgo.Session

//Configuration is the images  configuration
var Configuration *Config

// VarsHandler is a simple wrapper so the request params can be injected into the main handler
type VarsHandler func(http.ResponseWriter, *http.Request, *ServerConfiguration)

// ServeHTTP wraps the imageHandler function and validates request parameters
// in order to create a ServerConfiguration object
func (h VarsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	requestConfig, validateError := CreateConfigurationFromVars(r, vars)

	if validateError != nil {
		log.Printf("%d invalid request parameters given.\n", http.StatusNotFound)
		w.WriteHeader(http.StatusNotFound)
		return
	}

	h(w, r, requestConfig)
}

// addImageMetaData adds data to the image the image
func addImageMetaData(targetImage *mgo.GridFile, imageData image.Image, imageFormat string, originalImage *mgo.GridFile, entry *Entry) {
	width := imageData.Bounds().Dx()
	height := imageData.Bounds().Dy()
	originalRef := mgo.DBRef{"fs.files", originalImage.Id(), ""}

	metadata := bson.M{
		"width":            width,
		"height":           height,
		"original":         originalRef,
		"originalFilename": originalImage.Name(),
		"resizeType":       entry.Type,
		"size":             fmt.Sprintf("%dx%d", entry.Width, entry.Height)}

	targetImage.SetContentType(fmt.Sprintf("image/%s", imageFormat))
	targetImage.SetMeta(metadata)
}

// isModified returns true if the file must be delivered, false otherwise.
func isModified(file *mgo.GridFile, header *http.Header) bool {
	md5 := file.MD5()
	modifiedHeader := header.Get("If-Modified-Since")
	modifiedTime := time.Now()

	if modifiedHeader != "" {
		modifiedTime, _ = time.Parse(time.RFC1123, modifiedHeader)
	}

	// normalize upload date to use the same format as the browser
	uploadDate, _ := time.Parse(time.RFC1123, file.UploadDate().Format(time.RFC1123))

	if header.Get("Cache-Control") == "no-cache" {
		log.Printf("Is modified, because caching not enabled.")
		return true
	}

	if uploadDate.After(modifiedTime) {
		log.Printf("Is modified, because upload date after modified date.\n")
		return true
	}

	if md5 != header.Get("If-None-Match") {
		log.Printf("Is modified, because md5 mismatch. %s != %s", md5, header.Get("If-None-Match"))
		return true
	}

	return false
}

// setCacheHeaders sets the cache headers into the http.ResponseWriter
func setCacheHeaders(file *mgo.GridFile, w http.ResponseWriter) {
	w.Header().Set("Etag", file.MD5())
	w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", ImageCacheDuration))
	d, _ := time.ParseDuration(fmt.Sprintf("%ds", ImageCacheDuration))

	expires := file.UploadDate().Add(d)

	w.Header().Set("Last-Modified", file.UploadDate().Format(time.RFC1123))
	w.Header().Set("Expires", expires.Format(time.RFC1123))
	w.Header().Set("Date", file.UploadDate().Format(time.RFC1123))
}

// imageHandler the main handler
func imageHandler(w http.ResponseWriter, r *http.Request, requestConfig *ServerConfiguration) {
	log.Printf("Request on %s", r.URL)

	defer func() {
		runtime.GC()
	}()

	if Connection == nil {
		log.Printf("Connection is not set.")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	if Configuration == nil {
		log.Printf("Configuration object is not set.")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	gridfs := Connection.DB(requestConfig.Database).GridFS("fs")

	resizeEntry, _ := Configuration.GetEntryByName(requestConfig.FormatName)

	var foundImage *mgo.GridFile

	if bson.IsObjectIdHex(requestConfig.Filename) {
		foundImage, _ = FindImageByParentId(requestConfig.Filename, resizeEntry, gridfs)
	} else {
		foundImage, _ = FindImageByParentFilename(requestConfig.Filename, resizeEntry, gridfs)
	}

	// case that we do not want resizing and did not find any image
	if foundImage == nil && resizeEntry == nil {
		log.Printf("%d invalid request parameters given.\n", http.StatusNotFound)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// we found a image but did not want resizing
	if foundImage != nil {
		if !isModified(foundImage, &r.Header) {
			w.WriteHeader(http.StatusNotModified)
			log.Printf("%d Returning cached image.\n", http.StatusNotModified)
			return
		}

		setCacheHeaders(foundImage, w)

		io.Copy(w, foundImage)
		foundImage.Close()
		log.Printf("%d Image found, no resizing.\n", http.StatusOK)
		return
	}

	if foundImage == nil && resizeEntry != nil {
		// generate new image
		if bson.IsObjectIdHex(requestConfig.Filename) {
			fmt.Printf(requestConfig.Filename)
			foundImage, _ = FindImageByParentId(requestConfig.Filename, nil, gridfs)
		} else {
			foundImage, _ = FindImageByParentFilename(requestConfig.Filename, nil, gridfs)
		}

		if foundImage == nil {
			log.Printf("%d Could not find original image.\n", http.StatusNotFound)
			w.WriteHeader(http.StatusNotFound)
			return
		}

		resizedImage, imageFormat, imageErr := ResizeImageFromGridfs(foundImage, resizeEntry)

		// in this case, resizing for this image does not work, therefore, we at least return the original image
		if imageErr != nil {

			// this might be a problem at the moment, go does not support interlaced pngs
			// http://code.google.com/p/go/issues/detail?id=6293
			// at the moment, we return a not found...
			log.Printf("%d image could not be decoded.\n", http.StatusNotFound)
			w.WriteHeader(http.StatusNotFound)
			return
		}

		// return the image to the client if all cache headers could be set
		targetfile, _ := gridfs.Create(GetRandomFilename(imageFormat))

		encodeErr := EncodeImage(targetfile, *resizedImage, imageFormat)

		if targetfile == nil {
			log.Printf("new gridfs file could not be created")
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		if encodeErr != nil {
			log.Fatalf(imageErr.Error())
			w.WriteHeader(http.StatusBadRequest)
			log.Printf("%d image could not be encoded.\n", http.StatusBadRequest)
			return
		}

		addImageMetaData(targetfile, *resizedImage, imageFormat, foundImage, resizeEntry)

		defer targetfile.Close()

		setCacheHeaders(targetfile, w)

		EncodeImage(w, *resizedImage, imageFormat)
		targetfile.Close()
		log.Printf("%d image succesfully resized and returned.\n", http.StatusOK)
	}
}

//Deliver is the startup method that parses configuration files and opens the mongo connection
func Deliver() int {
	configurationFilepath := flag.String("config", "configuration.json", "path to the configuration file")
	serverPort := flag.Int("port", 8000, "the server port where we will serve images")
	host := flag.String("host", "localhost:27017", "the database host with an optional port, localhost would suffice")
	newrelicKey := flag.String("license", "", "your newrelic license key in order to enable monitoring")

	flag.Parse()

	var err error

	Configuration, err = CreateConfigFromFile(*configurationFilepath)

	if err != nil {
		fmt.Printf("Error %s\n", err)
		return -2
	}

	fmt.Printf("Server started. Listening on %d database host is %s\n", *serverPort, *host)

	// in order to simple configure the image server in the proxy configuration of nginx
	// we will be getting every database variable from the request
	serverRoute := "/{database}/{filename}"

	Connection, err = mgo.Dial(*host)

	if err != nil {
		log.Fatal("Cannot connect to database")
		return -1
	}

	if *newrelicKey != "" {
		agent := gorelic.NewAgent()
		agent.NewrelicLicense = *newrelicKey
		agent.Run()
	}

	Connection.SetMode(mgo.Eventual, true)
	Connection.SetSyncTimeout(0)

	r := mux.NewRouter()
	r.HandleFunc("/", welcomeHandler)
	r.Handle(serverRoute, VarsHandler(imageHandler))

	http.Handle("/", r)

	err = http.ListenAndServe(fmt.Sprintf(":%d", *serverPort), context.ClearHandler(http.DefaultServeMux))

	if err != nil {
		log.Fatal(err)
		return -1
	}

	return 0
}

// just a static welcome handler
func welcomeHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "")
	fmt.Fprintf(w, "
Image Server.
")
	fmt.Fprintf(w, "")
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/trinchan/slackbot/Godeps/_workspace/src/github.com/gorilla/schema"
	_ "github.com/trinchan/slackbot/importer"
	"github.com/trinchan/slackbot/robots"
)

func Main(robotMap map[string][]robots.Robot) {
	log.Println("[Info] Starting up with the following Robots: ")
	for key, _ := range robotMap {
		log.Println("  ", key)
	}
	http.HandleFunc("/slack", slashCommandHandler(robotMap))
	http.HandleFunc("/slack_hook", hookHandler(robotMap))
	startServer()
}

func hookHandler(robotMap map[string][]robots.Robot) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseForm()
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		d := schema.NewDecoder()
		command := new(robots.OutgoingWebHook)
		err = d.Decode(command, r.PostForm)
		if err != nil {
			log.Println("Couldn't parse post request:", err)
		}
		if command.Text == "" || command.Token != getOutToken(command.TeamDomain) {
			log.Printf("[DEBUG] Ignoring request from unidentified source: %s - %s - %s", command.Token, r.Host, command.TeamDomain)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		com := strings.TrimPrefix(command.Text, command.TriggerWord+" ")
		c := strings.Split(com, " ")
		command.Robot = c[0]
		command.Text = strings.Join(c[1:], " ")

		log.Printf("[DEBUG] Robot: %s Text: %s", command.Robot, command.Text)

		robots := robots.Robots[command.Robot]
		if len(robots) == 0 {
			jsonResp(w, "No robot for that command yet :(")
			return
		}
		resp := ""
		for _, robot := range robots {
			resp += fmt.Sprintf("\n%s", robot.Run(&command.Payload))
		}
		w.WriteHeader(http.StatusOK)
		jsonResp(w, strings.TrimSpace(resp))
	}
}

func slashCommandHandler(robotMap map[string][]robots.Robot) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseForm()
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		d := schema.NewDecoder()
		command := new(robots.SlashCommand)
		err = d.Decode(command, r.PostForm)
		if err != nil {
			log.Println("Couldn't parse post request:", err)
		}
		if command.Command == "" || command.Token == "" {
			log.Printf("[DEBUG] Ignoring request from unidentified source: %s - %s", command.Token, r.Host)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		command.Robot = command.Command[1:]

		if token := getSlackToken(command.Robot); token != "" && token != command.Token {
			log.Printf("[DEBUG] Ignoring request from unidentified source: %s - %s", command.Token, r.Host)
			w.WriteHeader(http.StatusBadRequest)
		}
		robots := robots.Robots[command.Robot]
		if len(robots) == 0 {
			plainResp(w, "No robot for that command yet :(")
			return
		}
		resp := ""
		for _, robot := range robots {
			resp += fmt.Sprintf("\n%s", robot.Run(&command.Payload))
		}
		plainResp(w, strings.TrimSpace(resp))
	}
}

func jsonResp(w http.ResponseWriter, msg string) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	resp := map[string]string{"text": msg}
	r, err := json.Marshal(resp)
	if err != nil {
		log.Println("Couldn't marshal hook response:", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.Write(r)
}

func plainResp(w http.ResponseWriter, msg string) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.Write([]byte(msg))
}

func getSlackToken(robot string) string {
	return os.Getenv(fmt.Sprintf("%s_SLACK_TOKEN", strings.ToUpper(robot)))
}

func getOutToken(teamDomain string) string {
	return os.Getenv(fmt.Sprintf("%s_OUT_TOKEN", strings.ToUpper(teamDomain)))
}

func startServer() {
	port := os.Getenv("PORT")
	if port == "" {
		log.Fatal("PORT not set")
	}
	log.Printf("Starting HTTP server on %s", port)
	err := http.ListenAndServe(":"+port, nil)
	if err != nil {
		log.Fatal("Server start error: ", err)
	}
}
<commit_msg>updates<commit_after>package server

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/trinchan/slackbot/Godeps/_workspace/src/github.com/gorilla/schema"
	_ "github.com/trinchan/slackbot/importer"
	"github.com/trinchan/slackbot/robots"
)

func Main(robotMap map[string][]robots.Robot) {
	log.Println("[Info] Starting up with the following Robots: ")
	for key, _ := range robotMap {
		log.Println("  ", key)
	}
	http.HandleFunc("/slack", slashCommandHandler(robotMap))
	http.HandleFunc("/slack_hook", hookHandler(robotMap))
	startServer()
}

func hookHandler(robotMap map[string][]robots.Robot) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseForm()
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		d := schema.NewDecoder()
		command := new(robots.OutgoingWebHook)
		err = d.Decode(command, r.PostForm)
		if err != nil {
			log.Println("Couldn't parse post request:", err)
		}
		if command.Text == "" || command.Token != getOutToken(command.TeamDomain) {
			log.Printf("[DEBUG] Ignoring request from unidentified source: %s - %s - %s", command.Token, r.Host, command.TeamDomain)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		com := strings.TrimPrefix(command.Text, command.TriggerWord)
		c := strings.Split(com, " ")
		command.Robot = c[0]
		command.Text = strings.Join(c[1:], " ")

		log.Printf("[DEBUG] Robot: %s Text: %s", command.Robot, command.Text)

		robots := robots.Robots[command.Robot]
		if len(robots) == 0 {
			jsonResp(w, "No robot for that command yet :(")
			return
		}
		resp := ""
		for _, robot := range robots {
			resp += fmt.Sprintf("\n%s", robot.Run(&command.Payload))
		}
		w.WriteHeader(http.StatusOK)
		jsonResp(w, strings.TrimSpace(resp))
	}
}

func slashCommandHandler(robotMap map[string][]robots.Robot) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseForm()
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		d := schema.NewDecoder()
		command := new(robots.SlashCommand)
		err = d.Decode(command, r.PostForm)
		if err != nil {
			log.Println("Couldn't parse post request:", err)
		}
		if command.Command == "" || command.Token == "" {
			log.Printf("[DEBUG] Ignoring request from unidentified source: %s - %s", command.Token, r.Host)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		command.Robot = command.Command[1:]

		if token := getSlackToken(command.Robot); token != "" && token != command.Token {
			log.Printf("[DEBUG] Ignoring request from unidentified source: %s - %s", command.Token, r.Host)
			w.WriteHeader(http.StatusBadRequest)
		}
		robots := robots.Robots[command.Robot]
		if len(robots) == 0 {
			plainResp(w, "No robot for that command yet :(")
			return
		}
		resp := ""
		for _, robot := range robots {
			resp += fmt.Sprintf("\n%s", robot.Run(&command.Payload))
		}
		plainResp(w, strings.TrimSpace(resp))
	}
}

func jsonResp(w http.ResponseWriter, msg string) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	resp := map[string]string{"text": msg}
	r, err := json.Marshal(resp)
	if err != nil {
		log.Println("Couldn't marshal hook response:", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.Write(r)
}

func plainResp(w http.ResponseWriter, msg string) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.Write([]byte(msg))
}

func getSlackToken(robot string) string {
	return os.Getenv(fmt.Sprintf("%s_SLACK_TOKEN", strings.ToUpper(robot)))
}

func getOutToken(teamDomain string) string {
	return os.Getenv(fmt.Sprintf("%s_OUT_TOKEN", strings.ToUpper(teamDomain)))
}

func startServer() {
	port := os.Getenv("PORT")
	if port == "" {
		log.Fatal("PORT not set")
	}
	log.Printf("Starting HTTP server on %s", port)
	err := http.ListenAndServe(":"+port, nil)
	if err != nil {
		log.Fatal("Server start error: ", err)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"math/rand"
	"net"
	"net/http"
	"runtime"
	"strconv"
	"time"

	"github.com/influxdata/chronograf"
	"github.com/influxdata/chronograf/bolt"
	"github.com/influxdata/chronograf/canned"
	"github.com/influxdata/chronograf/influx"
	"github.com/influxdata/chronograf/layouts"
	clog "github.com/influxdata/chronograf/log"
	"github.com/influxdata/chronograf/uuid"
	client "github.com/influxdata/usage-client/v1"
	"github.com/tylerb/graceful"
)

var (
	startTime time.Time
	basepath  string
)

func init() {
	startTime = time.Now().UTC()
}

// Server for the chronograf API
type Server struct {
	Host string `long:"host" description:"the IP to listen on" default:"0.0.0.0" env:"HOST"`
	Port int    `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" default:"8888" env:"PORT"`

	/* TODO: add in support for TLS
	TLSHost           string         `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
	TLSPort           int            `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"`
	TLSCertificate    flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"`
	TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure conections" env:"TLS_PRIVATE_KEY"`
	*/

	Develop            bool     `short:"d" long:"develop" description:"Run server in develop mode."`
	BoltPath           string   `short:"b" long:"bolt-path" description:"Full path to boltDB file (/var/lib/chronograf/chronograf-v1.db)" env:"BOLT_PATH" default:"chronograf-v1.db"`
	CannedPath         string   `short:"c" long:"canned-path" description:"Path to directory of pre-canned application layouts (/usr/share/chronograf/canned)" env:"CANNED_PATH" default:"canned"`
	TokenSecret        string   `short:"t" long:"token-secret" description:"Secret to sign tokens" env:"TOKEN_SECRET"`
	GithubClientID     string   `short:"i" long:"github-client-id" description:"Github Client ID for OAuth 2 support" env:"GH_CLIENT_ID"`
	GithubClientSecret string   `short:"s" long:"github-client-secret" description:"Github Client Secret for OAuth 2 support" env:"GH_CLIENT_SECRET"`
	GithubOrgs         []string `short:"o" long:"github-organization" description:"Github organization user is required to have active membership" env:"GH_ORGS" env-delim:","`
	ReportingDisabled  bool     `short:"r" long:"reporting-disabled" description:"Disable reporting of usage stats (os,arch,version,cluster_id,uptime) once every 24hr" env:"REPORTING_DISABLED"`
	LogLevel           string   `short:"l" long:"log-level" value-name:"choice" choice:"debug" choice:"info" choice:"warn" choice:"error" choice:"fatal" choice:"panic" default:"info" description:"Set the logging level" env:"LOG_LEVEL"`
	Basepath           string   `short:"p" long:"basepath" description:"A URL path prefix under which all chronograf routes will be mounted" env:"BASE_PATH"`
	ShowVersion        bool     `short:"v" long:"version" description:"Show Chronograf version info"`
	BuildInfo          BuildInfo
	Listener           net.Listener
	handler            http.Handler
}

// BuildInfo is sent to the usage client to track versions and commits
type BuildInfo struct {
	Version string
	Commit  string
}

func (s *Server) useAuth() bool {
	return s.TokenSecret != "" && s.GithubClientID != "" && s.GithubClientSecret != ""
}

// Serve starts and runs the chronograf server
func (s *Server) Serve() error {
	logger := clog.New(clog.ParseLevel(s.LogLevel))
	service := openService(s.BoltPath, s.CannedPath, logger, s.useAuth())
	basepath = s.Basepath
	s.handler = NewMux(MuxOpts{
		Develop:            s.Develop,
		TokenSecret:        s.TokenSecret,
		GithubClientID:     s.GithubClientID,
		GithubClientSecret: s.GithubClientSecret,
		GithubOrgs:         s.GithubOrgs,
		Logger:             logger,
		UseAuth:            s.useAuth(),
	}, service)

	s.handler = Version(s.BuildInfo.Version, s.handler)

	var err error
	s.Listener, err = net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)))
	if err != nil {
		logger.
			WithField("component", "server").
			Error(err)
		return err
	}

	httpServer := &graceful.Server{Server: new(http.Server)}
	httpServer.SetKeepAlivesEnabled(true)
	httpServer.TCPKeepAlive = 5 * time.Second
	httpServer.Handler = s.handler

	if !s.ReportingDisabled {
		go reportUsageStats(s.BuildInfo, logger)
	}

	logger.
		WithField("component", "server").
		Info("Serving chronograf at http://", s.Listener.Addr())

	if err := httpServer.Serve(s.Listener); err != nil {
		logger.
			WithField("component", "server").
			Error(err)
		return err
	}

	logger.
		WithField("component", "server").
		Info("Stopped serving chronograf at http://", s.Listener.Addr())

	return nil
}

func openService(boltPath, cannedPath string, logger chronograf.Logger, useAuth bool) Service {
	db := bolt.NewClient()
	db.Path = boltPath
	if err := db.Open(); err != nil {
		logger.
			WithField("component", "boltstore").
			Fatal("Unable to open boltdb; is there a chronograf already running?  ", err)
	}

	// These apps are those handled from a directory
	apps := canned.NewApps(cannedPath, &uuid.V4{}, logger)
	// These apps are statically compiled into chronograf
	binApps := &canned.BinLayoutStore{
		Logger: logger,
	}

	// Acts as a front-end to both the bolt layouts, filesystem layouts and binary statically compiled layouts.
	// The idea here is that these stores form a hierarchy in which each is tried sequentially until
	// the operation has success.  So, the database is preferred over filesystem over binary data.
	layouts := &layouts.MultiLayoutStore{
		Stores: []chronograf.LayoutStore{
			db.LayoutStore,
			apps,
			binApps,
		},
	}

	return Service{
		SourcesStore: db.SourcesStore,
		ServersStore: db.ServersStore,
		UsersStore:   db.UsersStore,
		TimeSeries: &influx.Client{
			Logger: logger,
		},
		LayoutStore:     layouts,
		DashboardsStore: db.DashboardsStore,
		AlertRulesStore: db.AlertsStore,
		Logger:          logger,
		UseAuth:         useAuth,
	}
}

// reportUsageStats starts periodic server reporting.
func reportUsageStats(bi BuildInfo, logger chronograf.Logger) {
	rand.Seed(time.Now().UTC().UnixNano())
	serverID := strconv.FormatUint(uint64(rand.Int63()), 10)
	reporter := client.New("")
	u := &client.Usage{
		Product: "chronograf-ng",
		Data: []client.UsageData{
			{
				Values: client.Values{
					"os":         runtime.GOOS,
					"arch":       runtime.GOARCH,
					"version":    bi.Version,
					"cluster_id": serverID,
					"uptime":     time.Since(startTime).Seconds(),
				},
			},
		},
	}
	l := logger.WithField("component", "usage").
		WithField("reporting_addr", reporter.URL).
		WithField("freq", "24h").
		WithField("stats", "os,arch,version,cluster_id,uptime")
	l.Info("Reporting usage stats")
	_, _ = reporter.Save(u)

	ticker := time.NewTicker(24 * time.Hour)
	defer ticker.Stop()
	for {
		<-ticker.C
		l.Debug("Reporting usage stats")
		go reporter.Save(u)
	}
}
<commit_msg>Add TLS w/ env vars TLS_CERTIFICATE/TLS_PRIVATE_KEY<commit_after>package server

import (
	"crypto/tls"
	"math/rand"
	"net"
	"net/http"
	"runtime"
	"strconv"
	"time"

	"github.com/influxdata/chronograf"
	"github.com/influxdata/chronograf/bolt"
	"github.com/influxdata/chronograf/canned"
	"github.com/influxdata/chronograf/influx"
	"github.com/influxdata/chronograf/layouts"
	clog "github.com/influxdata/chronograf/log"
	"github.com/influxdata/chronograf/uuid"
	client "github.com/influxdata/usage-client/v1"
	flags "github.com/jessevdk/go-flags"
	"github.com/tylerb/graceful"
)

var (
	startTime time.Time
	basepath  string
)

func init() {
	startTime = time.Now().UTC()
}

// Server for the chronograf API
type Server struct {
	Host string `long:"host" description:"The IP to listen on" default:"0.0.0.0" env:"HOST"`
	Port int    `long:"port" description:"The port to listen on for insecure connections, defaults to a random value" default:"8888" env:"PORT"`

	Cert flags.Filename `long:"cert" description:"Path to PEM encoded public key certificate. " env:"TLS_CERTIFICATE"`
	Key  flags.Filename `long:"key" description:"Path to private key associated with given certificate. " env:"TLS_PRIVATE_KEY"`

	Develop            bool     `short:"d" long:"develop" description:"Run server in develop mode."`
	BoltPath           string   `short:"b" long:"bolt-path" description:"Full path to boltDB file (/var/lib/chronograf/chronograf-v1.db)" env:"BOLT_PATH" default:"chronograf-v1.db"`
	CannedPath         string   `short:"c" long:"canned-path" description:"Path to directory of pre-canned application layouts (/usr/share/chronograf/canned)" env:"CANNED_PATH" default:"canned"`
	TokenSecret        string   `short:"t" long:"token-secret" description:"Secret to sign tokens" env:"TOKEN_SECRET"`
	GithubClientID     string   `short:"i" long:"github-client-id" description:"Github Client ID for OAuth 2 support" env:"GH_CLIENT_ID"`
	GithubClientSecret string   `short:"s" long:"github-client-secret" description:"Github Client Secret for OAuth 2 support" env:"GH_CLIENT_SECRET"`
	GithubOrgs         []string `short:"o" long:"github-organization" description:"Github organization user is required to have active membership" env:"GH_ORGS" env-delim:","`
	ReportingDisabled  bool     `short:"r" long:"reporting-disabled" description:"Disable reporting of usage stats (os,arch,version,cluster_id,uptime) once every 24hr" env:"REPORTING_DISABLED"`
	LogLevel           string   `short:"l" long:"log-level" value-name:"choice" choice:"debug" choice:"info" choice:"warn" choice:"error" choice:"fatal" choice:"panic" default:"info" description:"Set the logging level" env:"LOG_LEVEL"`
	Basepath           string   `short:"p" long:"basepath" description:"A URL path prefix under which all chronograf routes will be mounted" env:"BASE_PATH"`
	ShowVersion        bool     `short:"v" long:"version" description:"Show Chronograf version info"`
	BuildInfo          BuildInfo
	Listener           net.Listener
	handler            http.Handler
}

// BuildInfo is sent to the usage client to track versions and commits
type BuildInfo struct {
	Version string
	Commit  string
}

func (s *Server) useAuth() bool {
	return s.TokenSecret != "" && s.GithubClientID != "" && s.GithubClientSecret != ""
}

func (s *Server) useTLS() bool {
	return s.Cert != ""
}

func (s *Server) NewListener() (net.Listener, error) {
	addr := net.JoinHostPort(s.Host, strconv.Itoa(s.Port))
	if !s.useTLS() {
		listener, err := net.Listen("tcp", addr)
		if err != nil {
			return nil, err
		}
		return listener, nil
	}

	// If no key specified, therefore, we assume it is in the cert
	if s.Key == "" {
		s.Key = s.Cert
	}

	cert, err := tls.LoadX509KeyPair(string(s.Cert), string(s.Key))
	if err != nil {
		return nil, err
	}

	listener, err := tls.Listen("tcp", addr, &tls.Config{
		Certificates: []tls.Certificate{cert},
	})

	if err != nil {
		return nil, err
	}
	return listener, nil
}

// Serve starts and runs the chronograf server
func (s *Server) Serve() error {
	logger := clog.New(clog.ParseLevel(s.LogLevel))
	service := openService(s.BoltPath, s.CannedPath, logger, s.useAuth())
	basepath = s.Basepath

	s.handler = NewMux(MuxOpts{
		Develop:            s.Develop,
		TokenSecret:        s.TokenSecret,
		GithubClientID:     s.GithubClientID,
		GithubClientSecret: s.GithubClientSecret,
		GithubOrgs:         s.GithubOrgs,
		Logger:             logger,
		UseAuth:            s.useAuth(),
	}, service)

	// Add chronograf's version header to all requests
	s.handler = Version(s.BuildInfo.Version, s.handler)

	if s.useTLS() {
		// Add HSTS to instruct all browsers to change from http to https
		s.handler = HSTS(s.handler)
	}

	listener, err := s.NewListener()
	if err != nil {
		logger.
			WithField("component", "server").
			Error(err)
		return err
	}
	s.Listener = listener

	httpServer := &graceful.Server{Server: new(http.Server)}
	httpServer.SetKeepAlivesEnabled(true)
	httpServer.TCPKeepAlive = 5 * time.Second
	httpServer.Handler = s.handler

	if !s.ReportingDisabled {
		go reportUsageStats(s.BuildInfo, logger)
	}
	scheme := "http"
	if s.useTLS() {
		scheme = "https"
	}
	logger.
		WithField("component", "server").
		Info("Serving chronograf at ", scheme, "://", s.Listener.Addr())

	if err := httpServer.Serve(s.Listener); err != nil {
		logger.
			WithField("component", "server").
			Error(err)
		return err
	}

	logger.
		WithField("component", "server").
		Info("Stopped serving chronograf at ", scheme, "://", s.Listener.Addr())

	return nil
}

func openService(boltPath, cannedPath string, logger chronograf.Logger, useAuth bool) Service {
	db := bolt.NewClient()
	db.Path = boltPath
	if err := db.Open(); err != nil {
		logger.
			WithField("component", "boltstore").
			Fatal("Unable to open boltdb; is there a chronograf already running?  ", err)
	}

	// These apps are those handled from a directory
	apps := canned.NewApps(cannedPath, &uuid.V4{}, logger)
	// These apps are statically compiled into chronograf
	binApps := &canned.BinLayoutStore{
		Logger: logger,
	}

	// Acts as a front-end to both the bolt layouts, filesystem layouts and binary statically compiled layouts.
	// The idea here is that these stores form a hierarchy in which each is tried sequentially until
	// the operation has success.  So, the database is preferred over filesystem over binary data.
	layouts := &layouts.MultiLayoutStore{
		Stores: []chronograf.LayoutStore{
			db.LayoutStore,
			apps,
			binApps,
		},
	}

	return Service{
		SourcesStore: db.SourcesStore,
		ServersStore: db.ServersStore,
		UsersStore:   db.UsersStore,
		TimeSeries: &influx.Client{
			Logger: logger,
		},
		LayoutStore:     layouts,
		DashboardsStore: db.DashboardsStore,
		AlertRulesStore: db.AlertsStore,
		Logger:          logger,
		UseAuth:         useAuth,
	}
}

// reportUsageStats starts periodic server reporting.
func reportUsageStats(bi BuildInfo, logger chronograf.Logger) {
	rand.Seed(time.Now().UTC().UnixNano())
	serverID := strconv.FormatUint(uint64(rand.Int63()), 10)
	reporter := client.New("")
	u := &client.Usage{
		Product: "chronograf-ng",
		Data: []client.UsageData{
			{
				Values: client.Values{
					"os":         runtime.GOOS,
					"arch":       runtime.GOARCH,
					"version":    bi.Version,
					"cluster_id": serverID,
					"uptime":     time.Since(startTime).Seconds(),
				},
			},
		},
	}
	l := logger.WithField("component", "usage").
		WithField("reporting_addr", reporter.URL).
		WithField("freq", "24h").
		WithField("stats", "os,arch,version,cluster_id,uptime")
	l.Info("Reporting usage stats")
	_, _ = reporter.Save(u)

	ticker := time.NewTicker(24 * time.Hour)
	defer ticker.Stop()
	for {
		<-ticker.C
		l.Debug("Reporting usage stats")
		go reporter.Save(u)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"net/http"

	"github.com/gilcrest/errors"
	"github.com/gilcrest/srvr"
	"github.com/gilcrest/srvr/datastore"
	"github.com/rs/zerolog"
)

// Server struct is a pointer to srvr.Server
// allows additional local methods to be added
type Server struct {
	*srvr.Server
}

// handleRespHeader middleware is used to add standard HTTP response headers
func (s *Server) handleRespHeader(h http.Handler) http.Handler {
	return http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			w.Header().Add("Content-Type", "application/json")
			h.ServeHTTP(w, r) // call original
		})
}

// NewServer is a constructor for the Server struct
// Sets up the struct and registers routes
func NewServer(lvl zerolog.Level) (*Server, error) {
	const op errors.Op = "server.NewServer"

	srvr, err := srvr.NewServer(lvl)
	if err != nil {
		return nil, errors.E(op, err)
	}

	server := &Server{srvr}

	err = server.DS.Option(datastore.InitLogDB())
	if err != nil {
		return nil, errors.E(op, err)
	}

	err = server.routes()
	if err != nil {
		return nil, errors.E(op, err)
	}

	return server, nil
}
<commit_msg>Added more doc comments<commit_after>package server

import (
	"net/http"

	"github.com/gilcrest/errors"
	"github.com/gilcrest/srvr"
	"github.com/gilcrest/srvr/datastore"
	"github.com/rs/zerolog"
)

// Server struct is a pointer to srvr.Server
// allows additional local methods to be added
type Server struct {
	*srvr.Server
}

// handleRespHeader middleware is used to add standard HTTP response headers
func (s *Server) handleRespHeader(h http.Handler) http.Handler {
	return http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			w.Header().Add("Content-Type", "application/json")
			h.ServeHTTP(w, r) // call original
		})
}

// NewServer is a constructor for the Server struct
// Sets up the struct and registers routes
func NewServer(lvl zerolog.Level) (*Server, error) {
	const op errors.Op = "server.NewServer"

	// call constructor for Server struct from srvr module
	srvr, err := srvr.NewServer(lvl)
	if err != nil {
		return nil, errors.E(op, err)
	}

	// Use composition to make srvr.Server struct part of
	// local Server struct
	server := &Server{srvr}

	// Use Datastore(DS) Option method to initialize logging
	// database
	err = server.DS.Option(datastore.InitLogDB())
	if err != nil {
		return nil, errors.E(op, err)
	}

	// routes registers handlers to the Server router
	err = server.routes()
	if err != nil {
		return nil, errors.E(op, err)
	}

	return server, nil
}
<|endoftext|>


<commit_before>package main

import "net/http"
import "fmt"
import "html"
import "time"
import "io/ioutil"
import "strings"
import "./players"
func getPlayerOfIp(ip string) players.NetworkPlayer {
	for _, plr := range networkPlayerList {
		plrIp := plr.GetIp()
		if plrIp == ip[:strings.Index(ip, ":")] {
			return plr
		}
	}
	//for now, add a player to the list and return it
	networkPlayerList=append(networkPlayerList,players.NewNetworkPlayer("uwe",3,ip))
	return networkPlayerList[len(networkPlayerList)-1]
}
func fileServe(w http.ResponseWriter, r *http.Request) {
	b,_ := ioutil.ReadFile("client/"+html.EscapeString(r.URL.Path)[1:])
	if r.URL.Path[len(r.URL.Path)-4:] == ".css" {
		w.Header().Set("Content-Type", "text/css; charset=utf-8")
	}
	w.Write(b)
}
func eventFunc(w http.ResponseWriter, r *http.Request) {
	plr := getPlayerOfIp(r.RemoteAddr)
	for len(plr.GetDataToBeSent()) == 0 {
		time.Sleep(time.Second / 2)
	}
	_, err := w.Write(plr.GetDataToBeSent()[0])
	if err == nil {
		plr.RemoveASentData()
	}
}
func chatFunc(w http.ResponseWriter, r *http.Request) {
	chat := ""
	r.ParseForm()
	for key := range r.Form {
		chat=key
		break
	}

	chat = strings.Replace(chat,"&","&",-1)
	chat = strings.Replace(chat,"<","<",-1)
	chat = strings.Replace(chat,">",">",-1)

	plr := getPlayerOfIp(r.RemoteAddr)
	data := []byte("CHAT"+plr.GetName()+": "+chat)
	for _, plr2 := range networkPlayerList {
		plr2.SendData(data)
	}
}
func setupServer(quitChan chan struct{}) {
	http.HandleFunc("/ordos.html",fileServe)
	http.HandleFunc("/ordos.js",fileServe)
	http.HandleFunc("/ordos.css",fileServe)
	http.HandleFunc("/event",eventFunc)
	http.HandleFunc("/chat",chatFunc)
	go http.ListenAndServe(":8081",nil)
	fmt.Println("Server loaded")
}
<commit_msg>almost mang<commit_after>package main

import "net/http"
import "fmt"
import "html"
import "time"
import "io/ioutil"
import "strings"
import "./players"
func getPlayerOfIp(ip string) players.NetworkPlayer {
	for _, plr := range networkPlayerList {
		plrIp := plr.GetIp()
		if plrIp == ip[:strings.Index(ip, ":")] {
			return plr
		}
	}
	//for now, add a player to the list and return it
	networkPlayerList=append(networkPlayerList,players.NewNetworkPlayer("uwe",3,ip))
	return networkPlayerList[len(networkPlayerList)-1]
}
func fileServe(w http.ResponseWriter, r *http.Request) {
	b,_ := ioutil.ReadFile("client/"+html.EscapeString(r.URL.Path)[1:])
	if r.URL.Path[len(r.URL.Path)-4:] == ".css" {
		w.Header().Set("Content-Type", "text/css; charset=utf-8")
	}
	w.Write(b)
}
func eventFunc(w http.ResponseWriter, r *http.Request) {
	plr := getPlayerOfIp(r.RemoteAddr)
	for len(plr.GetDataToBeSent()) == 0 {
		time.Sleep(time.Second / 2)
	}
	_, err := w.Write(plr.GetDataToBeSent()[0])
	if err == nil {
		plr.RemoveASentData()
	}
}
func chatFunc(w http.ResponseWriter, r *http.Request) {
	chat := ""
	r.ParseForm()
	for key := range r.Form {
		chat=key
		break
	}

	chat = strings.Replace(chat,"&","&",-1)
	chat = strings.Replace(chat,"<","<",-1)
	chat = strings.Replace(chat,">",">",-1)

	fmt.Println(chat)

	plr := getPlayerOfIp(r.RemoteAddr)
	data := []byte("CHAT"+plr.GetName()+": "+chat)
	for _, plr2 := range networkPlayerList {
		plr2.SendData(data)
	}
}
func setupServer(quitChan chan struct{}) {
	http.HandleFunc("/ordos.html",fileServe)
	http.HandleFunc("/ordos.js",fileServe)
	http.HandleFunc("/ordos.css",fileServe)
	http.HandleFunc("/event",eventFunc)
	http.HandleFunc("/chat",chatFunc)
	go http.ListenAndServe(":8081",nil)
	fmt.Println("Server loaded")
}
<|endoftext|>


<commit_before>package server

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/micro/cli/v2"
	"github.com/micro/go-micro/v2"
	"github.com/micro/go-micro/v2/client"
	"github.com/micro/go-micro/v2/cmd"
	log "github.com/micro/go-micro/v2/logger"
	gorun "github.com/micro/go-micro/v2/runtime"
	"github.com/micro/go-micro/v2/util/file"
	ccli "github.com/micro/micro/v2/client/cli"
	"github.com/micro/micro/v2/internal/update"
)

var (
	// list of services managed
	services = []string{
		// runtime services
		"config",   // ????
		"auth",     // :8010
		"network",  // :8085
		"runtime",  // :8088
		"registry", // :8000
		"broker",   // :8001
		"store",    // :8002
		"debug",    // :????
		"proxy",    // :8081
		"api",      // :8080
		"web",      // :8082
	}
)

var (
	// Name of the server microservice
	Name = "go.micro.server"
	// Address is the router microservice bind address
	Address = ":10001"
)

// upload is used for file uploads to the server
func upload(ctx *cli.Context, args []string) ([]byte, error) {
	if ctx.Args().Len() == 0 {
		return nil, errors.New("Required filename to upload")
	}

	filename := ctx.Args().Get(0)
	localfile := ctx.Args().Get(1)

	fileClient := file.New("go.micro.server", client.DefaultClient)
	return nil, fileClient.Upload(filename, localfile)
}

func Commands(options ...micro.Option) []*cli.Command {
	command := &cli.Command{
		Name:  "server",
		Usage: "Run the micro server",
		Description: `Launching the micro server ('micro server') will enable one to connect to it by
		setting the appropriate Micro environment (see 'micro env' && 'micro env --help') commands.`,
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "address",
				Usage:   "Set the micro server address :10001",
				EnvVars: []string{"MICRO_SERVER_ADDRESS"},
			},
			&cli.BoolFlag{
				Name:  "peer",
				Usage: "Peer with the global network to share services",
			},
			&cli.StringFlag{
				Name:    "profile",
				Usage:   "Set the runtime profile to use for services e.g local, kubernetes, platform",
				EnvVars: []string{"MICRO_RUNTIME_PROFILE"},
			},
		},
		Action: func(ctx *cli.Context) error {
			Run(ctx)
			return nil
		},
		Subcommands: []*cli.Command{{
			Name:  "file",
			Usage: "Move files between your local machine and the server",
			Subcommands: []*cli.Command{
				{
					Name:   "upload",
					Action: ccli.Print(upload),
				},
			},
		}},
	}

	for _, p := range Plugins() {
		if cmds := p.Commands(); len(cmds) > 0 {
			command.Subcommands = append(command.Subcommands, cmds...)
		}

		if flags := p.Flags(); len(flags) > 0 {
			command.Flags = append(command.Flags, flags...)
		}
	}

	return []*cli.Command{command}
}

// Run runs the entire platform
func Run(context *cli.Context) error {
	log.Init(log.WithFields(map[string]interface{}{"service": "micro"}))

	if context.Args().Len() > 0 {
		cli.ShowSubcommandHelp(context)
		os.Exit(1)
	}

	// get the network flag
	peer := context.Bool("peer")

	// pass through the environment
	// By default we want a file store when we run micro server.
	// This will get overridden if user has set their own MICRO_STORE env var or passed in --store
	env := []string{"MICRO_STORE=file"}
	profile := context.String("profile")
	if len(profile) == 0 {
		profile = "server"
	}

	env = append(env, "MICRO_RUNTIME_PROFILE="+profile)
	env = append(env, os.Environ()...)

	// connect to the network if specified
	if peer {
		log.Info("Setting global network")

		if v := os.Getenv("MICRO_NETWORK_NODES"); len(v) == 0 {
			// set the resolver to use https://micro.mu/network
			env = append(env, "MICRO_NETWORK_NODES=network.micro.mu")
			log.Info("Setting default network micro.mu")
		}
		if v := os.Getenv("MICRO_NETWORK_TOKEN"); len(v) == 0 {
			// set the network token
			env = append(env, "MICRO_NETWORK_TOKEN=micro.mu")
			log.Info("Setting default network token")
		}
	}

	log.Info("Loading core services")

	// create new micro runtime
	muRuntime := cmd.DefaultCmd.Options().Runtime

	// Use default update notifier
	if context.Bool("auto_update") {
		updateURL := context.String("update_url")
		if len(updateURL) == 0 {
			updateURL = update.DefaultURL
		}

		options := []gorun.Option{
			gorun.WithScheduler(update.NewScheduler(updateURL, fmt.Sprintf("%d", time.Now().Unix()))),
		}
		(*muRuntime).Init(options...)
	}

	for _, service := range services {
		name := service

		if namespace := context.String("namespace"); len(namespace) > 0 {
			name = fmt.Sprintf("%s.%s", namespace, service)
		}

		log.Infof("Registering %s", name)
		// @todo this is a hack
		envs := env
		var isClient bool
		switch service {
		case "proxy", "web", "api", "bot", "cli":
			isClient = true
			envs = append(envs, "MICRO_AUTH=service")
			envs = append(envs, "MICRO_REGISTRY=service")
		}

		cmdArgs := []string{}
		if !isClient {
			// run non-clients as "micro service [name]"
			cmdArgs = append(cmdArgs, "service")
		}

		// we want to pass through the global args so go up one level in the context lineage
		if len(context.Lineage()) > 1 {
			globCtx := context.Lineage()[1]
			for _, f := range globCtx.FlagNames() {
				cmdArgs = append(cmdArgs, "--"+f, context.String(f))
			}
		}
		cmdArgs = append(cmdArgs, service)

		// runtime based on environment we run the service in
		args := []gorun.CreateOption{
			gorun.WithCommand(os.Args[0]),
			gorun.WithArgs(cmdArgs...),
			gorun.WithEnv(envs),
			gorun.WithOutput(os.Stdout),
			gorun.WithRetries(10),
			gorun.CreateImage("micro/micro"),
		}

		// NOTE: we use Version right now to check for the latest release
		muService := &gorun.Service{Name: name, Version: fmt.Sprintf("%d", time.Now().Unix())}
		if err := (*muRuntime).Create(muService, args...); err != nil {
			log.Errorf("Failed to create runtime environment: %v", err)
			return err
		}
	}

	log.Info("Starting service runtime")

	// start the runtime
	if err := (*muRuntime).Start(); err != nil {
		log.Fatal(err)
		return err
	}

	log.Info("Service runtime started")

	// TODO: should we launch the console?
	// start the console
	// cli.Init(context)

	server := micro.NewService(
		micro.Name(Name),
		micro.Address(Address),
	)

	// @todo make this configurable
	uploadDir := filepath.Join(os.TempDir(), "micro", "uploads")
	os.MkdirAll(uploadDir, 0777)
	file.RegisterHandler(server.Server(), uploadDir)
	// start the server
	server.Run()

	log.Info("Stopping service runtime")

	// stop all the things
	if err := (*muRuntime).Stop(); err != nil {
		log.Fatal(err)
		return err
	}

	log.Info("Service runtime shutdown")

	// exit success
	os.Exit(0)
	return nil
}
<commit_msg>tidy up server<commit_after>package server

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/micro/cli/v2"
	"github.com/micro/go-micro/v2"
	"github.com/micro/go-micro/v2/client"
	"github.com/micro/go-micro/v2/cmd"
	log "github.com/micro/go-micro/v2/logger"
	gorun "github.com/micro/go-micro/v2/runtime"
	"github.com/micro/go-micro/v2/util/file"
	ccli "github.com/micro/micro/v2/client/cli"
	"github.com/micro/micro/v2/internal/update"
	"github.com/micro/micro/v2/service/runtime/profile"
)

var (
	// list of services managed
	services = []string{
		// runtime services
		"config",   // ????
		"auth",     // :8010
		"network",  // :8085
		"runtime",  // :8088
		"registry", // :8000
		"broker",   // :8001
		"store",    // :8002
		"debug",    // :????
		"proxy",    // :8081
		"api",      // :8080
		"web",      // :8082
	}
)

var (
	// Name of the server microservice
	Name = "go.micro.server"
	// Address is the router microservice bind address
	Address = ":10001"
)

// upload is used for file uploads to the server
func upload(ctx *cli.Context, args []string) ([]byte, error) {
	if ctx.Args().Len() == 0 {
		return nil, errors.New("Required filename to upload")
	}

	filename := ctx.Args().Get(0)
	localfile := ctx.Args().Get(1)

	fileClient := file.New("go.micro.server", client.DefaultClient)
	return nil, fileClient.Upload(filename, localfile)
}

func Commands(options ...micro.Option) []*cli.Command {
	command := &cli.Command{
		Name:  "server",
		Usage: "Run the micro server",
		Description: `Launching the micro server ('micro server') will enable one to connect to it by
		setting the appropriate Micro environment (see 'micro env' && 'micro env --help') commands.`,
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "address",
				Usage:   "Set the micro server address :10001",
				EnvVars: []string{"MICRO_SERVER_ADDRESS"},
			},
			&cli.BoolFlag{
				Name:  "peer",
				Usage: "Peer with the global network to share services",
			},
			&cli.StringFlag{
				Name:    "profile",
				Usage:   "Set the runtime profile to use for services e.g local, kubernetes, platform",
				EnvVars: []string{"MICRO_RUNTIME_PROFILE"},
			},
		},
		Action: func(ctx *cli.Context) error {
			Run(ctx)
			return nil
		},
		Subcommands: []*cli.Command{{
			Name:  "file",
			Usage: "Move files between your local machine and the server",
			Subcommands: []*cli.Command{
				{
					Name:   "upload",
					Action: ccli.Print(upload),
				},
			},
		}},
	}

	for _, p := range Plugins() {
		if cmds := p.Commands(); len(cmds) > 0 {
			command.Subcommands = append(command.Subcommands, cmds...)
		}

		if flags := p.Flags(); len(flags) > 0 {
			command.Flags = append(command.Flags, flags...)
		}
	}

	return []*cli.Command{command}
}

// Run runs the entire platform
func Run(context *cli.Context) error {
	log.Init(log.WithFields(map[string]interface{}{"service": "micro"}))

	if context.Args().Len() > 0 {
		cli.ShowSubcommandHelp(context)
		os.Exit(1)
	}

	// get the network flag
	peer := context.Bool("peer")

	// pass through the environment
	// By default we want a file store when we run micro server.
	// This will get overridden if user has set their own MICRO_STORE env var or passed in --store
	env := []string{"MICRO_STORE=file"}
	env = append(env, os.Environ()...)

	// connect to the network if specified
	if peer {
		log.Info("Setting global network")

		if v := os.Getenv("MICRO_NETWORK_NODES"); len(v) == 0 {
			// set the resolver to use https://micro.mu/network
			env = append(env, "MICRO_NETWORK_NODES=network.micro.mu")
			log.Info("Setting default network micro.mu")
		}
		if v := os.Getenv("MICRO_NETWORK_TOKEN"); len(v) == 0 {
			// set the network token
			env = append(env, "MICRO_NETWORK_TOKEN=micro.mu")
			log.Info("Setting default network token")
		}
	}

	log.Info("Loading core services")

	// create new micro runtime
	muRuntime := cmd.DefaultCmd.Options().Runtime

	// Use default update notifier
	if context.Bool("auto_update") {
		updateURL := context.String("update_url")
		if len(updateURL) == 0 {
			updateURL = update.DefaultURL
		}

		options := []gorun.Option{
			gorun.WithScheduler(update.NewScheduler(updateURL, fmt.Sprintf("%d", time.Now().Unix()))),
		}
		(*muRuntime).Init(options...)
	}

	for _, service := range services {
		name := service

		if namespace := context.String("namespace"); len(namespace) > 0 {
			name = fmt.Sprintf("%s.%s", namespace, service)
		}

		log.Infof("Registering %s", name)
		args := []string{}
		envs := []string{}
		switch service {
		case "proxy", "web", "api", "bot", "cli":
			envs = profile.Server()
		default:
			// run non-clients as "micro service [name]"
			args = append(args, "service")
			envs = env
		}

		// pass the profile for the runtime
		if service == "runtime" {
			if p := context.String("profile"); len(p) > 0 {
				env = append(env, "MICRO_RUNTIME_PROFILE="+p)
			} else {
				env = append(env, "MICRO_RUNTIME_PROFILE=server")
			}
		}

		// we want to pass through the global args so go up one level in the context lineage
		if len(context.Lineage()) > 1 {
			globCtx := context.Lineage()[1]
			for _, f := range globCtx.FlagNames() {
				args = append(args, "--"+f, context.String(f))
			}
		}
		args = append(args, service)

		// runtime based on environment we run the service in
		opts := []gorun.CreateOption{
			gorun.WithCommand(os.Args[0]),
			gorun.WithArgs(args...),
			gorun.WithEnv(envs),
			gorun.WithOutput(os.Stdout),
			gorun.WithRetries(10),
			gorun.CreateImage("micro/micro"),
		}

		// NOTE: we use Version right now to check for the latest release
		muService := &gorun.Service{Name: name, Version: fmt.Sprintf("%d", time.Now().Unix())}
		if err := (*muRuntime).Create(muService, opts...); err != nil {
			log.Errorf("Failed to create runtime environment: %v", err)
			return err
		}
	}

	log.Info("Starting service runtime")

	// start the runtime
	if err := (*muRuntime).Start(); err != nil {
		log.Fatal(err)
		return err
	}

	log.Info("Service runtime started")

	// TODO: should we launch the console?
	// start the console
	// cli.Init(context)

	server := micro.NewService(
		micro.Name(Name),
		micro.Address(Address),
	)

	// @todo make this configurable
	uploadDir := filepath.Join(os.TempDir(), "micro", "uploads")
	os.MkdirAll(uploadDir, 0777)
	file.RegisterHandler(server.Server(), uploadDir)
	// start the server
	server.Run()

	log.Info("Stopping service runtime")

	// stop all the things
	if err := (*muRuntime).Stop(); err != nil {
		log.Fatal(err)
		return err
	}

	log.Info("Service runtime shutdown")

	// exit success
	os.Exit(0)
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2013 The GoMPD Authors. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file

package main

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/textproto"
	"os"
	"strconv"
	"sync"
)

func unquote(line string, start int) (string, int) {
	i := start
	if line[i] != '"' {
		for i < len(line) && (line[i] != ' ' && line[i] != '\t') {
			i++
		}
		return line[start:i], i
	}

	i++ // beginning "
	s := make([]byte, len(line[i:]))
	n := 0
	for i < len(line) {
		if line[i] == '"' { // ending "
			i++
			break
		}
		if line[i] == '\\' && i+1 < len(line) {
			i++
		}
		s[n] = line[i]
		i++
		n++
	}
	return string(s[:n]), i
}

func parseArgs(line string) (args []string) {
	var s string
	i := 0
	for i < len(line) {
		if line[i] == ' ' || line[i] == '\t' {
			i++
			continue
		}
		s, i = unquote(line, i)
		args = append(args, s)
	}
	return
}

type Attrs map[string]string

type Playlist struct {
	songs []int
}

func NewPlaylist() *Playlist {
	return &Playlist{songs: make([]int, 0)}
}

func (p *Playlist) At(i int) int {
	return p.songs[i]
}

func (p *Playlist) Len() int {
	return len(p.songs)
}

func (p *Playlist) Add(song int) {
	p.songs = append(p.songs, song)
}

func (p *Playlist) Delete(i int) {
	copy(p.songs[i:], p.songs[i+1:])
	p.songs = p.songs[:len(p.songs)-1]
}

func (p *Playlist) Clear() {
	p.songs = p.songs[:0]
}

func (p *Playlist) Append(q *Playlist) {
	// TODO: do at most one allocation
	for i := 0; i < q.Len(); i++ {
		p.Add(q.At(i))
	}
}

type Server struct {
	state           string
	database        []Attrs        // database of songs
	index           map[string]int // maps URI to database index
	playlists       map[string]*Playlist
	currentPlaylist *Playlist
	mu              sync.RWMutex
}

func NewServer() *Server {
	s := &Server{
		state:           "stop",
		database:        make([]Attrs, 100),
		index:           make(map[string]int, 100),
		playlists:       make(map[string]*Playlist),
		currentPlaylist: NewPlaylist(),
	}
	for i := 0; i < len(s.database); i++ {
		s.database[i] = make(Attrs, 5)
		filename := fmt.Sprintf("song%04d.ogg", i)
		s.database[i]["file"] = filename
		s.index[filename] = i
	}
	return s
}

func (s *Server) writeResponse(p *textproto.Conn, id uint, args []string) (closed bool) {
	p.StartResponse(id)
	defer p.EndResponse(id)

	if len(args) < 1 {
		p.PrintfLine("No command given")
		return
	}
	ack := func(format string, a ...interface{}) error {
		return p.PrintfLine("ACK {"+args[0]+"} "+format, a...)
	}
	switch args[0] {
	case "close":
		closed = true
		return
	case "list":
		if len(args) < 2 {
			ack("too few arguments")
			return
		}
		if args[1] == "file" {
			for _, a := range s.database {
				p.PrintfLine("file: %s", a["file"])
			}
		}
	case "listplaylists":
		for k := range s.playlists {
			p.PrintfLine("playlist: %s", k)
		}
	case "playlistinfo":
		for i := 0; i < s.currentPlaylist.Len(); i++ {
			p.PrintfLine("file: %s", s.database[s.currentPlaylist.At(i)]["file"])
		}
	case "listplaylistinfo":
		if len(args) < 2 {
			ack("too few arguments")
			return
		}
		pl, ok := s.playlists[args[1]]
		if !ok {
			ack("no such playlist")
			return
		}
		for i := 0; i < pl.Len(); i++ {
			p.PrintfLine("file: %s", s.database[pl.At(i)]["file"])
		}
	case "playlistadd":
		if len(args) != 3 {
			ack("wrong number of arguments")
			return
		}
		name, uri := args[1], args[2]
		i, ok := s.index[uri]
		if !ok {
			ack("URI not found")
			return
		}
		if s.playlists[name] == nil {
			s.playlists[name] = NewPlaylist()
		}
		s.playlists[name].Add(i)
	case "playlistdelete":
		if len(args) != 3 {
			ack("wrong number of arguments")
			return
		}
		name := args[1]
		pos, err := strconv.Atoi(args[2])
		if err != nil {
			ack("invalid position number")
			return
		}
		pl, ok := s.playlists[name]
		if !ok {
			ack("playlist not found")
			return
		}
		if pos >= pl.Len() {
			ack("invalid song position")
			return
		}
		pl.Delete(pos)
	case "playlistclear":
		if len(args) != 2 {
			ack("wrong number of arguments")
			return
		}
		pl, ok := s.playlists[args[1]]
		if !ok {
			ack("playlist not found")
			return
		}
		pl.Clear()
	case "rm":
		if len(args) != 2 {
			ack("wrong number of arguments")
			return
		}
		_, ok := s.playlists[args[1]]
		if !ok {
			ack("playlist not found")
			return
		}
		delete(s.playlists, args[1])
	case "rename":
		if len(args) != 3 {
			ack("wrong number of arguments")
			return
		}
		old, new := args[1], args[2]
		_, ok := s.playlists[old]
		if !ok {
			ack("playlist %s does not exist", old)
			return
		}
		_, ok = s.playlists[new]
		if ok {
			ack("playlist %s already exists", new)
			return
		}
		s.playlists[new] = s.playlists[old]
		delete(s.playlists, old)
	case "load":
		if len(args) != 2 {
			ack("wrong number of arguments")
			return
		}
		pl, ok := s.playlists[args[1]]
		if !ok {
			ack("playlist %s does not exist", args[1])
			return
		}
		s.currentPlaylist.Append(pl)
	case "clear":
		s.currentPlaylist.Clear()
	case "save":
		if len(args) != 2 {
			ack("wrong number of arguments")
			return
		}
		name := args[1]
		_, ok := s.playlists[name]
		if ok {
			ack("playlist %s already exists", name)
			return
		}
		s.playlists[name] = NewPlaylist()
		s.playlists[name].Append(s.currentPlaylist)
	case "play", "stop":
		s.mu.Lock()
		s.state = args[0]
		s.mu.Lock()
	case "pause":
		s.mu.Lock()
		if s.state != "stop" {
			s.state = args[0]
		}
		s.mu.Lock()
	case "status":
		s.mu.RLock()
		state := s.state
		s.mu.RLock()
		p.PrintfLine("state: %s", state)
	case "update":
		p.PrintfLine("updating_db: 1")
	case "ping":
	default:
		log.Printf("unknown command: %s\n", args[0])
	}
	p.PrintfLine("OK")
	return
}

func (s *Server) handleConnection(p *textproto.Conn) {
	id := p.Next()
	p.StartRequest(id)
	p.EndRequest(id)
	p.StartResponse(id)
	p.PrintfLine("OK MPD gompd0.1")
	p.EndResponse(id)

	for {
		id := p.Next()
		p.StartRequest(id)
		line, err := p.ReadLine()
		p.EndRequest(id)
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Printf("reading request failed: %v\n", err)
			break
		}
		args := parseArgs(line)
		if s.writeResponse(p, id, args) {
			break
		}
	}
	p.Close()
}

func main() {
	ln, err := net.Listen("tcp", ":6600")
	if err != nil {
		log.Fatalf("Listen failed: %v\n", err)
		os.Exit(1)
	}
	s := NewServer()
	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Printf("Accept failed: %v\n", err)
			continue
		}
		go s.handleConnection(textproto.NewConn(conn))
	}
}
<commit_msg>server: remove unnecessary locks<commit_after>// Copyright 2013 The GoMPD Authors. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file

package main

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/textproto"
	"os"
	"strconv"
)

func unquote(line string, start int) (string, int) {
	i := start
	if line[i] != '"' {
		for i < len(line) && (line[i] != ' ' && line[i] != '\t') {
			i++
		}
		return line[start:i], i
	}

	i++ // beginning "
	s := make([]byte, len(line[i:]))
	n := 0
	for i < len(line) {
		if line[i] == '"' { // ending "
			i++
			break
		}
		if line[i] == '\\' && i+1 < len(line) {
			i++
		}
		s[n] = line[i]
		i++
		n++
	}
	return string(s[:n]), i
}

func parseArgs(line string) (args []string) {
	var s string
	i := 0
	for i < len(line) {
		if line[i] == ' ' || line[i] == '\t' {
			i++
			continue
		}
		s, i = unquote(line, i)
		args = append(args, s)
	}
	return
}

type Attrs map[string]string

type Playlist struct {
	songs []int
}

func NewPlaylist() *Playlist {
	return &Playlist{songs: make([]int, 0)}
}

func (p *Playlist) At(i int) int {
	return p.songs[i]
}

func (p *Playlist) Len() int {
	return len(p.songs)
}

func (p *Playlist) Add(song int) {
	p.songs = append(p.songs, song)
}

func (p *Playlist) Delete(i int) {
	copy(p.songs[i:], p.songs[i+1:])
	p.songs = p.songs[:len(p.songs)-1]
}

func (p *Playlist) Clear() {
	p.songs = p.songs[:0]
}

func (p *Playlist) Append(q *Playlist) {
	// TODO: do at most one allocation
	for i := 0; i < q.Len(); i++ {
		p.Add(q.At(i))
	}
}

type Server struct {
	state           string
	database        []Attrs        // database of songs
	index           map[string]int // maps URI to database index
	playlists       map[string]*Playlist
	currentPlaylist *Playlist
}

func NewServer() *Server {
	s := &Server{
		state:           "stop",
		database:        make([]Attrs, 100),
		index:           make(map[string]int, 100),
		playlists:       make(map[string]*Playlist),
		currentPlaylist: NewPlaylist(),
	}
	for i := 0; i < len(s.database); i++ {
		s.database[i] = make(Attrs, 5)
		filename := fmt.Sprintf("song%04d.ogg", i)
		s.database[i]["file"] = filename
		s.index[filename] = i
	}
	return s
}

func (s *Server) writeResponse(p *textproto.Conn, id uint, args []string) (closed bool) {
	p.StartResponse(id)
	defer p.EndResponse(id)

	if len(args) < 1 {
		p.PrintfLine("No command given")
		return
	}
	ack := func(format string, a ...interface{}) error {
		return p.PrintfLine("ACK {"+args[0]+"} "+format, a...)
	}
	switch args[0] {
	case "close":
		closed = true
		return
	case "list":
		if len(args) < 2 {
			ack("too few arguments")
			return
		}
		if args[1] == "file" {
			for _, a := range s.database {
				p.PrintfLine("file: %s", a["file"])
			}
		}
	case "listplaylists":
		for k := range s.playlists {
			p.PrintfLine("playlist: %s", k)
		}
	case "playlistinfo":
		for i := 0; i < s.currentPlaylist.Len(); i++ {
			p.PrintfLine("file: %s", s.database[s.currentPlaylist.At(i)]["file"])
		}
	case "listplaylistinfo":
		if len(args) < 2 {
			ack("too few arguments")
			return
		}
		pl, ok := s.playlists[args[1]]
		if !ok {
			ack("no such playlist")
			return
		}
		for i := 0; i < pl.Len(); i++ {
			p.PrintfLine("file: %s", s.database[pl.At(i)]["file"])
		}
	case "playlistadd":
		if len(args) != 3 {
			ack("wrong number of arguments")
			return
		}
		name, uri := args[1], args[2]
		i, ok := s.index[uri]
		if !ok {
			ack("URI not found")
			return
		}
		if s.playlists[name] == nil {
			s.playlists[name] = NewPlaylist()
		}
		s.playlists[name].Add(i)
	case "playlistdelete":
		if len(args) != 3 {
			ack("wrong number of arguments")
			return
		}
		name := args[1]
		pos, err := strconv.Atoi(args[2])
		if err != nil {
			ack("invalid position number")
			return
		}
		pl, ok := s.playlists[name]
		if !ok {
			ack("playlist not found")
			return
		}
		if pos >= pl.Len() {
			ack("invalid song position")
			return
		}
		pl.Delete(pos)
	case "playlistclear":
		if len(args) != 2 {
			ack("wrong number of arguments")
			return
		}
		pl, ok := s.playlists[args[1]]
		if !ok {
			ack("playlist not found")
			return
		}
		pl.Clear()
	case "rm":
		if len(args) != 2 {
			ack("wrong number of arguments")
			return
		}
		_, ok := s.playlists[args[1]]
		if !ok {
			ack("playlist not found")
			return
		}
		delete(s.playlists, args[1])
	case "rename":
		if len(args) != 3 {
			ack("wrong number of arguments")
			return
		}
		old, new := args[1], args[2]
		_, ok := s.playlists[old]
		if !ok {
			ack("playlist %s does not exist", old)
			return
		}
		_, ok = s.playlists[new]
		if ok {
			ack("playlist %s already exists", new)
			return
		}
		s.playlists[new] = s.playlists[old]
		delete(s.playlists, old)
	case "load":
		if len(args) != 2 {
			ack("wrong number of arguments")
			return
		}
		pl, ok := s.playlists[args[1]]
		if !ok {
			ack("playlist %s does not exist", args[1])
			return
		}
		s.currentPlaylist.Append(pl)
	case "clear":
		s.currentPlaylist.Clear()
	case "save":
		if len(args) != 2 {
			ack("wrong number of arguments")
			return
		}
		name := args[1]
		_, ok := s.playlists[name]
		if ok {
			ack("playlist %s already exists", name)
			return
		}
		s.playlists[name] = NewPlaylist()
		s.playlists[name].Append(s.currentPlaylist)
	case "play", "stop":
		s.state = args[0]
	case "pause":
		if s.state != "stop" {
			s.state = args[0]
		}
	case "status":
		state := s.state
		p.PrintfLine("state: %s", state)
	case "update":
		p.PrintfLine("updating_db: 1")
	case "ping":
	default:
		log.Printf("unknown command: %s\n", args[0])
	}
	p.PrintfLine("OK")
	return
}

func (s *Server) handleConnection(p *textproto.Conn) {
	id := p.Next()
	p.StartRequest(id)
	p.EndRequest(id)
	p.StartResponse(id)
	p.PrintfLine("OK MPD gompd0.1")
	p.EndResponse(id)

	for {
		id := p.Next()
		p.StartRequest(id)
		line, err := p.ReadLine()
		p.EndRequest(id)
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Printf("reading request failed: %v\n", err)
			break
		}
		args := parseArgs(line)
		if s.writeResponse(p, id, args) {
			break
		}
	}
	p.Close()
}

func main() {
	ln, err := net.Listen("tcp", ":6600")
	if err != nil {
		log.Fatalf("Listen failed: %v\n", err)
		os.Exit(1)
	}
	s := NewServer()
	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Printf("Accept failed: %v\n", err)
			continue
		}
		go s.handleConnection(textproto.NewConn(conn))
	}
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"

	"github.com/blang/semver"
	"github.com/getlantern/golog"
)

var log = golog.LoggerFor("autoupdate-server")

// Initiative type.
type Initiative string

const (
	INITIATIVE_NEVER  Initiative = "never"
	INITIATIVE_AUTO              = "auto"
	INITIATIVE_MANUAL            = "manual"
)

// PatchType represents the type of a binary patch, if any. Only bsdiff is supported
type PatchType string

const (
	PATCHTYPE_BSDIFF PatchType = "bsdiff"
	PATCHTYPE_NONE             = ""
)

// Params represent parameters sent by the go-update client.
type Params struct {
	// protocol version
	Version int `json:"version"`
	// identifier of the application to update
	//AppId string `json:"app_id"`

	// version of the application updating itself
	AppVersion string `json:"app_version"`
	// operating system of target platform
	OS string `json:"-"`
	// hardware architecture of target platform
	Arch string `json:"-"`
	// application-level user identifier
	//UserId string `json:"user_id"`
	// checksum of the binary to replace (used for returning diff patches)
	Checksum string `json:"checksum"`
	// release channel (empty string means 'stable')
	//Channel string `json:"-"`
	// tags for custom update channels
	Tags map[string]string `json:"tags"`
}

// Result represents the answer to be sent to the client.
type Result struct {
	// should the update be applied automatically/manually
	Initiative Initiative `json:"initiative"`
	// url where to download the updated application
	URL string `json:"url"`
	// a URL to a patch to apply
	PatchURL string `json:"patch_url"`
	// the patch format (only bsdiff supported at the moment)
	PatchType PatchType `json:"patch_type"`
	// version of the new application
	Version string `json:"version"`
	// expected checksum of the new application
	Checksum string `json:"checksum"`
	// signature for verifying update authenticity
	Signature string `json:"signature"`
}

// CheckForUpdate receives a *Params message and emits a *Result. If both res
// and err are nil it means no update is available.
func (g *ReleaseManager) CheckForUpdate(p *Params) (res *Result, err error) {

	// Keep for the future.
	if p.Version < 1 {
		p.Version = 1
	}

	// p must not be nil.
	if p == nil {
		return nil, fmt.Errorf("Expecting params")
	}

	if p.Tags != nil {
		// Compatibility with go-check.
		if p.Tags["os"] != "" {
			p.OS = p.Tags["os"]
		}
		if p.Tags["arch"] != "" {
			p.Arch = p.Tags["arch"]
		}
	}

	if p.OS == "" {
		return nil, fmt.Errorf("OS is required")
	}

	// The checksum is optional if the OS is Android
	// since we aren't doing binary diffs
	if p.OS != OS.Android && p.Checksum == "" {
		return nil, fmt.Errorf("Checksum must not be nil")
	}

	if p.Arch == "" {
		return nil, fmt.Errorf("Arch is required")
	}

	// If this binary has a known checksum, the p.AppVersion will be changed to
	// 2.0.0beta8+manoto regardless of the value that was sent.
	if hasManotoChecksum(p.Checksum) {
		p.AppVersion = manotoBeta8
	}

	appVersion, err := semver.Parse(p.AppVersion)
	if err != nil {
		return nil, fmt.Errorf("Bad version string: %v", err)
	}

	var update *Asset

	// This is a hack that allows Lantern 2.0.0beta8+manoto clients to upgrade to
	// Lantern 2.0.0+manoto
	//
	// See https://github.com/getlantern/lantern/issues/2868
	if appVersion.String() == manotoBeta8 {
		// Always return 2.0.0+manoto
		if update, err = g.lookupAssetWithVersion(p.OS, p.Arch, manotoBeta8Upgrade); err != nil {
			return nil, fmt.Errorf("No upgrade for %s/%s", p.OS, p.Arch)
		}
	}

	// Looking if there is a newer version for the os/arch.
	if update == nil {
		if update, err = g.getProductUpdate(p.OS, p.Arch); err != nil {
			return nil, fmt.Errorf("Could not lookup for updates: %s", err)
		}
	}

	// No update available.
	if update.v.LTE(appVersion) {
		return nil, ErrNoUpdateAvailable
	}

	// A newer version is available!

	// Looking for the asset thay matches the current app checksum.
	var current *Asset
	if current, err = g.lookupAssetWithChecksum(p.OS, p.Arch, p.Checksum); err != nil {
		// No such asset with the given checksum, nothing to compare. Making the
		// client download the full binary
		return &Result{
			Initiative: INITIATIVE_AUTO,
			URL:        update.URL,
			PatchType:  PATCHTYPE_NONE,
			Version:    update.v.String(),
			Checksum:   update.Checksum,
			Signature:  update.Signature,
		}, nil
	}

	// A newer version is available!

	// Generate a binary diff of the two assets.
	var patch *Patch
	if patch, err = generatePatch(current.URL, update.URL); err != nil {
		return nil, fmt.Errorf("Unable to generate patch: %q", err)
	}

	// Generate result.
	r := &Result{
		Initiative: INITIATIVE_AUTO,
		URL:        update.URL,
		PatchURL:   patch.File,
		PatchType:  PATCHTYPE_BSDIFF,
		Version:    update.v.String(),
		Checksum:   update.Checksum,
		Signature:  update.Signature,
	}

	return r, nil
}
<commit_msg>remove duplicate comment<commit_after>package server

import (
	"fmt"

	"github.com/blang/semver"
	"github.com/getlantern/golog"
)

var log = golog.LoggerFor("autoupdate-server")

// Initiative type.
type Initiative string

const (
	INITIATIVE_NEVER  Initiative = "never"
	INITIATIVE_AUTO              = "auto"
	INITIATIVE_MANUAL            = "manual"
)

// PatchType represents the type of a binary patch, if any. Only bsdiff is supported
type PatchType string

const (
	PATCHTYPE_BSDIFF PatchType = "bsdiff"
	PATCHTYPE_NONE             = ""
)

// Params represent parameters sent by the go-update client.
type Params struct {
	// protocol version
	Version int `json:"version"`
	// identifier of the application to update
	//AppId string `json:"app_id"`

	// version of the application updating itself
	AppVersion string `json:"app_version"`
	// operating system of target platform
	OS string `json:"-"`
	// hardware architecture of target platform
	Arch string `json:"-"`
	// application-level user identifier
	//UserId string `json:"user_id"`
	// checksum of the binary to replace (used for returning diff patches)
	Checksum string `json:"checksum"`
	// release channel (empty string means 'stable')
	//Channel string `json:"-"`
	// tags for custom update channels
	Tags map[string]string `json:"tags"`
}

// Result represents the answer to be sent to the client.
type Result struct {
	// should the update be applied automatically/manually
	Initiative Initiative `json:"initiative"`
	// url where to download the updated application
	URL string `json:"url"`
	// a URL to a patch to apply
	PatchURL string `json:"patch_url"`
	// the patch format (only bsdiff supported at the moment)
	PatchType PatchType `json:"patch_type"`
	// version of the new application
	Version string `json:"version"`
	// expected checksum of the new application
	Checksum string `json:"checksum"`
	// signature for verifying update authenticity
	Signature string `json:"signature"`
}

// CheckForUpdate receives a *Params message and emits a *Result. If both res
// and err are nil it means no update is available.
func (g *ReleaseManager) CheckForUpdate(p *Params) (res *Result, err error) {

	// Keep for the future.
	if p.Version < 1 {
		p.Version = 1
	}

	// p must not be nil.
	if p == nil {
		return nil, fmt.Errorf("Expecting params")
	}

	if p.Tags != nil {
		// Compatibility with go-check.
		if p.Tags["os"] != "" {
			p.OS = p.Tags["os"]
		}
		if p.Tags["arch"] != "" {
			p.Arch = p.Tags["arch"]
		}
	}

	if p.OS == "" {
		return nil, fmt.Errorf("OS is required")
	}

	// The checksum is optional if the OS is Android
	// since we aren't doing binary diffs
	if p.OS != OS.Android && p.Checksum == "" {
		return nil, fmt.Errorf("Checksum must not be nil")
	}

	if p.Arch == "" {
		return nil, fmt.Errorf("Arch is required")
	}

	// If this binary has a known checksum, the p.AppVersion will be changed to
	// 2.0.0beta8+manoto regardless of the value that was sent.
	if hasManotoChecksum(p.Checksum) {
		p.AppVersion = manotoBeta8
	}

	appVersion, err := semver.Parse(p.AppVersion)
	if err != nil {
		return nil, fmt.Errorf("Bad version string: %v", err)
	}

	var update *Asset

	// This is a hack that allows Lantern 2.0.0beta8+manoto clients to upgrade to
	// Lantern 2.0.0+manoto
	//
	// See https://github.com/getlantern/lantern/issues/2868
	if appVersion.String() == manotoBeta8 {
		// Always return 2.0.0+manoto
		if update, err = g.lookupAssetWithVersion(p.OS, p.Arch, manotoBeta8Upgrade); err != nil {
			return nil, fmt.Errorf("No upgrade for %s/%s", p.OS, p.Arch)
		}
	}

	// Looking if there is a newer version for the os/arch.
	if update == nil {
		if update, err = g.getProductUpdate(p.OS, p.Arch); err != nil {
			return nil, fmt.Errorf("Could not lookup for updates: %s", err)
		}
	}

	// No update available.
	if update.v.LTE(appVersion) {
		return nil, ErrNoUpdateAvailable
	}

	// A newer version is available!

	// Looking for the asset thay matches the current app checksum.
	var current *Asset
	if current, err = g.lookupAssetWithChecksum(p.OS, p.Arch, p.Checksum); err != nil {
		// No such asset with the given checksum, nothing to compare. Making the
		// client download the full binary
		return &Result{
			Initiative: INITIATIVE_AUTO,
			URL:        update.URL,
			PatchType:  PATCHTYPE_NONE,
			Version:    update.v.String(),
			Checksum:   update.Checksum,
			Signature:  update.Signature,
		}, nil
	}

	// Generate a binary diff of the two assets.
	var patch *Patch
	if patch, err = generatePatch(current.URL, update.URL); err != nil {
		return nil, fmt.Errorf("Unable to generate patch: %q", err)
	}

	// Generate result.
	r := &Result{
		Initiative: INITIATIVE_AUTO,
		URL:        update.URL,
		PatchURL:   patch.File,
		PatchType:  PATCHTYPE_BSDIFF,
		Version:    update.v.String(),
		Checksum:   update.Checksum,
		Signature:  update.Signature,
	}

	return r, nil
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/albertyw/reaction-pics/tumblr"
	"github.com/ikeikeikeike/go-sitemap-generator/stm"
	"github.com/newrelic/go-agent/v3/newrelic"
	"github.com/pkg/errors"
	"github.com/rollbar/rollbar-go"
	"go.uber.org/zap"
)

const (
	maxResults = 20
)

var serverDir = filepath.Join(os.Getenv("ROOT_DIR"), "server")
var staticPath = fmt.Sprintf("%s/static/", serverDir)

// indexHandler is an http handler that returns the index page HTML
func indexHandler(w http.ResponseWriter, r *http.Request, d handlerDeps) {
	if r.URL.Path != "/" && !strings.HasPrefix(r.URL.Path, "/post/") {
		err := fmt.Errorf("file not found: %s", r.URL.Path)
		d.logger.Warn(err)
		rollbar.RequestError(rollbar.WARN, r, err)
		http.NotFound(w, r)
		return
	}
	t, err := template.ParseFiles(staticPath + "index.htm")
	if err != nil {
		err = errors.New("Cannot read post template")
		d.logger.Error(err)
		rollbar.RequestError(rollbar.ERR, r, err)
		http.Error(w, err.Error(), 500)
		return
	}
	templateData := struct {
		CacheString string
	}{d.appCacheString}
	err = t.Execute(w, templateData)
	if err != nil {
		err = errors.Wrap(err, "Cannot execute template")
		d.logger.Error(err)
		rollbar.RequestError(rollbar.ERR, r, err)
		http.Error(w, err.Error(), 500)
		return
	}
}

// searchHandler is an http handler to search data for keywords in json format
// It matches the query against post titles and then ranks posts by number of likes
func searchHandler(w http.ResponseWriter, r *http.Request, d handlerDeps) {
	query := r.URL.Query().Get("query")
	query = strings.ToLower(query)
	queriedBoard := d.board.FilterBoard(query)
	if query == "" {
		queriedBoard.RandomizePosts()
	}
	offsetString := r.URL.Query().Get("offset")
	offset, err := strconv.Atoi(offsetString)
	if err != nil {
		offset = 0
	}
	data := map[string]interface{}{
		"offset":       offset,
		"totalResults": len(queriedBoard.Posts),
	}
	queriedBoard.LimitBoard(offset, maxResults)
	queriedBoard.SortPostsByLikes()
	data["data"] = queriedBoard.PostsToJSON()
	dataBytes, _ := json.Marshal(data)
	fmt.Fprint(w, string(dataBytes))
}

// postDataHandler is an http handler to return post data by ID in json format
func postDataHandler(w http.ResponseWriter, r *http.Request, d handlerDeps) {
	pathStrings := strings.Split(r.URL.Path, "/")
	postIDString := pathStrings[2]
	postID, err := strconv.ParseInt(postIDString, 10, 64)
	if err != nil {
		err = errors.Wrap(err, "Cannot parse post id")
		d.logger.Warn(err)
		rollbar.RequestError(rollbar.WARN, r, err)
		http.NotFound(w, r)
		return
	}
	post := d.board.GetPostByID(postID)
	if post == nil {
		err = errors.New("Cannot find post")
		d.logger.Warn(err)
		rollbar.RequestError(rollbar.WARN, r, err)
		http.NotFound(w, r)
		return
	}
	data := map[string]interface{}{
		"offset":       0,
		"totalResults": 1,
		"data":         []tumblr.PostJSON{post.ToJSONStruct()},
	}
	marshalledPost, _ := json.Marshal(data)
	fmt.Fprint(w, string(marshalledPost))
}

// postHandler is an http handler that validates the correctness of a post url
// and returns the index page html to render it correct
func postHandler(w http.ResponseWriter, r *http.Request, d handlerDeps) {
	pathStrings := strings.Split(r.URL.Path, "/")
	postIDString := pathStrings[2]
	postID, err := strconv.ParseInt(postIDString, 10, 64)
	if err != nil {
		err = errors.Wrap(err, "Cannot parse post id")
		d.logger.Warn(err)
		rollbar.RequestError(rollbar.WARN, r, err)
		http.NotFound(w, r)
		return
	}
	foundPost := false
	for _, p := range d.board.Posts {
		if p.ID == postID {
			foundPost = true
		}
	}
	if !foundPost {
		err = errors.New("Cannot find post")
		d.logger.Warn(err)
		rollbar.RequestError(rollbar.WARN, r, err)
		http.NotFound(w, r)
		return
	}
	indexHandler(w, r, d)
}

// statsHandler returns internal stats about the reaction.pics DB as json
func statsHandler(w http.ResponseWriter, r *http.Request, d handlerDeps) {
	postCount := strconv.Itoa(len(d.board.Posts))
	data := map[string]interface{}{
		"postCount": postCount,
		"keywords":  d.board.Keywords(),
	}
	stats, _ := json.Marshal(data)
	fmt.Fprint(w, string(stats))
}

// sitemapHandler returns a sitemap of reaction.pics as an xml file
func sitemapHandler(w http.ResponseWriter, r *http.Request, d handlerDeps) {
	sm := stm.NewSitemap(0)
	sm.SetDefaultHost(os.Getenv("HOST"))

	sm.Create()
	sm.Add(stm.URL{{"loc", "/"}})
	for _, url := range d.board.URLs() {
		sm.Add(stm.URL{{"loc", url}})
	}
	w.Write(sm.XMLContent())
}

// staticHandler returns static files
func staticHandler(w http.ResponseWriter, r *http.Request, _ handlerDeps) {
	staticFS := rewriteFS(http.FileServer(http.Dir(staticPath)).ServeHTTP)
	staticFS(w, r)
}

func faviconHandler(w http.ResponseWriter, r *http.Request, _ handlerDeps) {
	faviconPath := filepath.Join(staticPath, "favicon", "favicon.ico")
	http.ServeFile(w, r, faviconPath)
}

// Run starts up the HTTP server
func Run(newrelicApp *newrelic.Application, logger *zap.SugaredLogger) {
	board := tumblr.InitializeBoard()
	address := fmt.Sprintf(":%s", os.Getenv("PORT"))
	logger.Infof("server listening on %s", address)
	generator := newHandlerGenerator(board, newrelicApp, logger)
	http.Handle(generator.newHandler("/", indexHandler))
	http.Handle(generator.newHandler("/favicon.ico", faviconHandler))
	http.Handle(generator.newHandler("/search", searchHandler))
	http.Handle(generator.newHandler("/postdata/", postDataHandler))
	http.Handle(generator.newHandler("/post/", postHandler))
	http.Handle(generator.newHandler("/stats.json", statsHandler))
	http.Handle(generator.newHandler("/sitemap.xml", sitemapHandler))
	http.Handle(generator.newHandler("/static/", staticHandler))
	http.ListenAndServe(address, nil)
}
<commit_msg>Serve a blank robots.txt<commit_after>package server

import (
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/albertyw/reaction-pics/tumblr"
	"github.com/ikeikeikeike/go-sitemap-generator/stm"
	"github.com/newrelic/go-agent/v3/newrelic"
	"github.com/pkg/errors"
	"github.com/rollbar/rollbar-go"
	"go.uber.org/zap"
)

const (
	maxResults = 20
)

var serverDir = filepath.Join(os.Getenv("ROOT_DIR"), "server")
var staticPath = fmt.Sprintf("%s/static/", serverDir)

// indexHandler is an http handler that returns the index page HTML
func indexHandler(w http.ResponseWriter, r *http.Request, d handlerDeps) {
	if r.URL.Path != "/" && !strings.HasPrefix(r.URL.Path, "/post/") {
		err := fmt.Errorf("file not found: %s", r.URL.Path)
		d.logger.Warn(err)
		rollbar.RequestError(rollbar.WARN, r, err)
		http.NotFound(w, r)
		return
	}
	t, err := template.ParseFiles(staticPath + "index.htm")
	if err != nil {
		err = errors.New("Cannot read post template")
		d.logger.Error(err)
		rollbar.RequestError(rollbar.ERR, r, err)
		http.Error(w, err.Error(), 500)
		return
	}
	templateData := struct {
		CacheString string
	}{d.appCacheString}
	err = t.Execute(w, templateData)
	if err != nil {
		err = errors.Wrap(err, "Cannot execute template")
		d.logger.Error(err)
		rollbar.RequestError(rollbar.ERR, r, err)
		http.Error(w, err.Error(), 500)
		return
	}
}

// searchHandler is an http handler to search data for keywords in json format
// It matches the query against post titles and then ranks posts by number of likes
func searchHandler(w http.ResponseWriter, r *http.Request, d handlerDeps) {
	query := r.URL.Query().Get("query")
	query = strings.ToLower(query)
	queriedBoard := d.board.FilterBoard(query)
	if query == "" {
		queriedBoard.RandomizePosts()
	}
	offsetString := r.URL.Query().Get("offset")
	offset, err := strconv.Atoi(offsetString)
	if err != nil {
		offset = 0
	}
	data := map[string]interface{}{
		"offset":       offset,
		"totalResults": len(queriedBoard.Posts),
	}
	queriedBoard.LimitBoard(offset, maxResults)
	queriedBoard.SortPostsByLikes()
	data["data"] = queriedBoard.PostsToJSON()
	dataBytes, _ := json.Marshal(data)
	fmt.Fprint(w, string(dataBytes))
}

// postDataHandler is an http handler to return post data by ID in json format
func postDataHandler(w http.ResponseWriter, r *http.Request, d handlerDeps) {
	pathStrings := strings.Split(r.URL.Path, "/")
	postIDString := pathStrings[2]
	postID, err := strconv.ParseInt(postIDString, 10, 64)
	if err != nil {
		err = errors.Wrap(err, "Cannot parse post id")
		d.logger.Warn(err)
		rollbar.RequestError(rollbar.WARN, r, err)
		http.NotFound(w, r)
		return
	}
	post := d.board.GetPostByID(postID)
	if post == nil {
		err = errors.New("Cannot find post")
		d.logger.Warn(err)
		rollbar.RequestError(rollbar.WARN, r, err)
		http.NotFound(w, r)
		return
	}
	data := map[string]interface{}{
		"offset":       0,
		"totalResults": 1,
		"data":         []tumblr.PostJSON{post.ToJSONStruct()},
	}
	marshalledPost, _ := json.Marshal(data)
	fmt.Fprint(w, string(marshalledPost))
}

// postHandler is an http handler that validates the correctness of a post url
// and returns the index page html to render it correct
func postHandler(w http.ResponseWriter, r *http.Request, d handlerDeps) {
	pathStrings := strings.Split(r.URL.Path, "/")
	postIDString := pathStrings[2]
	postID, err := strconv.ParseInt(postIDString, 10, 64)
	if err != nil {
		err = errors.Wrap(err, "Cannot parse post id")
		d.logger.Warn(err)
		rollbar.RequestError(rollbar.WARN, r, err)
		http.NotFound(w, r)
		return
	}
	foundPost := false
	for _, p := range d.board.Posts {
		if p.ID == postID {
			foundPost = true
		}
	}
	if !foundPost {
		err = errors.New("Cannot find post")
		d.logger.Warn(err)
		rollbar.RequestError(rollbar.WARN, r, err)
		http.NotFound(w, r)
		return
	}
	indexHandler(w, r, d)
}

// statsHandler returns internal stats about the reaction.pics DB as json
func statsHandler(w http.ResponseWriter, r *http.Request, d handlerDeps) {
	postCount := strconv.Itoa(len(d.board.Posts))
	data := map[string]interface{}{
		"postCount": postCount,
		"keywords":  d.board.Keywords(),
	}
	stats, _ := json.Marshal(data)
	fmt.Fprint(w, string(stats))
}

// sitemapHandler returns a sitemap of reaction.pics as an xml file
func sitemapHandler(w http.ResponseWriter, r *http.Request, d handlerDeps) {
	sm := stm.NewSitemap(0)
	sm.SetDefaultHost(os.Getenv("HOST"))

	sm.Create()
	sm.Add(stm.URL{{"loc", "/"}})
	for _, url := range d.board.URLs() {
		sm.Add(stm.URL{{"loc", url}})
	}
	w.Write(sm.XMLContent())
}

// staticHandler returns static files
func staticHandler(w http.ResponseWriter, r *http.Request, _ handlerDeps) {
	staticFS := rewriteFS(http.FileServer(http.Dir(staticPath)).ServeHTTP)
	staticFS(w, r)
}

func faviconHandler(w http.ResponseWriter, r *http.Request, _ handlerDeps) {
	faviconPath := filepath.Join(staticPath, "favicon", "favicon.ico")
	http.ServeFile(w, r, faviconPath)
}

func robotsTxtHandler(w http.ResponseWriter, r *http.Request, _ handlerDeps) {
	fmt.Fprint(w, "")
}

// Run starts up the HTTP server
func Run(newrelicApp *newrelic.Application, logger *zap.SugaredLogger) {
	board := tumblr.InitializeBoard()
	address := fmt.Sprintf(":%s", os.Getenv("PORT"))
	logger.Infof("server listening on %s", address)
	generator := newHandlerGenerator(board, newrelicApp, logger)
	http.Handle(generator.newHandler("/", indexHandler))
	http.Handle(generator.newHandler("/favicon.ico", faviconHandler))
	http.Handle(generator.newHandler("/robots.txt", robotsTxtHandler))
	http.Handle(generator.newHandler("/search", searchHandler))
	http.Handle(generator.newHandler("/postdata/", postDataHandler))
	http.Handle(generator.newHandler("/post/", postHandler))
	http.Handle(generator.newHandler("/stats.json", statsHandler))
	http.Handle(generator.newHandler("/sitemap.xml", sitemapHandler))
	http.Handle(generator.newHandler("/static/", staticHandler))
	http.ListenAndServe(address, nil)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"os"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

type NutritionInfo struct {
	Kcal      string
	Fat       string
	Saturates string
	Carbs     string
	Sugars    string
	Fibre     string
	Protein   string
	Salt      string
}

type Recipe struct {
	Name        string
	Ingredients []string
	Steps       []string
	Yield       string
	Difficulty  string
	Preperation string
	Cook        string
	Nutrition   NutritionInfo
}

func Scrape(recipe string) (Recipe, error) {
	doc, err := goquery.NewDocument(recipe)
	if err != nil {
		return Recipe{}, err
	}

	name := doc.Find(".recipe-header__title").First().Text()

	var ingredients []string
	doc.Find(".ingredients-list__item").Each(func(i int, s *goquery.Selection) {
		s.Find("span").Remove()
		ingredients = append(ingredients, strings.TrimSpace(s.Text()))
	})

	var steps []string
	doc.Find(".method__item").Each(func(i int, s *goquery.Selection) {
		steps = append(steps, strings.TrimSpace(s.Text()))
	})

	yield := extract("recipeYield", doc)

	difficulty := strings.TrimSpace(doc.Find("section.recipe-details__item--skill-level").Text())

	preperationSpan := doc.Find(".recipe-details__cooking-time-prep")
	preperationSpan.Find("strong").Remove()

	preperation := strings.TrimSpace(preperationSpan.Text())

	cookSpan := doc.Find(".recipe-details__cooking-time-cook")
	cookSpan.Find("strong").Remove()

	cook := strings.TrimSpace(cookSpan.Text())

	nutrition := NutritionInfo{
		Kcal:      extract("calories", doc),
		Fat:       extract("fatContent", doc),
		Saturates: extract("saturatedFatContent", doc),
		Sugars:    extract("sugarContent", doc),
		Fibre:     extract("fiberContent", doc),
		Protein:   extract("proteinContent", doc),
		Salt:      extract("sodiumContent", doc),
	}

	r := Recipe{
		Name:        name,
		Ingredients: ingredients,
		Steps:       steps,
		Yield:       yield,
		Difficulty:  difficulty,
		Preperation: preperation,
		Cook:        cook,
		Nutrition:   nutrition,
	}

	return r, nil
}

func extract(itemprop string, doc *goquery.Document) string {
	return strings.TrimSpace(doc.Find(fmt.Sprintf("span[itemprop='%s']", itemprop)).Text())
}

func main() {
	if len(os.Args) != 2 {
		fmt.Fprintf(os.Stderr, "usage: %s <url>\n", os.Args[0])
		return
	}

	r, err := Scrape(os.Args[1])
	if err != nil {
		panic(err)
	}

	b, err := json.Marshal(r)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%s", b)
}
<commit_msg>Spelling fix<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"os"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

type NutritionInfo struct {
	Kcal      string
	Fat       string
	Saturates string
	Carbs     string
	Sugars    string
	Fibre     string
	Protein   string
	Salt      string
}

type Recipe struct {
	Name        string
	Ingredients []string
	Steps       []string
	Yield       string
	Difficulty  string
	Preparation string
	Cook        string
	Nutrition   NutritionInfo
}

func Scrape(recipe string) (Recipe, error) {
	doc, err := goquery.NewDocument(recipe)
	if err != nil {
		return Recipe{}, err
	}

	name := doc.Find(".recipe-header__title").First().Text()

	var ingredients []string
	doc.Find(".ingredients-list__item").Each(func(i int, s *goquery.Selection) {
		s.Find("span").Remove()
		ingredients = append(ingredients, strings.TrimSpace(s.Text()))
	})

	var steps []string
	doc.Find(".method__item").Each(func(i int, s *goquery.Selection) {
		steps = append(steps, strings.TrimSpace(s.Text()))
	})

	yield := extract("recipeYield", doc)

	difficulty := strings.TrimSpace(doc.Find("section.recipe-details__item--skill-level").Text())

	preparationSpan := doc.Find(".recipe-details__cooking-time-prep")
	preparationSpan.Find("strong").Remove()

	preparation := strings.TrimSpace(preparationSpan.Text())

	cookSpan := doc.Find(".recipe-details__cooking-time-cook")
	cookSpan.Find("strong").Remove()

	cook := strings.TrimSpace(cookSpan.Text())

	nutrition := NutritionInfo{
		Kcal:      extract("calories", doc),
		Fat:       extract("fatContent", doc),
		Saturates: extract("saturatedFatContent", doc),
		Sugars:    extract("sugarContent", doc),
		Fibre:     extract("fiberContent", doc),
		Protein:   extract("proteinContent", doc),
		Salt:      extract("sodiumContent", doc),
	}

	r := Recipe{
		Name:        name,
		Ingredients: ingredients,
		Steps:       steps,
		Yield:       yield,
		Difficulty:  difficulty,
		Preparation: preparation,
		Cook:        cook,
		Nutrition:   nutrition,
	}

	return r, nil
}

func extract(itemprop string, doc *goquery.Document) string {
	return strings.TrimSpace(doc.Find(fmt.Sprintf("span[itemprop='%s']", itemprop)).Text())
}

func main() {
	if len(os.Args) != 2 {
		fmt.Fprintf(os.Stderr, "usage: %s <url>\n", os.Args[0])
		return
	}

	r, err := Scrape(os.Args[1])
	if err != nil {
		panic(err)
	}

	b, err := json.Marshal(r)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%s", b)
}
<|endoftext|>


<commit_before>package handlers

import (
	"net/http"

	"strings"

	"github.com/go-kit/kit/log"
)

func NewAllowedActions(l log.Logger, allowedActions []string) func(h http.Handler) http.Handler {
	var actions = make(map[string]bool, len(allowedActions))
	for _, p := range allowedActions {
		if p == "" {
			continue
		}

		actions[p] = true
	}

	return func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			action := r.URL.Path[1:]
			if _, exists := actions[action]; !exists {
				l.Log("error", "action is not white-listed", "action", action, "allowed", strings.Join(allowedActions, ","))
				http.Error(w, "Unregisterd action", http.StatusBadRequest)
				return
			}

			h.ServeHTTP(w, r)
		})
	}
}
<commit_msg>Ignoring empty actions<commit_after>package handlers

import (
	"net/http"

	"strings"

	"github.com/go-kit/kit/log"
)

func NewAllowedActions(l log.Logger, allowedActions []string) func(h http.Handler) http.Handler {
	var actions = make(map[string]bool, len(allowedActions))
	for _, p := range allowedActions {
		if p == "" {
			continue
		}

		actions[p] = true
	}

	return func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			action := r.URL.Path[1:]
			if _, exists := actions[action]; action != "" && !exists {
				l.Log("error", "action is not white-listed", "action", action, "allowed", strings.Join(allowedActions, ","))
				http.Error(w, "Unregisterd action", http.StatusBadRequest)
				return
			}

			h.ServeHTTP(w, r)
		})
	}
}
<|endoftext|>