<commit_before>package main

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

	r "github.com/garyburd/redigo/redis"
	"github.com/go-martini/martini"
	"github.com/martini-contrib/render"

	"github.com/vieux/gocover.io/server/redis"
)

var (
	docker_socket = flag.String("s", "", "Dockerd socket (e.g., /var/run/docker.sock)")
	docker_addr   = flag.String("d", "", "Dockerd addr (e.g., 127.0.0.1:2375)")
	serveAddr     = flag.String("p", ":8080", "Address and port to serve")
	serveSAddr    = flag.String("ps", ":80443", "Address and port to serve HTTPS")
	redisAddr     = flag.String("r", "127.0.0.1:6379", "redis address")
	redisPass     = flag.String("rp", "", "redis password")
	certPath      = flag.String("tls", "", "cert path")
)

func docker(repo, version string, pool *r.Pool) (int, string) {
	var (
		worker = "vieux/gocover"
		conn   = pool.Get()
	)

	defer conn.Close()

	if version != "" {
		worker = worker + ":" + version
	}

	if version == "" {
		if cached, fresh, err := redis.GetRepo(conn, repo); err != nil {
			return 500, err.Error()
		} else if fresh {
			return 200, string(cached)
		}
	}

	host := ""

	if *docker_socket != "" {
		host = "unix://" + *docker_socket
	} else if *docker_addr != "" {
		host = "tcp://" + *docker_addr
	} else {
		return 500, "cannot connect to docker daemon"
	}

	out, err := exec.Command("docker", "-H", host, "run", "--rm", "-a", "stdout", "-a", "stderr", worker, repo).CombinedOutput()
	if err != nil {
		if strings.Contains(string(out), "Unable to find image") {
			return 500, "go version '" + version + "' not found"
		}
		return 500, string(out)
	}
	re, err := regexp.Compile("\\")
	if err != nil {
		return 500, err.Error()
	}
	content := re.ReplaceAllString(string(out), "")
	content = strings.Replace(content, "background: black;", "background: #222222;", 2)

	content = strings.Replace(content, ".cov1 { color: rgb(128, 128, 128) }", ".cov1 { color: #52987D }", 2)
	content = strings.Replace(content, ".cov2 { color: rgb(128, 128, 128) }", ".cov2 { color: #4BA180 }", 2)
	content = strings.Replace(content, ".cov3 { color: rgb(128, 128, 128) }", ".cov3 { color: #44AA83 }", 2)
	content = strings.Replace(content, ".cov4 { color: rgb(128, 128, 128) }", ".cov4 { color: #3DB487 }", 2)
	content = strings.Replace(content, ".cov5 { color: rgb(128, 128, 128) }", ".cov5 { color: #36BD8A }", 2)
	content = strings.Replace(content, ".cov6 { color: rgb(128, 128, 128) }", ".cov6 { color: #2FC68D }", 2)
	content = strings.Replace(content, ".cov7 { color: rgb(128, 128, 128) }", ".cov7 { color: #28D091 }", 2)
	content = strings.Replace(content, ".cov8 { color: rgb(128, 128, 128) }", ".cov8 { color: #21D994 }", 2)
	content = strings.Replace(content, ".cov9 { color: rgb(128, 128, 128) }", ".cov9 { color: #1AE297 }", 2)

	content = strings.Replace(content, "\">"+repo, "\">", -1)

	re = regexp.MustCompile("-- cov:([0-9.]*) --")
	matches := re.FindStringSubmatch(content)
	if len(matches) == 2 {
		cov, err := strconv.ParseFloat(matches[1], 64)
		if err == nil {
			content = strings.Replace(content, "<select id=", fmt.Sprintf("%s%% | <select id=", int((cov-0.0001)/10), matches[1]), 1)
		}
		if version != "" {
			content = strings.Replace(content, "<select id=", fmt.Sprintf("%s | <select id=", version), 1)
		} else {
			redis.SetCache(conn, repo, content, matches[1])
		}
	} else if version != "" {
		content = strings.Replace(content, "<select id=", fmt.Sprintf("%s | <select id=", version), 1)
	} else {
		redis.SetCache(conn, repo, content, "-1")
	}
	if version == "" {
		redis.SetStats(conn, repo)
	}
	return 200, content
}

func main() {
	flag.Parse()

	var (
		m         = martini.Classic()
		pool, err = redis.NewPool("tcp", *redisAddr, *redisPass)
	)

	if err != nil {
		log.Fatalf("%v", err)
	}

	m.Use(martini.Static("static"))
	m.Use(render.Renderer(render.Options{Layout: "layout"}))
	m.Get("/about", func(r render.Render) {
		r.HTML(200, "about", map[string]interface{}{"about_active": "active"})
	})
	m.Get("/", func(r render.Render) {
		conn := pool.Get()
		defer conn.Close()

		top, err := redis.Top(conn, "top", 4)
		if err != nil {
			log.Println(err.Error())
		}
		last, err := redis.Top(conn, "last", 4)
		if err != nil {
			log.Println(err.Error())
		}
		r.HTML(200, "cover", map[string]interface{}{"top": top, "last": last, "cover_active": "active"})
	})
	m.Post("/_webhook", func(req *http.Request) (int, string) {

		github := struct {
			Repository struct {
				Full_Name string
			}
		}{}

		if err := json.NewDecoder(req.Body).Decode(&github); err != nil {
			return 500, err.Error()
		}

		go docker(github.Repository.Full_Name, "", pool)

		return 202, github.Repository.Full_Name
	})
	m.Get("/_badge/**", func(params martini.Params, r render.Render) {
		var (
			repo = params["_1"]
			conn = pool.Get()
		)

		defer conn.Close()
		if coverage, err := redis.GetCoverage(conn, repo); err != nil {
			r.Redirect(fmt.Sprintf("https://img.shields.io/badge/coverage-error-lightgrey.svg?style=flat"))
		} else if coverage < 25.0 {
			r.Redirect(fmt.Sprintf("https://img.shields.io/badge/coverage-%.1f%%-red.svg?style=flat", coverage))
		} else if coverage < 50.0 {
			r.Redirect(fmt.Sprintf("https://img.shields.io/badge/coverage-%.1f%%-orange.svg?style=flat", coverage))
		} else if coverage < 75.0 {
			r.Redirect(fmt.Sprintf("https://img.shields.io/badge/coverage-%.1f%%-green.svg?style=flat", coverage))
		} else {
			r.Redirect(fmt.Sprintf("https://img.shields.io/badge/coverage-%.1f%%-brightgreen.svg?style=flat", coverage))
		}

	})
	m.Get("/_cache/**", func(req *http.Request, params martini.Params) (int, string) {
		var (
			repo = params["_1"]
			conn = pool.Get()
		)
		defer conn.Close()

		if req.FormValue("version") == "" {
			if cached, _, err := redis.GetRepo(conn, repo); err != nil {
				return 500, err.Error()
			} else if cached != "" {
				redis.SetStats(conn, repo)
				return 200, string(cached)
			}
		}
		return 404, "No cached version of " + repo
	})
	m.Get("/_/**", func(req *http.Request, params martini.Params) (int, string) {
		return docker(params["_1"], req.FormValue("version"), pool)
	})
	m.Get("/**", func(req *http.Request, params martini.Params, r render.Render) {
		var (
			repo    = params["_1"]
			conn    = pool.Get()
			version = ""
		)
		defer conn.Close()

		if req.ParseForm() == nil {
			version = req.FormValue("version")
		}

		if cached, fresh, err := redis.GetRepo(conn, repo+version); err != nil {
			r.HTML(500, "", map[string]interface{}{"cover_active": "active", "error": err})
		} else if fresh {
			redis.SetStats(conn, repo)
			r.HTML(200, "cached", map[string]interface{}{"repo": repo, "cover_active": "active", "cache": template.HTML(cached), "version": version})
		} else {
			contexts := map[string]interface{}{"repo": repo, "cover_active": "active", "version": version}
			if cached != "" {
				contexts["cache"] = "ok"
			}
			r.HTML(200, "loading", contexts)
		}
	})
	if *certPath != "" {
		go func() {
			log.Println(http.ListenAndServe(*serveAddr, http.RedirectHandler("https://gocover.io", http.StatusMovedPermanently)))
		}()
		log.Fatal(http.ListenAndServeTLS(*serveSAddr, filepath.Join(*certPath, "cert.pem"), filepath.Join(*certPath, "privkey.pem"), m))
	} else {
		log.Fatal(http.ListenAndServe(*serveAddr, m))
	}
}
<commit_msg>fixTLS<commit_after>package main

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

	r "github.com/garyburd/redigo/redis"
	"github.com/go-martini/martini"
	"github.com/martini-contrib/render"

	"github.com/vieux/gocover.io/server/redis"
)

var (
	docker_socket = flag.String("s", "", "Dockerd socket (e.g., /var/run/docker.sock)")
	docker_addr   = flag.String("d", "", "Dockerd addr (e.g., 127.0.0.1:2375)")
	serveAddr     = flag.String("p", ":8080", "Address and port to serve")
	serveSAddr    = flag.String("ps", ":80443", "Address and port to serve HTTPS")
	redisAddr     = flag.String("r", "127.0.0.1:6379", "redis address")
	redisPass     = flag.String("rp", "", "redis password")
	certPath      = flag.String("tls", "", "cert path")
)

func docker(repo, version string, pool *r.Pool) (int, string) {
	var (
		worker = "vieux/gocover"
		conn   = pool.Get()
	)

	defer conn.Close()

	if version != "" {
		worker = worker + ":" + version
	}

	if version == "" {
		if cached, fresh, err := redis.GetRepo(conn, repo); err != nil {
			return 500, err.Error()
		} else if fresh {
			return 200, string(cached)
		}
	}

	host := ""

	if *docker_socket != "" {
		host = "unix://" + *docker_socket
	} else if *docker_addr != "" {
		host = "tcp://" + *docker_addr
	} else {
		return 500, "cannot connect to docker daemon"
	}

	out, err := exec.Command("docker", "-H", host, "run", "--rm", "-a", "stdout", "-a", "stderr", worker, repo).CombinedOutput()
	if err != nil {
		if strings.Contains(string(out), "Unable to find image") {
			return 500, "go version '" + version + "' not found"
		}
		return 500, string(out)
	}
	re, err := regexp.Compile("\\")
	if err != nil {
		return 500, err.Error()
	}
	content := re.ReplaceAllString(string(out), "")
	content = strings.Replace(content, "background: black;", "background: #222222;", 2)

	content = strings.Replace(content, ".cov1 { color: rgb(128, 128, 128) }", ".cov1 { color: #52987D }", 2)
	content = strings.Replace(content, ".cov2 { color: rgb(128, 128, 128) }", ".cov2 { color: #4BA180 }", 2)
	content = strings.Replace(content, ".cov3 { color: rgb(128, 128, 128) }", ".cov3 { color: #44AA83 }", 2)
	content = strings.Replace(content, ".cov4 { color: rgb(128, 128, 128) }", ".cov4 { color: #3DB487 }", 2)
	content = strings.Replace(content, ".cov5 { color: rgb(128, 128, 128) }", ".cov5 { color: #36BD8A }", 2)
	content = strings.Replace(content, ".cov6 { color: rgb(128, 128, 128) }", ".cov6 { color: #2FC68D }", 2)
	content = strings.Replace(content, ".cov7 { color: rgb(128, 128, 128) }", ".cov7 { color: #28D091 }", 2)
	content = strings.Replace(content, ".cov8 { color: rgb(128, 128, 128) }", ".cov8 { color: #21D994 }", 2)
	content = strings.Replace(content, ".cov9 { color: rgb(128, 128, 128) }", ".cov9 { color: #1AE297 }", 2)

	content = strings.Replace(content, "\">"+repo, "\">", -1)

	re = regexp.MustCompile("-- cov:([0-9.]*) --")
	matches := re.FindStringSubmatch(content)
	if len(matches) == 2 {
		cov, err := strconv.ParseFloat(matches[1], 64)
		if err == nil {
			content = strings.Replace(content, "<select id=", fmt.Sprintf("%s%% | <select id=", int((cov-0.0001)/10), matches[1]), 1)
		}
		if version != "" {
			content = strings.Replace(content, "<select id=", fmt.Sprintf("%s | <select id=", version), 1)
		} else {
			redis.SetCache(conn, repo, content, matches[1])
		}
	} else if version != "" {
		content = strings.Replace(content, "<select id=", fmt.Sprintf("%s | <select id=", version), 1)
	} else {
		redis.SetCache(conn, repo, content, "-1")
	}
	if version == "" {
		redis.SetStats(conn, repo)
	}
	return 200, content
}

func main() {
	flag.Parse()

	var (
		m         = martini.Classic()
		pool, err = redis.NewPool("tcp", *redisAddr, *redisPass)
	)

	if err != nil {
		log.Fatalf("%v", err)
	}

	m.Use(martini.Static("static"))
	m.Use(render.Renderer(render.Options{Layout: "layout"}))
	m.Get("/about", func(r render.Render) {
		r.HTML(200, "about", map[string]interface{}{"about_active": "active"})
	})
	m.Get("/", func(r render.Render) {
		conn := pool.Get()
		defer conn.Close()

		top, err := redis.Top(conn, "top", 4)
		if err != nil {
			log.Println(err.Error())
		}
		last, err := redis.Top(conn, "last", 4)
		if err != nil {
			log.Println(err.Error())
		}
		r.HTML(200, "cover", map[string]interface{}{"top": top, "last": last, "cover_active": "active"})
	})
	m.Post("/_webhook", func(req *http.Request) (int, string) {

		github := struct {
			Repository struct {
				Full_Name string
			}
		}{}

		if err := json.NewDecoder(req.Body).Decode(&github); err != nil {
			return 500, err.Error()
		}

		go docker(github.Repository.Full_Name, "", pool)

		return 202, github.Repository.Full_Name
	})
	m.Get("/_badge/**", func(params martini.Params, r render.Render) {
		var (
			repo = params["_1"]
			conn = pool.Get()
		)

		defer conn.Close()
		if coverage, err := redis.GetCoverage(conn, repo); err != nil {
			r.Redirect(fmt.Sprintf("https://img.shields.io/badge/coverage-error-lightgrey.svg?style=flat"))
		} else if coverage < 25.0 {
			r.Redirect(fmt.Sprintf("https://img.shields.io/badge/coverage-%.1f%%-red.svg?style=flat", coverage))
		} else if coverage < 50.0 {
			r.Redirect(fmt.Sprintf("https://img.shields.io/badge/coverage-%.1f%%-orange.svg?style=flat", coverage))
		} else if coverage < 75.0 {
			r.Redirect(fmt.Sprintf("https://img.shields.io/badge/coverage-%.1f%%-green.svg?style=flat", coverage))
		} else {
			r.Redirect(fmt.Sprintf("https://img.shields.io/badge/coverage-%.1f%%-brightgreen.svg?style=flat", coverage))
		}

	})
	m.Get("/_cache/**", func(req *http.Request, params martini.Params) (int, string) {
		var (
			repo = params["_1"]
			conn = pool.Get()
		)
		defer conn.Close()

		if req.FormValue("version") == "" {
			if cached, _, err := redis.GetRepo(conn, repo); err != nil {
				return 500, err.Error()
			} else if cached != "" {
				redis.SetStats(conn, repo)
				return 200, string(cached)
			}
		}
		return 404, "No cached version of " + repo
	})
	m.Get("/_/**", func(req *http.Request, params martini.Params) (int, string) {
		return docker(params["_1"], req.FormValue("version"), pool)
	})
	m.Get("/**", func(req *http.Request, params martini.Params, r render.Render) {
		var (
			repo    = params["_1"]
			conn    = pool.Get()
			version = ""
		)
		defer conn.Close()

		if req.ParseForm() == nil {
			version = req.FormValue("version")
		}

		if cached, fresh, err := redis.GetRepo(conn, repo+version); err != nil {
			r.HTML(500, "", map[string]interface{}{"cover_active": "active", "error": err})
		} else if fresh {
			redis.SetStats(conn, repo)
			r.HTML(200, "cached", map[string]interface{}{"repo": repo, "cover_active": "active", "cache": template.HTML(cached), "version": version})
		} else {
			contexts := map[string]interface{}{"repo": repo, "cover_active": "active", "version": version}
			if cached != "" {
				contexts["cache"] = "ok"
			}
			r.HTML(200, "loading", contexts)
		}
	})
	if *certPath != "" {
		go func() {
			log.Println(http.ListenAndServe(*serveAddr, http.RedirectHandler("https://gocover.io", http.StatusMovedPermanently)))
		}()
		log.Fatal(http.ListenAndServeTLS(*serveSAddr, filepath.Join(*certPath, "fullchain.pem"), filepath.Join(*certPath, "privkey.pem"), m))
	} else {
		log.Fatal(http.ListenAndServe(*serveAddr, m))
	}
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"net/http"

	"github.com/Sirupsen/logrus"

	"code.google.com/p/go.net/websocket"
	"golang.org/x/net/context"
)

var newConns = make(chan webSocketDone)

func New(ctx context.Context, total, diff chan []byte, port string) {

	var worldData []byte
	conns := make([]chan []byte, 0)

	go handleConnections(port)

	for {
		select {
		case data := <-total:
			worldData = data

		case data := <-diff:
			for _, c := range conns {
				c <- data
			}

		case wd := <-newConns:
			go sendWorld(wd, worldData)
			c := make(chan []byte)
			conns = append(conns, c)
			sendDiffs(ctx, wd, c)
			close(wd.done)
		}
	}
}

type webSocketDone struct {
	ws   *websocket.Conn
	done chan struct{}
}

func handleWebSocket(ws *websocket.Conn) {
	logrus.Infof("Accepted conn: %v", ws)
	done := make(chan struct{})
	newConns <- webSocketDone{ws, done}
	var b []byte
	err := websocket.Message.Receive(ws, b)
	if err == io.EOF {
		close(done)
	} else if err != nil {
		logrus.Error(err)
	}
	<-done
}

func handleConnections(port string) {
	http.Handle("/", websocket.Handler(handleWebSocket))
	err := http.ListenAndServe(port, nil)
	if err != nil {
		logrus.Error(err)
	}
}

func sendWorld(wd webSocketDone, data []byte) {
	err := websocket.Message.Send(wd.ws, data)
	if err != nil {
		logrus.Error(err)
	}
	logrus.Infof("Sent world to conn: %v", wd.ws)
}

func sendDiffs(ctx context.Context, wd webSocketDone, diff chan []byte) {
	for {
		select {
		case data := <-diff:
			err := websocket.Message.Send(wd.ws, data)
			if err != nil {
				logrus.Error(err)
			}
			logrus.Infof("Sent diff to conn: %v", wd.ws)

		case <-ctx.Done():
			return

		case <-wd.done:
			return
		}
	}
}
<commit_msg>dont double close connection channel<commit_after>package main

import (
	"io"
	"net/http"

	"github.com/Sirupsen/logrus"

	"code.google.com/p/go.net/websocket"
	"golang.org/x/net/context"
)

var newConns = make(chan webSocketDone)

func New(ctx context.Context, total, diff chan []byte, port string) {

	var worldData []byte
	conns := make([]chan []byte, 0)

	go handleConnections(port)

	for {
		select {
		case data := <-total:
			worldData = data

		case data := <-diff:
			for _, c := range conns {
				c <- data
			}

		case wd := <-newConns:
			go sendWorld(wd, worldData)
			c := make(chan []byte)
			conns = append(conns, c)
			sendDiffs(ctx, wd, c)
			close(wd.done)
		}
	}
}

type webSocketDone struct {
	ws   *websocket.Conn
	done chan struct{}
	ctx  context.Context
}

func handleWebSocket(ws *websocket.Conn) {
	logrus.Infof("Accepted conn: %v", ws)
	done := make(chan struct{})
	ctx, cancel := context.WithCancel(context.Background())
	newConns <- webSocketDone{ws, done, ctx}
	var b []byte
	err := websocket.Message.Receive(ws, b)
	if err == io.EOF {
		cancel()
	} else if err != nil {
		logrus.Error(err)
	}
	<-done
}

func handleConnections(port string) {
	http.Handle("/", websocket.Handler(handleWebSocket))
	err := http.ListenAndServe(port, nil)
	if err != nil {
		logrus.Error(err)
	}
}

func sendWorld(wd webSocketDone, data []byte) {
	err := websocket.Message.Send(wd.ws, data)
	if err != nil {
		logrus.Error(err)
	}
	logrus.Infof("Sent world to conn: %v", wd.ws)
}

func sendDiffs(ctx context.Context, wd webSocketDone, diff chan []byte) {
	for {
		select {
		case data := <-diff:
			err := websocket.Message.Send(wd.ws, data)
			if err != nil {
				logrus.Error(err)
			}
			logrus.Infof("Sent diff to conn: %v", wd.ws)

		case <-ctx.Done():
			return

		case <-wd.ctx.Done():
			return
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"os"
	"log"
	"fmt"
	"time"
	"strings"
	"net/http"
	mrand "math/rand"
	"crypto/rand"
	"runtime/pprof"
	"code.google.com/p/go.net/websocket"
)

var globalWorld = newWorld()

func handler(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "." + r.URL.Path)
}

type Message struct {
	Kind string
	Payload map[string]interface{}
}

func newMessage(kind string) *Message {
	ms := new(Message)
	ms.Kind = kind
	ms.Payload = make(map[string]interface{})
	return ms
}

func (m *Message) String() string {
	return fmt.Sprintf("{kind: %s, payload: %v}", m.Kind, m.Payload)
}

// http://stackoverflow.com/questions/12771930/what-is-the-fastest-way-to-generate-a-long-random-string-in-go
func randString(n int) string {
	const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	var bytes = make([]byte, n)
	rand.Read(bytes)
	for i, b := range bytes {
		bytes[i] = alphanum[b % byte(len(alphanum))]
	}
	return string(bytes)
}

type Player struct {
	w *World
	ws *websocket.Conn
	in chan *Message
	out chan *Message
	chunkOut chan *Message
	name string
	inBroadcast chan *Message
	loadedChunks map[ChunkCoords]bool
	visibleChunks map[ChunkCoords]bool
}

func newPlayer(ws *websocket.Conn) *Player {
	p := new(Player)
	p.w = globalWorld
	p.ws = ws
	p.in = make(chan *Message, 10)
	p.out = make(chan *Message, 10)
	p.chunkOut = make(chan *Message, 10)
	p.name = "player-" + randString(10)
	p.inBroadcast = make(chan *Message, 10)
	p.loadedChunks = make(map[ChunkCoords]bool, 0)
	p.visibleChunks = make(map[ChunkCoords]bool, 0)
	return p
}

func (p *Player) handleIncoming() {
	for {
		ms := new(Message)
		err := websocket.JSON.Receive(p.ws, ms)
		if err != nil {
			if err != io.EOF {
				log.Print("Reading websocket message (", p.name, "): ", err)
			}
			close(p.in)
			return
		}
		p.in <- ms
	}
}

func (p *Player) handleOutgoing() {
	for {
		ms := <-p.out
		err := websocket.JSON.Send(p.ws, ms)
		if err != nil {
			log.Print("Sending websocket message (", p.name, "): ", err)
			return
		}
	}
}

func (p *Player) sendChunk(cc ChunkCoords) {
	ms := newMessage("chunk")
	ms.Payload["ccpos"] = cc.toMap()
	ms.Payload["size"] = map[string]interface{}{
		"w": CHUNK_WIDTH,
		"h": CHUNK_HEIGHT,
		"d": CHUNK_DEPTH,
	}

	chunk := p.w.requestChunk(cc)
	p.loadedChunks[cc] = true
	p.visibleChunks[cc] = true
	ms.Payload["data"] = chunk.Flatten()
	p.chunkOut <- ms
}

func (p *Player) sendShowChunk(cc ChunkCoords) {
	ms := newMessage("show-chunk")
	ms.Payload["ccpos"] = cc.toMap()

	p.visibleChunks[cc] = true;
	p.chunkOut <- ms
}

func (p *Player) sendHideChunk(cc ChunkCoords) {
	ms := newMessage("hide-chunk")
	ms.Payload["ccpos"] = cc.toMap()

	delete(p.visibleChunks, cc)
	p.chunkOut <- ms
}

func (p *Player) sendUnloadChunk(cc ChunkCoords) {
	ms := newMessage("unload-chunk")
	ms.Payload["ccpos"] = cc.toMap()

	delete(p.loadedChunks, cc)
	delete(p.visibleChunks, cc)
	p.chunkOut <- ms
}

func (p *Player) handleBlock(ms *Message) {
	pl := ms.Payload
	wc := readWorldCoords(pl)
	typ := Block(pl["type"].(float64))

	p.w.changeBlock(wc, typ)
	p.w.broadcast <- ms
}

func (p *Player) handlerPlayerPosition(ms *Message) {
	pl := ms.Payload
	// TODO: Verify position is valid
	// (they didn't move too much in the last
	// couple frames, and they are not currently
	// in the ground).
	wc := readWorldCoords(pl["pos"].(map[string]interface{}))

	pl["id"] = p.name
	ms.Kind = "entity-position"
	p.w.broadcast <- ms

// 	MIN_LOAD_DIST := 1
	MAX_LOAD_DIST := 2
	MIN_HIDE_DIST := 2
	MAX_HIDE_DIST := 3
// 	MIN_UNLOAD_DIST := 3
// 	MAX_UNLOAD_DIST := 4

	eachWithin := func (cc ChunkCoords, dist int, cb func (newCC ChunkCoords)) {
		occ := func (x, y, z int) ChunkCoords {
			return ChunkCoords{
				x: cc.x + x,
				y: cc.y + y,
				z: cc.z + z,
			}
		}
		cb(cc)
		for i := 1; i <= dist; i++ {
			for x := -i; x <= i; x++ {
				for y := -i; y <= i; y++ {
					cb(occ(x, y, i))
					cb(occ(x, y, -i))
				}
			}
			for y := -i; y <= i; y++ {
				for z := 1 - i; z <= i - 1; z++ {
					cb(occ(i, y, z))
					cb(occ(-i, y, z))
				}
			}
			for x := 1 - i; x <= i - 1; x++ {
				for z := 1 - i; z <= i - 1; z++ {
					cb(occ(x, i, z))
					cb(occ(x, -i, z))
				}
			}
		}
	}
	cc := wc.Chunk()
	eachWithin(cc, MAX_LOAD_DIST, func (newCC ChunkCoords) {
		if p.loadedChunks[newCC] != true {
			p.sendChunk(newCC)
		} else if p.visibleChunks[newCC] != true {
			p.sendShowChunk(newCC)
		}
	})
	// Sometimes load far away chunks to reduce lag when
	// moving through the world (so coming up to a chunk
	// boundry doesn't require the loading of many chunks
	// in a single frame)
// 	eachWithin(cc, MAX_LOAD_DIST, func (newCC ChunkCoords) {
// 		if p.loadedChunks[newCC] != true {
// 			if (mrand.Float64() > 0.9) {
// 				p.sendChunk(newCC)
// 			}
// 		} else if p.visibleChunks[newCC] != true {
// 			if (mrand.Float64() > 0.5) {
// 				p.sendShowChunk(newCC)
// 			}
// 		}
// 	})

	eachOutside := func (list map[ChunkCoords]bool, cc ChunkCoords, dist int, cb func (lcc ChunkCoords)) {
		for lcc := range list {
			if lcc.x < cc.x - dist || lcc.x > cc.x + dist ||
				lcc.y < cc.y - dist || lcc.y > cc.y + dist ||
				lcc.z < cc.z - dist || lcc.z > cc.z + dist {
					cb(lcc)
			}
		}
	}
	eachOutside(p.visibleChunks, cc, MAX_HIDE_DIST, func (lcc ChunkCoords) {
		p.sendHideChunk(lcc)
	})
	eachOutside(p.visibleChunks, cc, MIN_HIDE_DIST, func (lcc ChunkCoords) {
		if (mrand.Float64() > 0.9) {
			p.sendHideChunk(lcc)
		}
	})

	// TODO: Send unload commands when really far away from
	// a chunk?
}

func wsHandler(ws *websocket.Conn) {
	config := ws.Config()
	fmt.Println(config, config.Location, config.Origin)
	p := newPlayer(ws)
	globalWorld.register <- p
	defer func () {
		globalWorld.unregister <- p
	}()
	go p.handleIncoming()
	go p.handleOutgoing()

	m := newMessage("player-id")
	m.Payload["id"] = p.name
	p.out <- m

	for {
		select {
		case m := <-p.in:
			if m == nil {
				return
			}
			switch m.Kind {
			case "block":
				p.handleBlock(m)
			case "player-position":
				p.handlerPlayerPosition(m)
			default:
				log.Print("Unknown message recieved from client of kind ", m.Kind)
				continue
			}
		case m := <-p.inBroadcast:
			if m.Kind == "entity-position" && p.name == m.Payload["id"] {
				continue
			}
			p.out <- m
		}
	}
}

func chunkHandler(ws *websocket.Conn) {
	config := ws.Config();
	path := config.Location.Path
	name := strings.Split(path, "/")[3]

	p := globalWorld.findPlayer(name)
	log.Print("WARNING< chunkHandrel not imeperaot..", name, p)

	for {
		ms := <-p.chunkOut
		err := websocket.JSON.Send(ws, ms)
		if err != nil {
			log.Print("Sending chunk websocket message (", p.name, "): ", err)
			return
		}
	}
}

func doProfile() {
	f, err := os.Create("cpuprofile")
	if err != nil {
		log.Fatal(err)
	}
	pprof.StartCPUProfile(f)

	go func () {
		for i := 1; i < 5; i++ {
			<-time.After(30*time.Second)
			log.Print(i * 30, " seconds past")
		}
		pprof.StopCPUProfile()
		os.Exit(1)
	}()
}

func main() {
	go globalWorld.run()
	http.HandleFunc("/", handler)
	http.Handle("/ws/new", websocket.Handler(wsHandler))
	http.Handle("/ws/chunks/", websocket.Handler(chunkHandler))
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}
<commit_msg>Cleanup server.go. We don't need to stagger far away chunks, and don't anticipate needing to in the future, so might as well delet that code.<commit_after>package main

import (
	"io"
	"os"
	"log"
	"fmt"
	"time"
	"strings"
	"net/http"
	mrand "math/rand"
	"crypto/rand"
	"runtime/pprof"
	"code.google.com/p/go.net/websocket"
)

var globalWorld = newWorld()

func handler(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "." + r.URL.Path)
}

type Message struct {
	Kind string
	Payload map[string]interface{}
}

func newMessage(kind string) *Message {
	ms := new(Message)
	ms.Kind = kind
	ms.Payload = make(map[string]interface{})
	return ms
}

func (m *Message) String() string {
	return fmt.Sprintf("{kind: %s, payload: %v}", m.Kind, m.Payload)
}

// http://stackoverflow.com/questions/12771930/what-is-the-fastest-way-to-generate-a-long-random-string-in-go
func randString(n int) string {
	const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	var bytes = make([]byte, n)
	rand.Read(bytes)
	for i, b := range bytes {
		bytes[i] = alphanum[b % byte(len(alphanum))]
	}
	return string(bytes)
}

type Player struct {
	w *World
	ws *websocket.Conn
	in chan *Message
	out chan *Message
	chunkOut chan *Message
	name string
	inBroadcast chan *Message
	loadedChunks map[ChunkCoords]bool
	visibleChunks map[ChunkCoords]bool
}

func newPlayer(ws *websocket.Conn) *Player {
	p := new(Player)
	p.w = globalWorld
	p.ws = ws
	p.in = make(chan *Message, 10)
	p.out = make(chan *Message, 10)
	p.chunkOut = make(chan *Message, 10)
	p.name = "player-" + randString(10)
	p.inBroadcast = make(chan *Message, 10)
	p.loadedChunks = make(map[ChunkCoords]bool, 0)
	p.visibleChunks = make(map[ChunkCoords]bool, 0)
	return p
}

func (p *Player) handleIncoming() {
	for {
		ms := new(Message)
		err := websocket.JSON.Receive(p.ws, ms)
		if err != nil {
			if err != io.EOF {
				log.Print("Reading websocket message (", p.name, "): ", err)
			}
			close(p.in)
			return
		}
		p.in <- ms
	}
}

func (p *Player) handleOutgoing() {
	for {
		ms := <-p.out
		err := websocket.JSON.Send(p.ws, ms)
		if err != nil {
			log.Print("Sending websocket message (", p.name, "): ", err)
			return
		}
	}
}

func (p *Player) sendChunk(cc ChunkCoords) {
	ms := newMessage("chunk")
	ms.Payload["ccpos"] = cc.toMap()
	ms.Payload["size"] = map[string]interface{}{
		"w": CHUNK_WIDTH,
		"h": CHUNK_HEIGHT,
		"d": CHUNK_DEPTH,
	}

	chunk := p.w.requestChunk(cc)
	p.loadedChunks[cc] = true
	p.visibleChunks[cc] = true
	ms.Payload["data"] = chunk.Flatten()
	p.chunkOut <- ms
}

func (p *Player) sendShowChunk(cc ChunkCoords) {
	ms := newMessage("show-chunk")
	ms.Payload["ccpos"] = cc.toMap()

	p.visibleChunks[cc] = true;
	p.chunkOut <- ms
}

func (p *Player) sendHideChunk(cc ChunkCoords) {
	ms := newMessage("hide-chunk")
	ms.Payload["ccpos"] = cc.toMap()

	delete(p.visibleChunks, cc)
	p.chunkOut <- ms
}

func (p *Player) sendUnloadChunk(cc ChunkCoords) {
	ms := newMessage("unload-chunk")
	ms.Payload["ccpos"] = cc.toMap()

	delete(p.loadedChunks, cc)
	delete(p.visibleChunks, cc)
	p.chunkOut <- ms
}

func (p *Player) handleBlock(ms *Message) {
	pl := ms.Payload
	wc := readWorldCoords(pl)
	typ := Block(pl["type"].(float64))

	p.w.changeBlock(wc, typ)
	p.w.broadcast <- ms
}

func (p *Player) handlerPlayerPosition(ms *Message) {
	pl := ms.Payload
	// TODO: Verify position is valid
	// (they didn't move too much in the last
	// couple frames, and they are not currently
	// in the ground).
	wc := readWorldCoords(pl["pos"].(map[string]interface{}))

	pl["id"] = p.name
	ms.Kind = "entity-position"
	p.w.broadcast <- ms

	MAX_LOAD_DIST := 2
	MIN_HIDE_DIST := 2
	MAX_HIDE_DIST := 3

	eachWithin := func (cc ChunkCoords, dist int, cb func (newCC ChunkCoords)) {
		occ := func (x, y, z int) ChunkCoords {
			return ChunkCoords{
				x: cc.x + x,
				y: cc.y + y,
				z: cc.z + z,
			}
		}
		cb(cc)
		for i := 1; i <= dist; i++ {
			for x := -i; x <= i; x++ {
				for y := -i; y <= i; y++ {
					cb(occ(x, y, i))
					cb(occ(x, y, -i))
				}
			}
			for y := -i; y <= i; y++ {
				for z := 1 - i; z <= i - 1; z++ {
					cb(occ(i, y, z))
					cb(occ(-i, y, z))
				}
			}
			for x := 1 - i; x <= i - 1; x++ {
				for z := 1 - i; z <= i - 1; z++ {
					cb(occ(x, i, z))
					cb(occ(x, -i, z))
				}
			}
		}
	}
	cc := wc.Chunk()
	eachWithin(cc, MAX_LOAD_DIST, func (newCC ChunkCoords) {
		if p.loadedChunks[newCC] != true {
			p.sendChunk(newCC)
		} else if p.visibleChunks[newCC] != true {
			p.sendShowChunk(newCC)
		}
	});

	eachOutside := func (list map[ChunkCoords]bool, cc ChunkCoords, dist int, cb func (lcc ChunkCoords)) {
		for lcc := range list {
			if lcc.x < cc.x - dist || lcc.x > cc.x + dist ||
				lcc.y < cc.y - dist || lcc.y > cc.y + dist ||
				lcc.z < cc.z - dist || lcc.z > cc.z + dist {
					cb(lcc)
			}
		}
	}
	eachOutside(p.visibleChunks, cc, MAX_HIDE_DIST, func (lcc ChunkCoords) {
		p.sendHideChunk(lcc)
	})
	eachOutside(p.visibleChunks, cc, MIN_HIDE_DIST, func (lcc ChunkCoords) {
		if (mrand.Float64() > 0.9) {
			p.sendHideChunk(lcc)
		}
	})
}

func wsHandler(ws *websocket.Conn) {
	config := ws.Config()
	fmt.Println(config, config.Location, config.Origin)
	p := newPlayer(ws)
	globalWorld.register <- p
	defer func () {
		globalWorld.unregister <- p
	}()
	go p.handleIncoming()
	go p.handleOutgoing()

	m := newMessage("player-id")
	m.Payload["id"] = p.name
	p.out <- m

	for {
		select {
		case m := <-p.in:
			if m == nil {
				return
			}
			switch m.Kind {
			case "block":
				p.handleBlock(m)
			case "player-position":
				p.handlerPlayerPosition(m)
			default:
				log.Print("Unknown message recieved from client of kind ", m.Kind)
				continue
			}
		case m := <-p.inBroadcast:
			if m.Kind == "entity-position" && p.name == m.Payload["id"] {
				continue
			}
			p.out <- m
		}
	}
}

func chunkHandler(ws *websocket.Conn) {
	config := ws.Config();
	path := config.Location.Path
	name := strings.Split(path, "/")[3]

	p := globalWorld.findPlayer(name)
	log.Print("WARNING< chunkHandrel not imeperaot..", name, p)

	for {
		ms := <-p.chunkOut
		err := websocket.JSON.Send(ws, ms)
		if err != nil {
			log.Print("Sending chunk websocket message (", p.name, "): ", err)
			return
		}
	}
}

func doProfile() {
	f, err := os.Create("cpuprofile")
	if err != nil {
		log.Fatal(err)
	}
	pprof.StartCPUProfile(f)

	go func () {
		for i := 1; i < 5; i++ {
			<-time.After(30*time.Second)
			log.Print(i * 30, " seconds past")
		}
		pprof.StopCPUProfile()
		os.Exit(1)
	}()
}

func main() {
	go globalWorld.run()
	http.HandleFunc("/", handler)
	http.Handle("/ws/new", websocket.Handler(wsHandler))
	http.Handle("/ws/chunks/", websocket.Handler(chunkHandler))
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package server provides a preconfigured HTTP server with diagnostic hooks.
package server

import (
	"net/http"
	"path"
	"sync"

	"github.com/google/go-cloud/health"
	"github.com/google/go-cloud/requestlog"
	"github.com/google/go-cloud/wire"

	"go.opencensus.io/trace"
)

// Set is a Wire provider set that produces a *Server given the fields of
// Options. This set might add new inputs over time, but they can always be the
// zero value.
var Set = wire.NewSet(New, Options{})

// Server is a preconfigured HTTP server with diagnostic hooks.
// The zero value is a server with the default options.
type Server struct {
	reqlog        requestlog.Logger
	healthHandler health.Handler
	te            trace.Exporter
	sampler       trace.Sampler
	once          sync.Once
}

// Options is the set of optional parameters.
type Options struct {
	RequestLogger requestlog.Logger
	HealthChecks  []health.Checker

	TraceExporter         trace.Exporter
	DefaultSamplingPolicy trace.Sampler
}

// New creates a new server. New(nil) is the same as new(Server).
func New(opts *Options) *Server {
	srv := new(Server)
	if opts != nil {
		srv.reqlog = opts.RequestLogger
		srv.te = opts.TraceExporter
		for _, c := range opts.HealthChecks {
			srv.healthHandler.Add(c)
		}
		srv.sampler = opts.DefaultSamplingPolicy
	}
	return srv
}

func (srv *Server) init() {
	srv.once.Do(func() {
		if srv.te != nil {
			trace.RegisterExporter(srv.te)
		}
		if srv.sampler != nil {
			trace.ApplyConfig(trace.Config{DefaultSampler: srv.sampler})
		}
	})
}

// ListenAndServe is a wrapper to use wherever http.ListenAndServe is used.
// It wraps the passed-in http.Handler with a handler that handles tracing and
// request logging. If the handler is nil, then http.DefaultServeMux will be used.
func (srv *Server) ListenAndServe(addr string, h http.Handler) error {
	srv.init()

	// Setup health checks, /healthz route is taken by health checks by default.
	// Note: App Engine Flex uses /_ah/health by default, which can be changed
	// in app.yaml. We may want to do an auto-detection for flex in future.
	hr := "/healthz/"
	hcMux := http.NewServeMux()
	hcMux.HandleFunc(path.Join(hr, "liveness"), health.HandleLive)
	hcMux.Handle(path.Join(hr, "readiness"), &srv.healthHandler)

	mux := http.NewServeMux()
	mux.Handle(hr, hcMux)
	h = http.Handler(handler{h})
	if srv.reqlog != nil {
		h = requestlog.NewHandler(srv.reqlog, h)
	}
	mux.Handle("/", h)

	return http.ListenAndServe(addr, mux)
}

// handler is a handler wrapper that handles tracing through OpenCensus for users.
// TODO(shantuo): unify handler types from trace, requestlog, health checks, etc together.
type handler struct {
	handler http.Handler
}

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx, span := trace.StartSpan(r.Context(), r.URL.Host+r.URL.Path)
	defer span.End()

	r = r.WithContext(ctx)

	if h.handler == nil {
		h.handler = http.DefaultServeMux
	}
	h.handler.ServeHTTP(w, r)
}
<commit_msg>server: add note about RequestLogger to server docs (#292)<commit_after>// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package server provides a preconfigured HTTP server with diagnostic hooks.
package server

import (
	"net/http"
	"path"
	"sync"

	"github.com/google/go-cloud/health"
	"github.com/google/go-cloud/requestlog"
	"github.com/google/go-cloud/wire"

	"go.opencensus.io/trace"
)

// Set is a Wire provider set that produces a *Server given the fields of
// Options. This set might add new inputs over time, but they can always be the
// zero value.
var Set = wire.NewSet(New, Options{})

// Server is a preconfigured HTTP server with diagnostic hooks.
// The zero value is a server with the default options.
type Server struct {
	reqlog        requestlog.Logger
	healthHandler health.Handler
	te            trace.Exporter
	sampler       trace.Sampler
	once          sync.Once
}

// Options is the set of optional parameters.
type Options struct {
	RequestLogger requestlog.Logger
	HealthChecks  []health.Checker

	TraceExporter         trace.Exporter
	DefaultSamplingPolicy trace.Sampler
}

// New creates a new server. New(nil) is the same as new(Server).
func New(opts *Options) *Server {
	srv := new(Server)
	if opts != nil {
		srv.reqlog = opts.RequestLogger
		srv.te = opts.TraceExporter
		for _, c := range opts.HealthChecks {
			srv.healthHandler.Add(c)
		}
		srv.sampler = opts.DefaultSamplingPolicy
	}
	return srv
}

func (srv *Server) init() {
	srv.once.Do(func() {
		if srv.te != nil {
			trace.RegisterExporter(srv.te)
		}
		if srv.sampler != nil {
			trace.ApplyConfig(trace.Config{DefaultSampler: srv.sampler})
		}
	})
}

// ListenAndServe is a wrapper to use wherever http.ListenAndServe is used.
// It wraps the passed-in http.Handler with a handler that handles tracing and
// request logging. If the handler is nil, then http.DefaultServeMux will be used.
// A configured Requestlogger will log all requests except HealthChecks.
func (srv *Server) ListenAndServe(addr string, h http.Handler) error {
	srv.init()

	// Setup health checks, /healthz route is taken by health checks by default.
	// Note: App Engine Flex uses /_ah/health by default, which can be changed
	// in app.yaml. We may want to do an auto-detection for flex in future.
	hr := "/healthz/"
	hcMux := http.NewServeMux()
	hcMux.HandleFunc(path.Join(hr, "liveness"), health.HandleLive)
	hcMux.Handle(path.Join(hr, "readiness"), &srv.healthHandler)

	mux := http.NewServeMux()
	mux.Handle(hr, hcMux)
	h = http.Handler(handler{h})
	if srv.reqlog != nil {
		h = requestlog.NewHandler(srv.reqlog, h)
	}
	mux.Handle("/", h)

	return http.ListenAndServe(addr, mux)
}

// handler is a handler wrapper that handles tracing through OpenCensus for users.
// TODO(shantuo): unify handler types from trace, requestlog, health checks, etc together.
type handler struct {
	handler http.Handler
}

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx, span := trace.StartSpan(r.Context(), r.URL.Host+r.URL.Path)
	defer span.End()

	r = r.WithContext(ctx)

	if h.handler == nil {
		h.handler = http.DefaultServeMux
	}
	h.handler.ServeHTTP(w, r)
}
<|endoftext|>


<commit_before>package server

import (
	"context"
	"fmt"
	"io/ioutil"
	"log/syslog"
	"net"
	"os"
	"path/filepath"

	log "github.com/Sirupsen/logrus"
	"github.com/sahib/brig/defaults"
	"github.com/sahib/brig/repo"
	formatter "github.com/sahib/brig/util/log"
	"github.com/sahib/brig/util/pwutil"
	"github.com/sahib/brig/util/server"
)

const (
	MaxConnections = 10
)

//////////////////////////////

type Server struct {
	baseServer *server.Server
	base       *base
}

func (sv *Server) Serve() error {
	log.Infof("Serving local requests from now on.")
	return sv.baseServer.Serve()
}

func (sv *Server) Close() error {
	return sv.baseServer.Close()
}

func readPasswordFromHelper(basePath string) (string, error) {
	configPath := filepath.Join(basePath, "config.yml")
	cfg, err := defaults.OpenMigratedConfig(configPath)
	if err != nil {
		return "", err
	}

	passwordCmd := cfg.String("repo.password_command")
	if passwordCmd == "" {
		return "", fmt.Errorf("no password helper set")
	}

	return pwutil.ReadPasswordFromHelper(basePath, passwordCmd)
}

func switchToSyslog() {
	wSyslog, err := syslog.New(syslog.LOG_NOTICE, "brig")
	if err != nil {
		log.Warningf("Failed to open connection to syslog: %v", err)
	}

	log.SetFormatter(&formatter.ColorfulLogFormatter{})
	log.SetLevel(log.DebugLevel)
	log.SetOutput(wSyslog)
}

func updateRegistry(basePath string, port int) error {
	data, err := ioutil.ReadFile(filepath.Join(basePath, "REPO_ID"))
	if err != nil {
		return err
	}

	uuid := string(data)

	// TODO: Move repo.OpenRegisry to util somewhere.
	// It is also used in the client.
	registry, err := repo.OpenRegistry()
	if err != nil {
		return err
	}

	entry, err := registry.Entry(uuid)
	if err != nil {
		return err
	}

	entry.Port = int64(port)
	entry.Path = basePath
	return registry.Update(uuid, entry)
}

func BootServer(basePath string, passwordFn func() (string, error), bindHost string, port int, logToStdout bool) (*Server, error) {
	if !logToStdout {
		switchToSyslog()
	} else {
		log.SetOutput(os.Stdout)
	}

	addr := fmt.Sprintf("%s:%d", bindHost, port)
	log.Infof("Starting daemon from %s on port %s", basePath, addr)

	password, err := readPasswordFromHelper(basePath)
	if err != nil {
		log.Infof("Failed to read password from helper: %s", err)
		log.Infof("Attempting to read it from stdin")

		password, err = passwordFn()
		if err != nil {
			return nil, err
		}
	} else {
		log.Infof("Password is coming from the configured password helper")
	}

	if err := repo.CheckPassword(basePath, password); err != nil {
		return nil, err
	}

	log.Infof("Password seems to be valid...")

	if err := updateRegistry(basePath, port); err != nil {
		log.Warningf("could not update global registry: %v", err)
	}

	if err := increaseMaxOpenFds(); err != nil {
		log.Warningf("Failed to incrase number of open fds")
	}

	ctx := context.Background()
	quitCh := make(chan struct{})
	base, err := newBase(basePath, password, bindHost, ctx, quitCh)
	if err != nil {
		return nil, err
	}

	lst, err := net.Listen("tcp", addr)
	if err != nil {
		return nil, err
	}

	baseServer, err := server.NewServer(lst, base, ctx)
	if err != nil {
		return nil, err
	}

	go func() {
		<-quitCh
		baseServer.Quit()
		if err := baseServer.Close(); err != nil {
			log.Warnf("Failed to close local server listener: %v", err)
		}
	}()

	// TODO: Go online automatically
	// TODO: Mount fstab entries here automatically.

	return &Server{
		baseServer: baseServer,
		base:       base,
	}, nil
}
<commit_msg>server: make sure to start net layer and mount all fstab mounts<commit_after>package server

import (
	"context"
	"fmt"
	"io/ioutil"
	"log/syslog"
	"net"
	"os"
	"path/filepath"

	log "github.com/Sirupsen/logrus"
	"github.com/sahib/brig/defaults"
	"github.com/sahib/brig/fuse"
	"github.com/sahib/brig/repo"
	formatter "github.com/sahib/brig/util/log"
	"github.com/sahib/brig/util/pwutil"
	"github.com/sahib/brig/util/server"
)

const (
	MaxConnections = 10
)

//////////////////////////////

type Server struct {
	baseServer *server.Server
	base       *base
}

func (sv *Server) Serve() error {
	log.Infof("Serving local requests from now on.")
	return sv.baseServer.Serve()
}

func (sv *Server) Close() error {
	return sv.baseServer.Close()
}

func readPasswordFromHelper(basePath string) (string, error) {
	configPath := filepath.Join(basePath, "config.yml")
	cfg, err := defaults.OpenMigratedConfig(configPath)
	if err != nil {
		return "", err
	}

	passwordCmd := cfg.String("repo.password_command")
	if passwordCmd == "" {
		return "", fmt.Errorf("no password helper set")
	}

	return pwutil.ReadPasswordFromHelper(basePath, passwordCmd)
}

func switchToSyslog() {
	wSyslog, err := syslog.New(syslog.LOG_NOTICE, "brig")
	if err != nil {
		log.Warningf("Failed to open connection to syslog: %v", err)
	}

	log.SetFormatter(&formatter.ColorfulLogFormatter{})
	log.SetLevel(log.DebugLevel)
	log.SetOutput(wSyslog)
}

func updateRegistry(basePath string, port int) error {
	data, err := ioutil.ReadFile(filepath.Join(basePath, "REPO_ID"))
	if err != nil {
		return err
	}

	uuid := string(data)

	// TODO: Move repo.OpenRegisry to util somewhere.
	// It is also used in the client.
	registry, err := repo.OpenRegistry()
	if err != nil {
		return err
	}

	entry, err := registry.Entry(uuid)
	if err != nil {
		return err
	}

	entry.Port = int64(port)
	entry.Path = basePath
	return registry.Update(uuid, entry)
}

func applyFstabInitially(base *base) error {
	rp, err := base.Repo()
	if err != nil {
		return err
	}

	mounts, err := base.Mounts()
	if err != nil {
		return err
	}

	return fuse.FsTabApply(rp.Config.Section("mounts"), mounts)
}

func startNetLayer(base *base) error {
	_, err := base.PeerServer()
	return err
}

func BootServer(basePath string, passwordFn func() (string, error), bindHost string, port int, logToStdout bool) (*Server, error) {
	if !logToStdout {
		switchToSyslog()
	} else {
		log.SetOutput(os.Stdout)
	}

	addr := fmt.Sprintf("%s:%d", bindHost, port)
	log.Infof("Starting daemon from %s on port %s", basePath, addr)

	password, err := readPasswordFromHelper(basePath)
	if err != nil {
		log.Infof("Failed to read password from helper: %s", err)
		log.Infof("Attempting to read it from stdin")

		password, err = passwordFn()
		if err != nil {
			return nil, err
		}
	} else {
		log.Infof("Password is coming from the configured password helper")
	}

	if err := repo.CheckPassword(basePath, password); err != nil {
		return nil, err
	}

	log.Infof("Password seems to be valid...")

	if err := updateRegistry(basePath, port); err != nil {
		log.Warningf("could not update global registry: %v", err)
	}

	if err := increaseMaxOpenFds(); err != nil {
		log.Warningf("Failed to incrase number of open fds")
	}

	ctx := context.Background()
	quitCh := make(chan struct{})
	base, err := newBase(basePath, password, bindHost, ctx, quitCh)
	if err != nil {
		return nil, err
	}

	lst, err := net.Listen("tcp", addr)
	if err != nil {
		return nil, err
	}

	baseServer, err := server.NewServer(lst, base, ctx)
	if err != nil {
		return nil, err
	}

	go func() {
		<-quitCh
		baseServer.Quit()
		if err := baseServer.Close(); err != nil {
			log.Warnf("Failed to close local server listener: %v", err)
		}
	}()

	// Do the rest of the init in the background.
	// This will curently log warnings for a not yet initialized repo.
	go func() {
		if err := startNetLayer(base); err != nil {
			log.Warnf("could not start the net layer yet: %v", err)
		}

		if err := applyFstabInitially(base); err != nil {
			log.Warnf("could not mount fstab mounts: %v", err)
		}
	}()

	return &Server{
		baseServer: baseServer,
		base:       base,
	}, nil
}
<|endoftext|>


<commit_before>/*
The MIT License (MIT)

Copyright (c) 2014-2017 DutchCoders [https://github.com/dutchcoders/]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package server

import (
	"errors"
	"log"
	"math/rand"
	"mime"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	context "golang.org/x/net/context"

	"github.com/PuerkitoBio/ghost/handlers"
	"github.com/VojtechVitek/ratelimit"
	"github.com/VojtechVitek/ratelimit/memory"
	"github.com/gorilla/mux"

	_ "net/http/pprof"

	"crypto/tls"

	web "github.com/dutchcoders/transfer.sh-web"
	assetfs "github.com/elazarl/go-bindata-assetfs"

	autocert "golang.org/x/crypto/acme/autocert"
	"path/filepath"
)

const SERVER_INFO = "transfer.sh"

// parse request with maximum memory of _24Kilobits
const _24K = (1 << 3) * 24

// parse request with maximum memory of _5Megabytes
const _5M = (1 << 20) * 5

type OptionFn func(*Server)

func ClamavHost(s string) OptionFn {
	return func(srvr *Server) {
		srvr.ClamAVDaemonHost = s
	}
}

func VirustotalKey(s string) OptionFn {
	return func(srvr *Server) {
		srvr.VirusTotalKey = s
	}
}

func Listener(s string) OptionFn {
	return func(srvr *Server) {
		srvr.ListenerString = s
	}

}

func GoogleAnalytics(gaKey string) OptionFn {
	return func(srvr *Server) {
		srvr.gaKey = gaKey
	}
}

func UserVoice(userVoiceKey string) OptionFn {
	return func(srvr *Server) {
		srvr.userVoiceKey = userVoiceKey
	}
}

func TLSListener(s string, t bool) OptionFn {
	return func(srvr *Server) {
		srvr.TLSListenerString = s
		srvr.TLSListenerOnly = t
	}

}

func ProfileListener(s string) OptionFn {
	return func(srvr *Server) {
		srvr.ProfileListenerString = s
	}
}

func WebPath(s string) OptionFn {
	return func(srvr *Server) {
		if s[len(s)-1:] != "/" {
			s = s + string(filepath.Separator)
		}

		srvr.webPath = s
	}
}

func ProxyPath(s string) OptionFn {
	return func(srvr *Server) {
		if s[len(s)-1:] != "/" {
			s = s + string(filepath.Separator)
		}

		srvr.proxyPath = s
	}
}

func TempPath(s string) OptionFn {
	return func(srvr *Server) {
		if s[len(s)-1:] != "/" {
			s = s + string(filepath.Separator)
		}

		srvr.tempPath = s
	}
}

func LogFile(logger *log.Logger, s string) OptionFn {
	return func(srvr *Server) {
		f, err := os.OpenFile(s, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("error opening file: %v", err)
		}

		logger.SetOutput(f)
		srvr.logger = logger
	}
}

func Logger(logger *log.Logger) OptionFn {
	return func(srvr *Server) {
		srvr.logger = logger
	}
}

func RateLimit(requests int) OptionFn {
	return func(srvr *Server) {
		srvr.rateLimitRequests = requests
	}
}

func ForceHTTPs() OptionFn {
	return func(srvr *Server) {
		srvr.forceHTTPs = true
	}
}

func EnableProfiler() OptionFn {
	return func(srvr *Server) {
		srvr.profilerEnabled = true
	}
}

func UseStorage(s Storage) OptionFn {
	return func(srvr *Server) {
		srvr.storage = s
	}
}

func UseLetsEncrypt(hosts []string) OptionFn {
	return func(srvr *Server) {
		cacheDir := "./cache/"

		m := autocert.Manager{
			Prompt: autocert.AcceptTOS,
			Cache:  autocert.DirCache(cacheDir),
			HostPolicy: func(_ context.Context, host string) error {
				found := false

				for _, h := range hosts {
					found = found || strings.HasSuffix(host, h)
				}

				if !found {
					return errors.New("acme/autocert: host not configured")
				}

				return nil
			},
		}

		srvr.tlsConfig = &tls.Config{
			GetCertificate: m.GetCertificate,
		}
	}
}

func TLSConfig(cert, pk string) OptionFn {
	certificate, err := tls.LoadX509KeyPair(cert, pk)
	return func(srvr *Server) {
		srvr.tlsConfig = &tls.Config{
			GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
				return &certificate, err
			},
		}
	}
}

func HttpAuthCredentials(user string, pass string) OptionFn {
	return func(srvr *Server) {
		srvr.AuthUser = user
		srvr.AuthPass = pass
	}
}

func FilterOptions(options IPFilterOptions) OptionFn {
	return func(srvr *Server) {
		srvr.ipFilterOptions = &options
	}
}

type Server struct {
	AuthUser string
	AuthPass string

	logger *log.Logger

	tlsConfig *tls.Config

	profilerEnabled bool

	locks map[string]*sync.Mutex

	rateLimitRequests int

	storage Storage

	forceHTTPs bool

	ipFilterOptions *IPFilterOptions

	VirusTotalKey    string
	ClamAVDaemonHost string

	tempPath string

	webPath      string
	proxyPath      string
	gaKey        string
	userVoiceKey string

	TLSListenerOnly bool

	ListenerString        string
	TLSListenerString     string
	ProfileListenerString string

	Certificate string

	LetsEncryptCache string
}

func New(options ...OptionFn) (*Server, error) {
	s := &Server{
		locks: map[string]*sync.Mutex{},
	}

	for _, optionFn := range options {
		optionFn(s)
	}

	return s, nil
}

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
}

func (s *Server) Run() {
	listening := false

	if s.profilerEnabled {
		listening = true

		go func() {
			s.logger.Println("Profiled listening at: :6060")

			http.ListenAndServe(":6060", nil)
		}()
	}

	r := mux.NewRouter()

	var fs http.FileSystem

	if s.webPath != "" {
		s.logger.Println("Using static file path: ", s.webPath)

		fs = http.Dir(s.webPath)

		htmlTemplates, _ = htmlTemplates.ParseGlob(s.webPath + "*.html")
		textTemplates, _ = textTemplates.ParseGlob(s.webPath + "*.txt")
	} else {
		fs = &assetfs.AssetFS{
			Asset:    web.Asset,
			AssetDir: web.AssetDir,
			AssetInfo: func(path string) (os.FileInfo, error) {
				return os.Stat(path)
			},
			Prefix: web.Prefix,
		}

		for _, path := range web.AssetNames() {
			bytes, err := web.Asset(path)
			if err != nil {
				s.logger.Panicf("Unable to parse: path=%s, err=%s", path, err)
			}

			htmlTemplates.New(stripPrefix(path)).Parse(string(bytes))
			textTemplates.New(stripPrefix(path)).Parse(string(bytes))
		}
	}

	staticHandler := http.FileServer(fs)

	r.PathPrefix("/images/").Handler(staticHandler).Methods("GET")
	r.PathPrefix("/styles/").Handler(staticHandler).Methods("GET")
	r.PathPrefix("/scripts/").Handler(staticHandler).Methods("GET")
	r.PathPrefix("/fonts/").Handler(staticHandler).Methods("GET")
	r.PathPrefix("/ico/").Handler(staticHandler).Methods("GET")
	r.HandleFunc("/favicon.ico", staticHandler.ServeHTTP).Methods("GET")
	r.HandleFunc("/robots.txt", staticHandler.ServeHTTP).Methods("GET")

	r.HandleFunc("/{filename:(?:favicon\\.ico|robots\\.txt|health\\.html)}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")

	r.HandleFunc("/health.html", healthHandler).Methods("GET")
	r.HandleFunc("/", s.viewHandler).Methods("GET")

	r.HandleFunc("/({files:.*}).zip", s.zipHandler).Methods("GET")
	r.HandleFunc("/({files:.*}).tar", s.tarHandler).Methods("GET")
	r.HandleFunc("/({files:.*}).tar.gz", s.tarGzHandler).Methods("GET")

	r.HandleFunc("/{token}/{filename}", s.headHandler).Methods("HEAD")
	r.HandleFunc("/{action:(?:download|get|inline)}/{token}/{filename}", s.headHandler).Methods("HEAD")

	r.HandleFunc("/{token}/{filename}", s.previewHandler).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) (match bool) {
		match = false

		// The file will show a preview page when opening the link in browser directly or
		// from external link. If the referer url path and current path are the same it will be
		// downloaded.
		if !acceptsHTML(r.Header) {
			return false
		}

		match = (r.Referer() == "")

		u, err := url.Parse(r.Referer())
		if err != nil {
			s.logger.Fatal(err)
			return
		}

		match = match || (u.Path != r.URL.Path)
		return
	}).Methods("GET")

	getHandlerFn := s.getHandler
	if s.rateLimitRequests > 0 {
		getHandlerFn = ratelimit.Request(ratelimit.IP).Rate(s.rateLimitRequests, 60*time.Second).LimitBy(memory.New())(http.HandlerFunc(getHandlerFn)).ServeHTTP
	}

	r.HandleFunc("/{token}/{filename}", getHandlerFn).Methods("GET")
	r.HandleFunc("/{action:(?:download|get|inline)}/{token}/{filename}", getHandlerFn).Methods("GET")

	r.HandleFunc("/{filename}/virustotal", s.virusTotalHandler).Methods("PUT")
	r.HandleFunc("/{filename}/scan", s.scanHandler).Methods("PUT")
	r.HandleFunc("/put/{filename}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
	r.HandleFunc("/upload/{filename}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
	r.HandleFunc("/{filename}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
	r.HandleFunc("/", s.BasicAuthHandler(http.HandlerFunc(s.postHandler))).Methods("POST")
	// r.HandleFunc("/{page}", viewHandler).Methods("GET")

	r.HandleFunc("/{token}/{filename}/{deletionToken}", s.deleteHandler).Methods("DELETE")

	r.NotFoundHandler = http.HandlerFunc(s.notFoundHandler)

	mime.AddExtensionType(".md", "text/x-markdown")

	s.logger.Printf("Transfer.sh server started.\nusing temp folder: %s\nusing storage provider: %s", s.tempPath, s.storage.Type())

	h := handlers.PanicHandler(
		IPFilterHandler(
			handlers.LogHandler(
				LoveHandler(
					s.RedirectHandler(r)),
				handlers.NewLogOptions(s.logger.Printf, "_default_"),
			),
			s.ipFilterOptions,
		),
		nil,
	)

	if !s.TLSListenerOnly {
		srvr := &http.Server{
			Addr:    s.ListenerString,
			Handler: h,
		}

		listening = true
		s.logger.Printf("listening on port: %v\n", s.ListenerString)

		go func() {
			srvr.ListenAndServe()
		}()
	}

	if s.TLSListenerString != "" {
		listening = true
		s.logger.Printf("listening on port: %v\n", s.TLSListenerString)

		go func() {
			s := &http.Server{
				Addr:      s.TLSListenerString,
				Handler:   h,
				TLSConfig: s.tlsConfig,
			}

			if err := s.ListenAndServeTLS("", ""); err != nil {
				panic(err)
			}
		}()
	}

	s.logger.Printf("---------------------------")

	term := make(chan os.Signal, 1)
	signal.Notify(term, os.Interrupt)
	signal.Notify(term, syscall.SIGTERM)

	if listening {
		<-term
	} else {
		s.logger.Printf("No listener active.")
	}

	s.logger.Printf("Server stopped.")
}
<commit_msg>ISSUE-223<commit_after>/*
The MIT License (MIT)

Copyright (c) 2014-2017 DutchCoders [https://github.com/dutchcoders/]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package server

import (
	"errors"
	"log"
	"math/rand"
	"mime"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	context "golang.org/x/net/context"

	"github.com/PuerkitoBio/ghost/handlers"
	"github.com/VojtechVitek/ratelimit"
	"github.com/VojtechVitek/ratelimit/memory"
	"github.com/gorilla/mux"

	_ "net/http/pprof"

	"crypto/tls"

	web "github.com/dutchcoders/transfer.sh-web"
	assetfs "github.com/elazarl/go-bindata-assetfs"

	autocert "golang.org/x/crypto/acme/autocert"
	"path/filepath"
)

const SERVER_INFO = "transfer.sh"

// parse request with maximum memory of _24Kilobits
const _24K = (1 << 3) * 24

// parse request with maximum memory of _5Megabytes
const _5M = (1 << 20) * 5

type OptionFn func(*Server)

func ClamavHost(s string) OptionFn {
	return func(srvr *Server) {
		srvr.ClamAVDaemonHost = s
	}
}

func VirustotalKey(s string) OptionFn {
	return func(srvr *Server) {
		srvr.VirusTotalKey = s
	}
}

func Listener(s string) OptionFn {
	return func(srvr *Server) {
		srvr.ListenerString = s
	}

}

func GoogleAnalytics(gaKey string) OptionFn {
	return func(srvr *Server) {
		srvr.gaKey = gaKey
	}
}

func UserVoice(userVoiceKey string) OptionFn {
	return func(srvr *Server) {
		srvr.userVoiceKey = userVoiceKey
	}
}

func TLSListener(s string, t bool) OptionFn {
	return func(srvr *Server) {
		srvr.TLSListenerString = s
		srvr.TLSListenerOnly = t
	}

}

func ProfileListener(s string) OptionFn {
	return func(srvr *Server) {
		srvr.ProfileListenerString = s
	}
}

func WebPath(s string) OptionFn {
	return func(srvr *Server) {
		if s[len(s)-1:] != "/" {
			s = s + string(filepath.Separator)
		}

		srvr.webPath = s
	}
}

func ProxyPath(s string) OptionFn {
	return func(srvr *Server) {
		if s[len(s)-1:] != "/" {
			s = s + string(filepath.Separator)
		}

		srvr.proxyPath = s
	}
}

func TempPath(s string) OptionFn {
	return func(srvr *Server) {
		if s[len(s)-1:] != "/" {
			s = s + string(filepath.Separator)
		}

		srvr.tempPath = s
	}
}

func LogFile(logger *log.Logger, s string) OptionFn {
	return func(srvr *Server) {
		f, err := os.OpenFile(s, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("error opening file: %v", err)
		}

		logger.SetOutput(f)
		srvr.logger = logger
	}
}

func Logger(logger *log.Logger) OptionFn {
	return func(srvr *Server) {
		srvr.logger = logger
	}
}

func RateLimit(requests int) OptionFn {
	return func(srvr *Server) {
		srvr.rateLimitRequests = requests
	}
}

func ForceHTTPs() OptionFn {
	return func(srvr *Server) {
		srvr.forceHTTPs = true
	}
}

func EnableProfiler() OptionFn {
	return func(srvr *Server) {
		srvr.profilerEnabled = true
	}
}

func UseStorage(s Storage) OptionFn {
	return func(srvr *Server) {
		srvr.storage = s
	}
}

func UseLetsEncrypt(hosts []string) OptionFn {
	return func(srvr *Server) {
		cacheDir := "./cache/"

		m := autocert.Manager{
			Prompt: autocert.AcceptTOS,
			Cache:  autocert.DirCache(cacheDir),
			HostPolicy: func(_ context.Context, host string) error {
				found := false

				for _, h := range hosts {
					found = found || strings.HasSuffix(host, h)
				}

				if !found {
					return errors.New("acme/autocert: host not configured")
				}

				return nil
			},
		}

		srvr.tlsConfig = &tls.Config{
			GetCertificate: m.GetCertificate,
		}
	}
}

func TLSConfig(cert, pk string) OptionFn {
	certificate, err := tls.LoadX509KeyPair(cert, pk)
	return func(srvr *Server) {
		srvr.tlsConfig = &tls.Config{
			GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
				return &certificate, err
			},
		}
	}
}

func HttpAuthCredentials(user string, pass string) OptionFn {
	return func(srvr *Server) {
		srvr.AuthUser = user
		srvr.AuthPass = pass
	}
}

func FilterOptions(options IPFilterOptions) OptionFn {
	for i, allowedIP := range options.AllowedIPs {
		options.AllowedIPs[i] = strings.TrimSpace(allowedIP)
	}

	for i, blockedIP := range options.BlockedIPs {
		options.BlockedIPs[i] = strings.TrimSpace(blockedIP)
	}

	return func(srvr *Server) {
		srvr.ipFilterOptions = &options
	}
}

type Server struct {
	AuthUser string
	AuthPass string

	logger *log.Logger

	tlsConfig *tls.Config

	profilerEnabled bool

	locks map[string]*sync.Mutex

	rateLimitRequests int

	storage Storage

	forceHTTPs bool

	ipFilterOptions *IPFilterOptions

	VirusTotalKey    string
	ClamAVDaemonHost string

	tempPath string

	webPath      string
	proxyPath      string
	gaKey        string
	userVoiceKey string

	TLSListenerOnly bool

	ListenerString        string
	TLSListenerString     string
	ProfileListenerString string

	Certificate string

	LetsEncryptCache string
}

func New(options ...OptionFn) (*Server, error) {
	s := &Server{
		locks: map[string]*sync.Mutex{},
	}

	for _, optionFn := range options {
		optionFn(s)
	}

	return s, nil
}

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
}

func (s *Server) Run() {
	listening := false

	if s.profilerEnabled {
		listening = true

		go func() {
			s.logger.Println("Profiled listening at: :6060")

			http.ListenAndServe(":6060", nil)
		}()
	}

	r := mux.NewRouter()

	var fs http.FileSystem

	if s.webPath != "" {
		s.logger.Println("Using static file path: ", s.webPath)

		fs = http.Dir(s.webPath)

		htmlTemplates, _ = htmlTemplates.ParseGlob(s.webPath + "*.html")
		textTemplates, _ = textTemplates.ParseGlob(s.webPath + "*.txt")
	} else {
		fs = &assetfs.AssetFS{
			Asset:    web.Asset,
			AssetDir: web.AssetDir,
			AssetInfo: func(path string) (os.FileInfo, error) {
				return os.Stat(path)
			},
			Prefix: web.Prefix,
		}

		for _, path := range web.AssetNames() {
			bytes, err := web.Asset(path)
			if err != nil {
				s.logger.Panicf("Unable to parse: path=%s, err=%s", path, err)
			}

			htmlTemplates.New(stripPrefix(path)).Parse(string(bytes))
			textTemplates.New(stripPrefix(path)).Parse(string(bytes))
		}
	}

	staticHandler := http.FileServer(fs)

	r.PathPrefix("/images/").Handler(staticHandler).Methods("GET")
	r.PathPrefix("/styles/").Handler(staticHandler).Methods("GET")
	r.PathPrefix("/scripts/").Handler(staticHandler).Methods("GET")
	r.PathPrefix("/fonts/").Handler(staticHandler).Methods("GET")
	r.PathPrefix("/ico/").Handler(staticHandler).Methods("GET")
	r.HandleFunc("/favicon.ico", staticHandler.ServeHTTP).Methods("GET")
	r.HandleFunc("/robots.txt", staticHandler.ServeHTTP).Methods("GET")

	r.HandleFunc("/{filename:(?:favicon\\.ico|robots\\.txt|health\\.html)}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")

	r.HandleFunc("/health.html", healthHandler).Methods("GET")
	r.HandleFunc("/", s.viewHandler).Methods("GET")

	r.HandleFunc("/({files:.*}).zip", s.zipHandler).Methods("GET")
	r.HandleFunc("/({files:.*}).tar", s.tarHandler).Methods("GET")
	r.HandleFunc("/({files:.*}).tar.gz", s.tarGzHandler).Methods("GET")

	r.HandleFunc("/{token}/{filename}", s.headHandler).Methods("HEAD")
	r.HandleFunc("/{action:(?:download|get|inline)}/{token}/{filename}", s.headHandler).Methods("HEAD")

	r.HandleFunc("/{token}/{filename}", s.previewHandler).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) (match bool) {
		match = false

		// The file will show a preview page when opening the link in browser directly or
		// from external link. If the referer url path and current path are the same it will be
		// downloaded.
		if !acceptsHTML(r.Header) {
			return false
		}

		match = (r.Referer() == "")

		u, err := url.Parse(r.Referer())
		if err != nil {
			s.logger.Fatal(err)
			return
		}

		match = match || (u.Path != r.URL.Path)
		return
	}).Methods("GET")

	getHandlerFn := s.getHandler
	if s.rateLimitRequests > 0 {
		getHandlerFn = ratelimit.Request(ratelimit.IP).Rate(s.rateLimitRequests, 60*time.Second).LimitBy(memory.New())(http.HandlerFunc(getHandlerFn)).ServeHTTP
	}

	r.HandleFunc("/{token}/{filename}", getHandlerFn).Methods("GET")
	r.HandleFunc("/{action:(?:download|get|inline)}/{token}/{filename}", getHandlerFn).Methods("GET")

	r.HandleFunc("/{filename}/virustotal", s.virusTotalHandler).Methods("PUT")
	r.HandleFunc("/{filename}/scan", s.scanHandler).Methods("PUT")
	r.HandleFunc("/put/{filename}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
	r.HandleFunc("/upload/{filename}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
	r.HandleFunc("/{filename}", s.BasicAuthHandler(http.HandlerFunc(s.putHandler))).Methods("PUT")
	r.HandleFunc("/", s.BasicAuthHandler(http.HandlerFunc(s.postHandler))).Methods("POST")
	// r.HandleFunc("/{page}", viewHandler).Methods("GET")

	r.HandleFunc("/{token}/{filename}/{deletionToken}", s.deleteHandler).Methods("DELETE")

	r.NotFoundHandler = http.HandlerFunc(s.notFoundHandler)

	mime.AddExtensionType(".md", "text/x-markdown")

	s.logger.Printf("Transfer.sh server started.\nusing temp folder: %s\nusing storage provider: %s", s.tempPath, s.storage.Type())

	h := handlers.PanicHandler(
		IPFilterHandler(
			handlers.LogHandler(
				LoveHandler(
					s.RedirectHandler(r)),
				handlers.NewLogOptions(s.logger.Printf, "_default_"),
			),
			s.ipFilterOptions,
		),
		nil,
	)

	if !s.TLSListenerOnly {
		srvr := &http.Server{
			Addr:    s.ListenerString,
			Handler: h,
		}

		listening = true
		s.logger.Printf("listening on port: %v\n", s.ListenerString)

		go func() {
			srvr.ListenAndServe()
		}()
	}

	if s.TLSListenerString != "" {
		listening = true
		s.logger.Printf("listening on port: %v\n", s.TLSListenerString)

		go func() {
			s := &http.Server{
				Addr:      s.TLSListenerString,
				Handler:   h,
				TLSConfig: s.tlsConfig,
			}

			if err := s.ListenAndServeTLS("", ""); err != nil {
				panic(err)
			}
		}()
	}

	s.logger.Printf("---------------------------")

	term := make(chan os.Signal, 1)
	signal.Notify(term, os.Interrupt)
	signal.Notify(term, syscall.SIGTERM)

	if listening {
		<-term
	} else {
		s.logger.Printf("No listener active.")
	}

	s.logger.Printf("Server stopped.")
}
<|endoftext|>


<commit_before>package main

import (
	// General
	"github.com/golang/glog"
	"flag"
	"github.com/iambc/xerrors"
	"reflect"
	"os"

	//API
	"net/http"
	"encoding/json"

	//DB
	"database/sql"
	_ "github.com/lib/pq"
)

/*
TODO:
2) Add different input/output formats for the API
6) quote of the day
*/

type image_board_clusters struct {
    Id int
    Descr string
    LongDescr string
    BoardLimitCount int
}

type boards struct {
    Id int
    Name string
    Descr string
    ImageBoardClusterId string
    MaxThreadCount int //to be checked in insert thread
    MaxActiveThreadCount int //to be checked  in insert thread
    MaxPostsPerThread int // to be checked in insert thread
    AreAttachmentsAllowed bool // to be checked in insert post
    PostLimitsReachedActionId int // to be checked in insert post
}

type threads struct{
    Id int
    Name string
    Descr string
    BoardId int
    MaxPostsPerThread int
    AreAttachmentsAllowed bool
    LimitsReachedActionId int
}

type thread_posts struct{
    Id int
    Body string
    ThreadId int
    AttachmentUrl *string
}

type thread_limits_reached_actions struct{
    Id	    int
    Name    string
    Descr   string
}

type api_request struct{
    Status  string
    Msg	    *string
    Payload interface{}
}

func getBoards(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
	return []byte{}, xerrors.NewSysErr()
    }

    values := req.URL.Query()
    api_key := values[`api_key`][0]
    rows, err := dbh.Query("select b.id, b.name, b.descr from boards b join image_board_clusters ibc on ibc.id = b.image_board_cluster_id where api_key = $1;", api_key)
    if err != nil {
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `002`, true)
    }
    defer rows.Close()

    var curr_boards []boards
    for rows.Next() {
	var board boards
	err = rows.Scan(&board.Id, &board.Name, &board.Descr)
	if err != nil {
	    return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `003`, true)
	}
	curr_boards = append(curr_boards, board)
    }
    bytes, err1 := json.Marshal(api_request{"ok", nil, &curr_boards})
    if err1 != nil {
	return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `004`, true)
    }
    return bytes, nil
}

func getActiveThreadsForBoard(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    board_id, is_passed := values[`board_id`]
    if !is_passed {
	return []byte{}, xerrors.NewUIErr(`Invalid params: No board_id given!`, `Invalid params: No board_id given!`, `005`, true)
    }

    api_key := values[`api_key`][0]
    rows, err := dbh.Query(`select t.id, t.name from threads t
				join boards b on b.id = t.board_id
				join image_board_clusters ibc on ibc.id = b.image_board_cluster_id
			    where t.is_active = TRUE and t.board_id = $1 and ibc.api_key = $2;`, board_id[0], api_key)
    if err != nil {
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `006`, true)
    }
    defer rows.Close()

    var active_threads []threads
    for rows.Next() {
	glog.Info("Popped new thread")
        var thread threads
        err = rows.Scan(&thread.Id, &thread.Name)
        if err != nil {
            return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `007`, true)
        }
        active_threads = append(active_threads, thread)
    }
    var bytes []byte
    var err1 error
    if(len(active_threads) == 0){
        errMsg := "No objects returned."
        bytes, err1 = json.Marshal(api_request{"error", &errMsg, &active_threads})
    }else {
        bytes, err1 = json.Marshal(api_request{"ok", nil, &active_threads})
    }

    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `008`, true)
    }

    return bytes, nil
}

func getPostsForThread(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    thread_id, is_passed := values[`thread_id`]
    if !is_passed {
        return []byte{},xerrors.NewUIErr(`Invalid params: No thread_id given!`, `Invalid params: No thread_id given!`, `006`, true)
    }

    api_key := values[`api_key`][0]
    rows, err := dbh.Query(`select tp.id, tp.body, tp.attachment_url
			    from thread_posts tp join threads t on t.id = tp.thread_id
						 join boards b on b.id = t.board_id
						 join image_board_clusters ibc on ibc.id = b.image_board_cluster_id
			    where tp.thread_id = $1 and ibc.api_key = $2 and t.is_active = true;`, thread_id[0], api_key)
    if err != nil {
	glog.Error(err)
        return []byte{}, xerrors.NewSysErr()
    }
    defer rows.Close()

    var curr_posts []thread_posts
    for rows.Next() {
	glog.Info("new post for thread with id: ", thread_id[0])
        var curr_post thread_posts
        err = rows.Scan(&curr_post.Id, &curr_post.Body, &curr_post.AttachmentUrl)
        if err != nil {
	    glog.Error(err)
            return []byte{}, xerrors.NewSysErr()
        }
        curr_posts = append(curr_posts, curr_post)
    }

    var bytes []byte
    var err1 error
    if(len(curr_posts) == 0){
	errMsg := "No objects returned."
	bytes, err1 = json.Marshal(api_request{"error", &errMsg, &curr_posts})
    }else {
	bytes, err1 = json.Marshal(api_request{"ok", nil, &curr_posts})
    }

    if err1 != nil {
        return []byte{}, xerrors.NewSysErr()
    }

    return bytes, nil
}

func addPostToThread(res http.ResponseWriter, req *http.Request) ([]byte,error) {
    if req == nil || res == nil{
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    thread_id, is_passed := values[`thread_id`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_id given!`, `Invalid params: No thread_id given!`, `001`, true)
    }

    thread_body_post, is_passed := values[`thread_post_body`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_post_body given!`, `Invalid params: No thread_post_body given!`, `001`, true)
    }

    var is_limit_reached bool
    err := dbh.QueryRow("select (select count(*) from thread_posts  where thread_id = $1) > max_posts_per_thread  from threads where id = $1;", thread_id[0]).Scan(&is_limit_reached)
    if err != nil {
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `009`, true)
    }

    if is_limit_reached {
	dbh.QueryRow("UPDATE threads set is_active = false where id = $1",  thread_id[0]).Scan()
	return []byte{}, xerrors.NewUIErr(`Thread post limit reached!`, `Thread post limit reached!`, `010`, true)
    }

    attachment_urls, is_passed := values[`attachment_url`]
    var attachment_url *string
    if !is_passed{
	attachment_url = nil
    }else{
	attachment_url = &attachment_urls[0]
    }

    _, err = dbh.Query("INSERT INTO thread_posts(body, thread_id, attachment_url) VALUES($1, $2, $3)", thread_body_post[0], thread_id[0], attachment_url)

    if err != nil {
	glog.Error(err)
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `011`, true)
    }

    bytes, err1 := json.Marshal(api_request{"ok", nil, nil})
    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `012`, true)
    }

    return bytes, nil
}

func addThread(res http.ResponseWriter, req *http.Request) ([]byte,error) {
    if req == nil || res == nil{
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()

    thread_name, is_passed := values[`thread_name`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_name given!`, `Invalid params: No thread_name given!`, `013`, true)
    }

    board_id, is_passed := values[`board_id`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No board_id given!`, `Invalid params: No board_id given!`, `014`, true)
    }

    var is_limit_reached bool
    err := dbh.QueryRow("select (select count(*) from threads  where board_id = $1) > thread_setting_max_thread_count  from boards where id = $1;", board_id[0]).Scan(&is_limit_reached)
    if err != nil {
	glog.Error("COULD NOT SELECT thread_count")
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `015`, true)
    }
    if is_limit_reached {
	return []byte{}, xerrors.NewUIErr(`Thread limit reached!`, `Thread limit reached!`, `016`, true)
    }

    _, err = dbh.Query("INSERT INTO threads(name, board_id, limits_reached_action_id, max_posts_per_thread) VALUES($1, $2, 1, 10)", thread_name[0], board_id[0])

    if err != nil {
	glog.Error("INSERT FAILED")
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `017`, true)
    }

    bytes, err1 := json.Marshal(api_request{"ok", nil, nil})
    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `018`, true)
    }

    return bytes, nil
}

var dbConnString = ``
var dbh *sql.DB

// sample usage
func main() {
    flag.Parse()

    var err error
    dbConnString = os.Getenv("ABC_DB_CONN_STRING") // DB will return error if empty string
    dbh, err = sql.Open("postgres", dbConnString)
    if err != nil {
	glog.Fatal(err)
    }

    commands := map[string]func(http.ResponseWriter, *http.Request) ([]byte, error){
				"getBoards": getBoards,
				"getActiveThreadsForBoard": getActiveThreadsForBoard,
				"getPostsForThread": getPostsForThread,
				"addPostToThread": addPostToThread,
				"addThread": addThread,
			       }

    http.HandleFunc("/api", func(res http.ResponseWriter, req *http.Request) {
					values := req.URL.Query()
					command, is_passed := values[`command`]
					if !is_passed {
					    res.Write([]byte(`{"Status":"error","Msg":"Paremeter 'command' is undefined.","Payload":null}`))
					    return
					}

					_, is_passed = values[`api_key`]
					if !is_passed {
					    res.Write([]byte(`{"Status":"error","Msg":"Paremeter 'api_key' is undefined.","Payload":null}`))
					    return
					}

					_, is_passed = commands[command[0]]
					if !is_passed{
					    res.Write([]byte(`{"Status":"error","Msg":"No such command exists.","Payload":null}`))
					    glog.Error("command: ", command[0])
					    return
					}

					res.Header().Set("Access-Control-Allow-Origin", "*")
					bytes, err := commands[command[0]](res, req)

					if err != nil{
					    if string(reflect.TypeOf(err).Name())  == `SysErr` {
						res.Write([]byte(`{"Status":"error","Msg":"` + err.Error()  +`","Payload":null}`))
					    } else if string(reflect.TypeOf(err).Name())  == `UIErr` {
						res.Write([]byte(`{"Status":"error","Msg":"`+ err.Error() +`","Payload":null}`))
					    } else {
						res.Write([]byte(`{"Status":"error","Msg":"Application Error!","Payload":null}`))
					    }
					    glog.Error(err)
					    return
					}

					glog.Info(string(bytes))
					res.Write(bytes)
    })

    http.ListenAndServe(`:`+ os.Getenv("ABC_SERVER_ENDPOINT_URL"), nil)
}

<commit_msg>feat: the interface is hosted<commit_after>package main

import (
	// General
	"github.com/golang/glog"
	"flag"
	"github.com/iambc/xerrors"
	"reflect"
	"os"

	//API
	"net/http"
	"encoding/json"

	//DB
	"database/sql"
	_ "github.com/lib/pq"
)

/*
TODO:
2) Add different input/output formats for the API
6) quote of the day
*/

type image_board_clusters struct {
    Id int
    Descr string
    LongDescr string
    BoardLimitCount int
}

type boards struct {
    Id int
    Name string
    Descr string
    ImageBoardClusterId string
    MaxThreadCount int //to be checked in insert thread
    MaxActiveThreadCount int //to be checked  in insert thread
    MaxPostsPerThread int // to be checked in insert thread
    AreAttachmentsAllowed bool // to be checked in insert post
    PostLimitsReachedActionId int // to be checked in insert post
}

type threads struct{
    Id int
    Name string
    Descr string
    BoardId int
    MaxPostsPerThread int
    AreAttachmentsAllowed bool
    LimitsReachedActionId int
}

type thread_posts struct{
    Id int
    Body string
    ThreadId int
    AttachmentUrl *string
}

type thread_limits_reached_actions struct{
    Id	    int
    Name    string
    Descr   string
}

type api_request struct{
    Status  string
    Msg	    *string
    Payload interface{}
}

func getBoards(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
	return []byte{}, xerrors.NewSysErr()
    }

    values := req.URL.Query()
    api_key := values[`api_key`][0]
    rows, err := dbh.Query("select b.id, b.name, b.descr from boards b join image_board_clusters ibc on ibc.id = b.image_board_cluster_id where api_key = $1;", api_key)
    if err != nil {
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `002`, true)
    }
    defer rows.Close()

    var curr_boards []boards
    for rows.Next() {
	var board boards
	err = rows.Scan(&board.Id, &board.Name, &board.Descr)
	if err != nil {
	    return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `003`, true)
	}
	curr_boards = append(curr_boards, board)
    }
    bytes, err1 := json.Marshal(api_request{"ok", nil, &curr_boards})
    if err1 != nil {
	return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `004`, true)
    }
    return bytes, nil
}

func getActiveThreadsForBoard(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    board_id, is_passed := values[`board_id`]
    if !is_passed {
	return []byte{}, xerrors.NewUIErr(`Invalid params: No board_id given!`, `Invalid params: No board_id given!`, `005`, true)
    }

    api_key := values[`api_key`][0]
    rows, err := dbh.Query(`select t.id, t.name from threads t
				join boards b on b.id = t.board_id
				join image_board_clusters ibc on ibc.id = b.image_board_cluster_id
			    where t.is_active = TRUE and t.board_id = $1 and ibc.api_key = $2;`, board_id[0], api_key)
    if err != nil {
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `006`, true)
    }
    defer rows.Close()

    var active_threads []threads
    for rows.Next() {
	glog.Info("Popped new thread")
        var thread threads
        err = rows.Scan(&thread.Id, &thread.Name)
        if err != nil {
            return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `007`, true)
        }
        active_threads = append(active_threads, thread)
    }
    var bytes []byte
    var err1 error
    if(len(active_threads) == 0){
        errMsg := "No objects returned."
        bytes, err1 = json.Marshal(api_request{"error", &errMsg, &active_threads})
    }else {
        bytes, err1 = json.Marshal(api_request{"ok", nil, &active_threads})
    }

    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `008`, true)
    }

    return bytes, nil
}

func getPostsForThread(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    thread_id, is_passed := values[`thread_id`]
    if !is_passed {
        return []byte{},xerrors.NewUIErr(`Invalid params: No thread_id given!`, `Invalid params: No thread_id given!`, `006`, true)
    }

    api_key := values[`api_key`][0]
    rows, err := dbh.Query(`select tp.id, tp.body, tp.attachment_url
			    from thread_posts tp join threads t on t.id = tp.thread_id
						 join boards b on b.id = t.board_id
						 join image_board_clusters ibc on ibc.id = b.image_board_cluster_id
			    where tp.thread_id = $1 and ibc.api_key = $2 and t.is_active = true;`, thread_id[0], api_key)
    if err != nil {
	glog.Error(err)
        return []byte{}, xerrors.NewSysErr()
    }
    defer rows.Close()

    var curr_posts []thread_posts
    for rows.Next() {
	glog.Info("new post for thread with id: ", thread_id[0])
        var curr_post thread_posts
        err = rows.Scan(&curr_post.Id, &curr_post.Body, &curr_post.AttachmentUrl)
        if err != nil {
	    glog.Error(err)
            return []byte{}, xerrors.NewSysErr()
        }
        curr_posts = append(curr_posts, curr_post)
    }

    var bytes []byte
    var err1 error
    if(len(curr_posts) == 0){
	errMsg := "No objects returned."
	bytes, err1 = json.Marshal(api_request{"error", &errMsg, &curr_posts})
    }else {
	bytes, err1 = json.Marshal(api_request{"ok", nil, &curr_posts})
    }

    if err1 != nil {
        return []byte{}, xerrors.NewSysErr()
    }

    return bytes, nil
}

func addPostToThread(res http.ResponseWriter, req *http.Request) ([]byte,error) {
    if req == nil || res == nil{
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    thread_id, is_passed := values[`thread_id`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_id given!`, `Invalid params: No thread_id given!`, `001`, true)
    }

    thread_body_post, is_passed := values[`thread_post_body`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_post_body given!`, `Invalid params: No thread_post_body given!`, `001`, true)
    }

    var is_limit_reached bool
    err := dbh.QueryRow("select (select count(*) from thread_posts  where thread_id = $1) > max_posts_per_thread  from threads where id = $1;", thread_id[0]).Scan(&is_limit_reached)
    if err != nil {
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `009`, true)
    }

    if is_limit_reached {
	dbh.QueryRow("UPDATE threads set is_active = false where id = $1",  thread_id[0]).Scan()
	return []byte{}, xerrors.NewUIErr(`Thread post limit reached!`, `Thread post limit reached!`, `010`, true)
    }

    attachment_urls, is_passed := values[`attachment_url`]
    var attachment_url *string
    if !is_passed{
	attachment_url = nil
    }else{
	attachment_url = &attachment_urls[0]
    }

    _, err = dbh.Query("INSERT INTO thread_posts(body, thread_id, attachment_url) VALUES($1, $2, $3)", thread_body_post[0], thread_id[0], attachment_url)

    if err != nil {
	glog.Error(err)
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `011`, true)
    }

    bytes, err1 := json.Marshal(api_request{"ok", nil, nil})
    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `012`, true)
    }

    return bytes, nil
}

func addThread(res http.ResponseWriter, req *http.Request) ([]byte,error) {
    if req == nil || res == nil{
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()

    thread_name, is_passed := values[`thread_name`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_name given!`, `Invalid params: No thread_name given!`, `013`, true)
    }

    board_id, is_passed := values[`board_id`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No board_id given!`, `Invalid params: No board_id given!`, `014`, true)
    }

    var is_limit_reached bool
    err := dbh.QueryRow("select (select count(*) from threads  where board_id = $1) > thread_setting_max_thread_count  from boards where id = $1;", board_id[0]).Scan(&is_limit_reached)
    if err != nil {
	glog.Error("COULD NOT SELECT thread_count")
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `015`, true)
    }
    if is_limit_reached {
	return []byte{}, xerrors.NewUIErr(`Thread limit reached!`, `Thread limit reached!`, `016`, true)
    }

    _, err = dbh.Query("INSERT INTO threads(name, board_id, limits_reached_action_id, max_posts_per_thread) VALUES($1, $2, 1, 10)", thread_name[0], board_id[0])

    if err != nil {
	glog.Error("INSERT FAILED")
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `017`, true)
    }

    bytes, err1 := json.Marshal(api_request{"ok", nil, nil})
    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `018`, true)
    }

    return bytes, nil
}

var dbConnString = ``
var dbh *sql.DB

// sample usage
func main() {
    flag.Parse()

    var err error
    dbConnString = os.Getenv("ABC_DB_CONN_STRING") // DB will return error if empty string
    dbh, err = sql.Open("postgres", dbConnString)
    if err != nil {
	glog.Fatal(err)
    }

    commands := map[string]func(http.ResponseWriter, *http.Request) ([]byte, error){
				"getBoards": getBoards,
				"getActiveThreadsForBoard": getActiveThreadsForBoard,
				"getPostsForThread": getPostsForThread,
				"addPostToThread": addPostToThread,
				"addThread": addThread,
			       }

    http.HandleFunc("/api", func(res http.ResponseWriter, req *http.Request) {
					values := req.URL.Query()
					command, is_passed := values[`command`]
					if !is_passed {
					    res.Write([]byte(`{"Status":"error","Msg":"Paremeter 'command' is undefined.","Payload":null}`))
					    return
					}

					_, is_passed = values[`api_key`]
					if !is_passed {
					    res.Write([]byte(`{"Status":"error","Msg":"Paremeter 'api_key' is undefined.","Payload":null}`))
					    return
					}

					_, is_passed = commands[command[0]]
					if !is_passed{
					    res.Write([]byte(`{"Status":"error","Msg":"No such command exists.","Payload":null}`))
					    glog.Error("command: ", command[0])
					    return
					}

					res.Header().Set("Access-Control-Allow-Origin", "*")
					bytes, err := commands[command[0]](res, req)

					if err != nil{
					    if string(reflect.TypeOf(err).Name())  == `SysErr` {
						res.Write([]byte(`{"Status":"`+ xerrors.SysErrCode +`","Msg":"` + err.Error()  +`","Payload":null}`))
					    } else if string(reflect.TypeOf(err).Name())  == `UIErr` {
						res.Write([]byte(`{"Status":"` + err.(xerrors.UIErr).Code + `","Msg":"`+ err.Error() +`","Payload":null}`))
					    } else {
						res.Write([]byte(`{"Status":"000","Msg":"Application Error!","Payload":null}`))
					    }
					    glog.Error(err)
					    return
					}

					glog.Info(string(bytes))
					res.Write(bytes)
    })

    http.Handle("/f/", http.StripPrefix("/f/", http.FileServer(http.Dir(os.Getenv("ABC_FILES_DIR")))))

    http.ListenAndServe(`:`+ os.Getenv("ABC_SERVER_ENDPOINT_URL"), nil)
}

<|endoftext|>