<commit_before>package main

import (
  "database/sql"
  _ "github.com/go-sql-driver/mysql"
  "github.com/codegangsta/martini"
  "encoding/json"
  "strconv"
  "net/http"
)

func main() {
  m := martini.Classic()

  db, err := sql.Open("mysql", "root@127.0.0.1/ops")
  if err != nil { panic(err) }
  defer db.Close()

  m.Map(db)

  schoolOne := School{
    Id: 1,
    Name: "Millard North High School",
    CountyId: 1,
    DistrictId: 1,
    ClassStats: []ClassStat{
      ClassStat{
        SchoolId: 1,
        Years: "2012-2013",
        Grade: "6",
        MaleStudents: "10",
        FemaleStudents: "15",
        TotalStudents: "25",
      },
    },
  }
  schoolTwo := School{
    Id: 2,
    Name: "Millard South High School",
    CountyId: 1,
    DistrictId: 1,
    ClassStats: []ClassStat{},
  }
  schools := []School{schoolOne, schoolTwo}

  entry2012 := DistrictYear{
    EnrollmentSize: 15,
    District: District{
      Id: 15,
      Name: "OPS",
      Latitude: 72.12345,
      Longitude: 45.215,
    },
  }

  allDistricts := []DistrictsByYear{
    DistrictsByYear{
      Year: "2012-2013",
      Districts: []DistrictYear{entry2012},
    },
    DistrictsByYear{
      Year: "2011-2012",
      Districts: []DistrictYear{entry2012},
    },
  }

  m.Get("/schools", func(res http.ResponseWriter) string {
    return render(res, schools)
  })

  m.Get("/districts", func(res http.ResponseWriter) string {
    return render(res, allDistricts)
  })

  m.Get("/schools/:id", func(res http.ResponseWriter, params martini.Params) string {
    school := schoolFind(schools, params["id"])
    return render(res, school)
  })

  m.Get("/schools/:id/:year", func(res http.ResponseWriter, params martini.Params) string {
    return "WOOOOO"
  })

  m.Run()
}

func render(res http.ResponseWriter, data interface{}) string {
  thing, err := json.Marshal(data)
  if err != nil { panic(err) }
  return asJson(res, thing)
}

func asJson(res http.ResponseWriter, data []byte) string {
  res.Header().Set("Content-Type", "application/json")
  return string(data[:])
}

func schoolFind(schools []School, id string) School {
  schoolId, err := strconv.ParseInt(id, 0, 64)
  if err != nil { panic(err) }

  for _, value := range schools {
    if value.Id == schoolId {
      return value
    }
  }

  return School{}
}

type School struct {
  Id          int64
  Name        string `sql:"size:255"`
  CountyId    int64
  DistrictId  int64
  ClassStats  []ClassStat
}

type District struct {
  Id              int64
  Name            string
  Latitude        float64
  Longitude       float64
}

type DistrictsByYear struct {
  Year      string
  Districts []DistrictYear
}

type DistrictYear struct {
  EnrollmentSize  int64
  District        District
}

type ClassStat struct {
  SchoolId        int64
  Years           string
  Grade           string
  MaleStudents    string
  FemaleStudents  string
  TotalStudents   string
}
<commit_msg>disabling security lol<commit_after>package main

import (
  "database/sql"
  _ "github.com/go-sql-driver/mysql"
  "github.com/codegangsta/martini"
  "encoding/json"
  "strconv"
  "net/http"
)

func main() {
  m := martini.Classic()

  db, err := sql.Open("mysql", "root@127.0.0.1/ops")
  if err != nil { panic(err) }
  defer db.Close()

  m.Map(db)

  schoolOne := School{
    Id: 1,
    Name: "Millard North High School",
    CountyId: 1,
    DistrictId: 1,
    ClassStats: []ClassStat{
      ClassStat{
        SchoolId: 1,
        Years: "2012-2013",
        Grade: "6",
        MaleStudents: "10",
        FemaleStudents: "15",
        TotalStudents: "25",
      },
    },
  }
  schoolTwo := School{
    Id: 2,
    Name: "Millard South High School",
    CountyId: 1,
    DistrictId: 1,
    ClassStats: []ClassStat{},
  }
  schools := []School{schoolOne, schoolTwo}

  entry2012 := DistrictYear{
    EnrollmentSize: 15,
    District: District{
      Id: 15,
      Name: "OPS",
      Latitude: 72.12345,
      Longitude: 45.215,
    },
  }

  allDistricts := []DistrictsByYear{
    DistrictsByYear{
      Year: "2012-2013",
      Districts: []DistrictYear{entry2012},
    },
    DistrictsByYear{
      Year: "2011-2012",
      Districts: []DistrictYear{entry2012},
    },
  }

  m.Get("/schools", func(res http.ResponseWriter) string {
    return render(res, schools)
  })

  m.Get("/districts", func(res http.ResponseWriter) string {
    return render(res, allDistricts)
  })

  m.Get("/schools/:id", func(res http.ResponseWriter, params martini.Params) string {
    school := schoolFind(schools, params["id"])
    return render(res, school)
  })

  m.Get("/schools/:id/:year", func(res http.ResponseWriter, params martini.Params) string {
    return "WOOOOO"
  })

  m.Run()
}

func render(res http.ResponseWriter, data interface{}) string {
  thing, err := json.Marshal(data)
  if err != nil { panic(err) }
  return asJson(res, thing)
}

func asJson(res http.ResponseWriter, data []byte) string {
  res.Header().Set("Content-Type", "application/json")
  res.Header().Set("Access-Control-Allow-Origin", "*")
  return string(data[:])
}

func schoolFind(schools []School, id string) School {
  schoolId, err := strconv.ParseInt(id, 0, 64)
  if err != nil { panic(err) }

  for _, value := range schools {
    if value.Id == schoolId {
      return value
    }
  }

  return School{}
}

type School struct {
  Id          int64
  Name        string `sql:"size:255"`
  CountyId    int64
  DistrictId  int64
  ClassStats  []ClassStat
}

type District struct {
  Id              int64
  Name            string
  Latitude        float64
  Longitude       float64
}

type DistrictsByYear struct {
  Year      string
  Districts []DistrictYear
}

type DistrictYear struct {
  EnrollmentSize  int64
  District        District
}

type ClassStat struct {
  SchoolId        int64
  Years           string
  Grade           string
  MaleStudents    string
  FemaleStudents  string
  TotalStudents   string
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"github.com/dimfeld/glog"
	"github.com/dimfeld/httptreemux"
	"net"
	"net/http"
	"strings"
)

type HookHandler func(http.ResponseWriter, *http.Request, map[string]string, *Hook)

func hookHandler(w http.ResponseWriter, r *http.Request, params map[string]string, hook *Hook) {
	githubEventType := r.Header.Get("X-GitHub-Event")

	if r.ContentLength > 16384 {
		// We should never get a request this large.
		w.WriteHeader(http.StatusRequestEntityTooLarge)
		return
	}

	buffer := bytes.Buffer{}
	buffer.ReadFrom(r.Body)
	r.Body.Close()

	if glog.V(2) {
		niceBuffer := &bytes.Buffer{}
		json.Indent(niceBuffer, buffer.Bytes(), "", "  ")
		glog.Infof("Hook %s received data %s\n",
			r.URL.Path, string(niceBuffer.Bytes()))
	}

	if hook.Secret != "" {
		secret := r.Header.Get("X-Hub-Signature")
		if !strings.HasPrefix(secret, "sha1=") {
			glog.Warningf("Request with no secret for hook %s from %s\n",
				r.URL.Path, r.RemoteAddr)
			w.WriteHeader(http.StatusForbidden)
			return
		}

		hash := hmac.New(sha1.New, []byte(hook.Secret))
		hash.Write(buffer.Bytes())
		expected := hash.Sum(nil)
		seen, err := hex.DecodeString(secret[5:])
		if err != nil || !hmac.Equal(expected, seen) {
			glog.Warningf("Request with bad secret for hook %s from %s\nExpected %s, saw %s",
				r.URL.Path, r.RemoteAddr, hex.EncodeToString(expected), secret)
			w.WriteHeader(http.StatusForbidden)
			return
		}
	}

	event, err := NewEvent(buffer.Bytes(), githubEventType)
	if err != nil {
		glog.Errorf("Error parinsg JSON for %s: %s", r.URL.Path, err)
	}
	event["urlparams"] = params
	go hook.Execute(event)
}

func handlerWrapper(handler HookHandler, hook *Hook) httptreemux.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request, params map[string]string) {
		glog.Infoln("Called", r.URL.Path)
		handler(w, r, params, hook)
	}
}

func SetupServer(config *Config) (net.Listener, http.Handler) {
	var listener net.Listener = nil

	listener, err := net.Listen("tcp", config.ListenAddress)
	if err != nil {
		glog.Fatal("Could not listen on", config.ListenAddress)
	}

	if len(config.AcceptIps) != 0 {
		listenFilter := NewListenFilter(listener, WhiteList)
		for _, a := range config.AcceptIps {
			listenFilter.FilterAddr[a] = true
		}
		listener = listenFilter
	}

	router := httptreemux.New()

	for _, hook := range config.Hook {
		router.POST(hook.Url, handlerWrapper(hookHandler, hook))
	}

	return listener, router
}

func RunServer(config *Config) {
	listener, router := SetupServer(config)
	glog.Fatal(http.Serve(listener, router))
}
<commit_msg>Update ListenFilter client code<commit_after>package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"github.com/dimfeld/glog"
	"github.com/dimfeld/httptreemux"
	"net"
	"net/http"
	"strings"
)

type HookHandler func(http.ResponseWriter, *http.Request, map[string]string, *Hook)

func hookHandler(w http.ResponseWriter, r *http.Request, params map[string]string, hook *Hook) {
	githubEventType := r.Header.Get("X-GitHub-Event")

	if r.ContentLength > 16384 {
		// We should never get a request this large.
		w.WriteHeader(http.StatusRequestEntityTooLarge)
		return
	}

	buffer := bytes.Buffer{}
	buffer.ReadFrom(r.Body)
	r.Body.Close()

	if glog.V(2) {
		niceBuffer := &bytes.Buffer{}
		json.Indent(niceBuffer, buffer.Bytes(), "", "  ")
		glog.Infof("Hook %s received data %s\n",
			r.URL.Path, string(niceBuffer.Bytes()))
	}

	if hook.Secret != "" {
		secret := r.Header.Get("X-Hub-Signature")
		if !strings.HasPrefix(secret, "sha1=") {
			glog.Warningf("Request with no secret for hook %s from %s\n",
				r.URL.Path, r.RemoteAddr)
			w.WriteHeader(http.StatusForbidden)
			return
		}

		hash := hmac.New(sha1.New, []byte(hook.Secret))
		hash.Write(buffer.Bytes())
		expected := hash.Sum(nil)
		seen, err := hex.DecodeString(secret[5:])
		if err != nil || !hmac.Equal(expected, seen) {
			glog.Warningf("Request with bad secret for hook %s from %s\nExpected %s, saw %s",
				r.URL.Path, r.RemoteAddr, hex.EncodeToString(expected), secret)
			w.WriteHeader(http.StatusForbidden)
			return
		}
	}

	event, err := NewEvent(buffer.Bytes(), githubEventType)
	if err != nil {
		glog.Errorf("Error parinsg JSON for %s: %s", r.URL.Path, err)
	}
	event["urlparams"] = params
	go hook.Execute(event)
}

func handlerWrapper(handler HookHandler, hook *Hook) httptreemux.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request, params map[string]string) {
		glog.Infoln("Called", r.URL.Path)
		handler(w, r, params, hook)
	}
}

func SetupServer(config *Config) (net.Listener, http.Handler) {
	var listener net.Listener = nil

	listener, err := net.Listen("tcp", config.ListenAddress)
	if err != nil {
		glog.Fatal("Could not listen on", config.ListenAddress)
	}

	if len(config.AcceptIps) != 0 {
		listenFilter := NewListenFilter(listener, WhiteList)
		for _, a := range config.AcceptIps {
			listenFilter.AddString(a)
		}
		listener = listenFilter
	}

	router := httptreemux.New()

	for _, hook := range config.Hook {
		router.POST(hook.Url, handlerWrapper(hookHandler, hook))
	}

	return listener, router
}

func RunServer(config *Config) {
	listener, router := SetupServer(config)
	glog.Fatal(http.Serve(listener, router))
}
<|endoftext|>


<commit_before>package gracehttp

import (
	"crypto/tls"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

const (
	GRACEFUL_ENVIRON_KEY    = "IS_GRACEFUL"
	GRACEFUL_ENVIRON_STRING = GRACEFUL_ENVIRON_KEY + "=1"

	DEFAULT_READ_TIMEOUT  = 30 * time.Second
	DEFAULT_WRITE_TIMEOUT = DEFAULT_READ_TIMEOUT
)

// refer http.ListenAndServe
func ListenAndServe(addr string, handler http.Handler) error {
	return NewServer(addr, handler, DEFAULT_READ_TIMEOUT, DEFAULT_WRITE_TIMEOUT).ListenAndServe()
}

// refer http.ListenAndServeTLS
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error {
	return NewServer(addr, handler, DEFAULT_READ_TIMEOUT, DEFAULT_WRITE_TIMEOUT).ListenAndServeTLS(certFile, keyFile)
}

// new server
func NewServer(addr string, handler http.Handler, readTimeout, writeTimeout time.Duration) *Server {

	// 获取环境变量
	isGraceful := false
	if os.Getenv(GRACEFUL_ENVIRON_KEY) != "" {
		isGraceful = true
	}

	// 实例化Server
	return &Server{
		httpServer: &http.Server{
			Addr:    addr,
			Handler: handler,

			ReadTimeout:  readTimeout,
			WriteTimeout: writeTimeout,
		},

		isGraceful: isGraceful,
		signalChan: make(chan os.Signal),
	}
}

// 支持优雅重启的http服务
type Server struct {
	httpServer *http.Server
	listener   net.Listener

	isGraceful bool
	signalChan chan os.Signal
}

func (this *Server) ListenAndServe() error {
	addr := this.httpServer.Addr
	if addr == "" {
		addr = ":http"
	}

	ln, err := this.getNetTCPListener(addr)
	if err != nil {
		return err
	}

	this.listener = newListener(ln)

	return this.Serve()
}

func (this *Server) ListenAndServeTLS(certFile, keyFile string) error {
	addr := this.httpServer.Addr
	if addr == "" {
		addr = ":https"
	}

	config := &tls.Config{}
	if this.httpServer.TLSConfig != nil {
		*config = *this.httpServer.TLSConfig
	}
	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}

	var err error
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}

	ln, err := this.getNetTCPListener(addr)
	if err != nil {
		return err
	}

	this.listener = tls.NewListener(newListener(ln), config)
	return this.Serve()
}

func (this *Server) Serve() error {

	// 处理信号
	go this.handleSignals()

	// 处理HTTP请求
	err := this.httpServer.Serve(this.listener)

	// 跳出Serve处理代表 listener 已经close，等待所有已有的连接处理结束
	this.logf("listener closed, waiting for connection close...")
	this.listener.(*Listener).Wait()

	return err
}

func (this *Server) getNetTCPListener(addr string) (*net.TCPListener, error) {

	var ln net.Listener
	var err error

	if this.isGraceful {
		file := os.NewFile(3, "")
		ln, err = net.FileListener(file)
		if err != nil {
			err = fmt.Errorf("net.FileListener error: %v", err)
			return nil, err
		}
	} else {
		ln, err = net.Listen("tcp", addr)
		if err != nil {
			err = fmt.Errorf("net.Listen error: %v", err)
			return nil, err
		}
	}
	return ln.(*net.TCPListener), nil
}

func (this *Server) handleSignals() {
	var sig os.Signal

	signal.Notify(
		this.signalChan,
		syscall.SIGHUP,
		syscall.SIGTERM,
	)

	pid := os.Getpid()
	for {
		sig = <-this.signalChan

		switch sig {

		case syscall.SIGTERM:

			this.logf("pid %d received SIGTERM.", pid)
			this.logf("graceful shutting down http server...")
			this.shutdown()

		case syscall.SIGHUP:

			this.logf("pid %d received SIGHUP.", pid)
			this.logf("graceful restart http server...")

			err := this.fork()
			if err != nil {
				this.logf("fork error: %v.", err)
			} else {
				this.shutdown()
			}

		default:

		}
	}
}

// 优雅关闭
func (this *Server) shutdown() {

	// 通过设置超时使得进程不再接受新请求
	this.listener.(*Listener).SetDeadline(time.Now())

	// 关闭链接
	this.listener.(*Listener).Close()
}

// 启动子进程执行新程序
func (this *Server) fork() error {

	listenerFd, err := this.listener.(*Listener).GetFd()
	if err != nil {
		return fmt.Errorf("failed to get socket file descriptor: %v.", err)
	}

	path := os.Args[0]

	// 设置标识优雅重启的环境变量
	environList := []string{}
	for _, value := range os.Environ() {
		if value != GRACEFUL_ENVIRON_STRING {
			environList = append(environList, value)
		}
	}
	environList = append(environList, GRACEFUL_ENVIRON_STRING)

	execSpec := &syscall.ProcAttr{
		Env:   environList,
		Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listenerFd},
	}

	fork, err := syscall.ForkExec(path, os.Args, execSpec)
	if err != nil {
		return fmt.Errorf("failed to forkexec: %v.", err)
	}

	this.logf("fork exec to pid %d.", fork)

	return nil
}

func (this *Server) logf(format string, args ...interface{}) {

	if this.httpServer.ErrorLog != nil {
		this.httpServer.ErrorLog.Printf(format, args...)
	} else {
		log.Printf(format, args...)
	}
}
<commit_msg>optimized code<commit_after>package gracehttp

import (
	"crypto/tls"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

const (
	GRACEFUL_ENVIRON_KEY    = "IS_GRACEFUL"
	GRACEFUL_ENVIRON_STRING = GRACEFUL_ENVIRON_KEY + "=1"

	DEFAULT_READ_TIMEOUT  = 30 * time.Second
	DEFAULT_WRITE_TIMEOUT = DEFAULT_READ_TIMEOUT
)

// refer http.ListenAndServe
func ListenAndServe(addr string, handler http.Handler) error {
	return NewServer(addr, handler, DEFAULT_READ_TIMEOUT, DEFAULT_WRITE_TIMEOUT).ListenAndServe()
}

// refer http.ListenAndServeTLS
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error {
	return NewServer(addr, handler, DEFAULT_READ_TIMEOUT, DEFAULT_WRITE_TIMEOUT).ListenAndServeTLS(certFile, keyFile)
}

// new server
func NewServer(addr string, handler http.Handler, readTimeout, writeTimeout time.Duration) *Server {

	// 获取环境变量
	isGraceful := false
	if os.Getenv(GRACEFUL_ENVIRON_KEY) != "" {
		isGraceful = true
	}

	// 实例化Server
	return &Server{
		httpServer: &http.Server{
			Addr:    addr,
			Handler: handler,

			ReadTimeout:  readTimeout,
			WriteTimeout: writeTimeout,
		},

		isGraceful: isGraceful,
		signalChan: make(chan os.Signal),
	}
}

// 支持优雅重启的http服务
type Server struct {
	httpServer *http.Server
	listener   net.Listener

	isGraceful bool
	signalChan chan os.Signal
}

func (this *Server) ListenAndServe() error {
	addr := this.httpServer.Addr
	if addr == "" {
		addr = ":http"
	}

	ln, err := this.getNetTCPListener(addr)
	if err != nil {
		return err
	}

	this.listener = newListener(ln)

	return this.Serve()
}

func (this *Server) ListenAndServeTLS(certFile, keyFile string) error {
	addr := this.httpServer.Addr
	if addr == "" {
		addr = ":https"
	}

	config := &tls.Config{}
	if this.httpServer.TLSConfig != nil {
		*config = *this.httpServer.TLSConfig
	}
	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}

	var err error
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}

	ln, err := this.getNetTCPListener(addr)
	if err != nil {
		return err
	}

	this.listener = tls.NewListener(newListener(ln), config)
	return this.Serve()
}

func (this *Server) Serve() error {

	// 处理信号
	go this.handleSignals()

	// 处理HTTP请求
	err := this.httpServer.Serve(this.listener)

	// 跳出Serve处理代表 listener 已经close，等待所有已有的连接处理结束
	this.logf("waiting for connection close...")
	this.listener.(*Listener).Wait()
	this.logf("all connection closed, process with pid %d shutting down...", os.Getpid())

	return err
}

func (this *Server) getNetTCPListener(addr string) (*net.TCPListener, error) {

	var ln net.Listener
	var err error

	if this.isGraceful {
		file := os.NewFile(3, "")
		ln, err = net.FileListener(file)
		if err != nil {
			err = fmt.Errorf("net.FileListener error: %v", err)
			return nil, err
		}
	} else {
		ln, err = net.Listen("tcp", addr)
		if err != nil {
			err = fmt.Errorf("net.Listen error: %v", err)
			return nil, err
		}
	}
	return ln.(*net.TCPListener), nil
}

func (this *Server) handleSignals() {
	var sig os.Signal

	signal.Notify(
		this.signalChan,
		syscall.SIGHUP,
		syscall.SIGTERM,
	)

	pid := os.Getpid()
	for {
		sig = <-this.signalChan

		switch sig {

		case syscall.SIGTERM:

			this.logf("pid %d received SIGTERM.", pid)
			this.logf("graceful shutting down http server...")

			// 关闭老进程的连接
			this.listener.(*Listener).Close()
			this.logf("listener of pid %d closed.", pid)

		case syscall.SIGHUP:

			this.logf("pid %d received SIGHUP.", pid)
			this.logf("graceful restart http server...")

			err := this.startNewProcess()
			if err != nil {
				this.logf("start new process failed: %v, pid %d continue serve.", err)
			} else {
				// 关闭老进程的连接
				this.listener.(*Listener).Close()
				this.logf("listener of pid %d closed.", pid)
			}

		default:

		}
	}
}

// 启动子进程执行新程序
func (this *Server) startNewProcess() error {

	listenerFd, err := this.listener.(*Listener).GetFd()
	if err != nil {
		return fmt.Errorf("failed to get socket file descriptor: %v", err)
	}

	path := os.Args[0]

	// 设置标识优雅重启的环境变量
	environList := []string{}
	for _, value := range os.Environ() {
		if value != GRACEFUL_ENVIRON_STRING {
			environList = append(environList, value)
		}
	}
	environList = append(environList, GRACEFUL_ENVIRON_STRING)

	execSpec := &syscall.ProcAttr{
		Env:   environList,
		Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listenerFd},
	}

	fork, err := syscall.ForkExec(path, os.Args, execSpec)
	if err != nil {
		return fmt.Errorf("failed to forkexec: %v", err)
	}

	this.logf("start new process success, pid %d.", fork)

	return nil
}

func (this *Server) logf(format string, args ...interface{}) {

	if this.httpServer.ErrorLog != nil {
		this.httpServer.ErrorLog.Printf(format, args...)
	} else {
		log.Printf(format, args...)
	}
}
<|endoftext|>


<commit_before>package main

import (
        "code.google.com/p/go.net/websocket"
        "net/http"
        "log"
	"encoding/json"
	"strings"
	"./uuid"
)

const (
	HOST_NAME = "localhost"
	PORT_NUMBER = "8080"
	APPSERVER_API_PREFIX = "/notify/"
)

type Client struct {
	Websocket *websocket.Conn
	UAID string
}

type Channel struct {
	ChannelID string `json:"channelID"`
	Version string   `json:"version"`
}

var uaid_to_channel map[string][]Channel;
var channel_to_client map[string] *Client;

func handleRegister(client *Client, f map[string]interface{}) {

	log.Println(" -> handleRegister");

	if (f["channelID"] == nil) {
		log.Println("channelID is missing!");
		return;
	}

	var channelID = f["channelID"].(string);
	var pushEndpoint = "http://" + HOST_NAME + ":" + PORT_NUMBER + APPSERVER_API_PREFIX + channelID;

	uaid_to_channel[client.UAID] = append(uaid_to_channel[client.UAID], Channel{channelID, ""});
	channel_to_client[channelID] = client;

	type RegisterResponse struct {
		Name string          `json:"messageType"`
		Status int           `json:"status"`
		PushEndpoint string  `json:"pushEndpoint"`
		ChannelID string      `json:"channelID"`
	}

	register := RegisterResponse{"register", 200, pushEndpoint, channelID}

	j, err := json.Marshal(register);
	if err != nil || string(j) == "null" {
		log.Println("Could not convert hello response to json %s",err)
		return;
        }

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		// we could not send the message to a peer
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleHello(client *Client, f map[string]interface{}) {
	log.Println(" -> handleHello");

	status := 200;

	if (f["uaid"] == nil) {
		uaid, err := uuid.GenUUID()
		if  err != nil {
			status = 400;
			log.Println("GenUUID error %s",err)
		}
		client.UAID = uaid;
	} else {
		client.UAID = f["uaid"].(string)
	}

	type HelloResponse struct {
		Name string          `json:"messageType"`
		Status int           `json:"status"`
		UAID string          `json:"uaid"`
		Channels []Channel   `json:"channelIDs"`
	}

	hello := HelloResponse{"hello", status, client.UAID, uaid_to_channel[client.UAID]}

	j, err := json.Marshal(hello);
	if err != nil || string(j) == "null" {
		log.Println("Could not convert hello response to json %s",err)
		return;
        }

	// update the channel_to_client table
//	for channel := range uaid_to_channel[client.UAID] {
//		channel_to_client[channel.ChannelID] = client;
//	}

	log.Println("going to send:  \n  ", string(j));
	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleAck(client *Client, f map[string]interface{}) {
	log.Println(" -> ack");
}

func pushHandler(ws *websocket.Conn) {

	client := Client{ws, ""}

	for {
		var f map[string]interface{}

		var err error
		if err = websocket.JSON.Receive(ws, &f); err != nil {
			log.Println("Websocket Disconnected.", err.Error())
			ws.Close();
			return;
		}

		switch f["messageType"] {
		case "hello":
			handleHello(&client, f);
			break;
		case "register":
			handleRegister(&client, f);
			break;

		case "ack":
			handleAck(&client, f);
			break;
		default:
			log.Println(" -> Unknown", f);
			break;
		}
	}
}

func notifyHandler(w http.ResponseWriter, r *http.Request) {

	if r.Method != "PUT" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Method must be PUT."))
		return
	}

	channelID := strings.Trim(r.URL.Path, APPSERVER_API_PREFIX)

	if (strings.Contains(channelID, "/") || len(channelID) != (32+4)) {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find a valid channelID."))
		return;
	}

	if err := r.ParseForm(); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find a valid version."))
		return;
	}

	values := r.Form["version"]

	if (len(values) != 1) {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find one version."))
		return;
	}

	version := values[0];
	client := channel_to_client[channelID];
	if (client == nil) {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Bad Channel ID."))
		return;
	}

	uaid := client.UAID;
	log.Println("uaid: ", uaid, "channelID: ", channelID, " version: ", version);

	// update the channel with the new version.
	for i := range uaid_to_channel[uaid] {
		if (uaid_to_channel[uaid][i].ChannelID == channelID) {
			uaid_to_channel[uaid][i].Version = version;
		}
	}

	type NotificationResponse struct {
		Name string          `json:"messageType"`
		Channels []Channel   `json:"updates"`
	}

	notification := NotificationResponse{"notification",  uaid_to_channel[client.UAID]}

	j, err := json.Marshal(notification);
	if err != nil || string(j) == "null" {
		log.Println("Could not convert hello response to json %s",err)
		return;
        }

	log.Println("going to send:  \n  ", string(j));
	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}

}

func main() {

	uaid_to_channel = make(map[string][]Channel)
	channel_to_client = make(map[string] *Client);

	http.Handle("/", http.FileServer(http.Dir(".")))

	http.Handle("/push", websocket.Handler(pushHandler))
	http.HandleFunc(APPSERVER_API_PREFIX, notifyHandler);

	log.Println("Listening on ", HOST_NAME + ":" + PORT_NUMBER );
	log.Fatal(http.ListenAndServe(HOST_NAME  + ":" + PORT_NUMBER , nil))
}

<commit_msg>Only post notifications to the client for the channel that was updated<commit_after>package main

import (
        "code.google.com/p/go.net/websocket"
        "net/http"
        "log"
	"encoding/json"
	"strings"
	"./uuid"
)

const (
	HOST_NAME = "localhost"
	PORT_NUMBER = "8080"
	APPSERVER_API_PREFIX = "/notify/"
)

type Client struct {
	Websocket *websocket.Conn
	UAID string
}

type Channel struct {
	ChannelID string `json:"channelID"`
	Version string   `json:"version"`
}

var uaid_to_channel map[string][]Channel;

var channel_to_client map[string] *Client;

func handleRegister(client *Client, f map[string]interface{}) {
	log.Println(" -> handleRegister");

	if (f["channelID"] == nil) {
		log.Println("channelID is missing!");
		return;
	}

	var channelID = f["channelID"].(string);
	var pushEndpoint = "http://" + HOST_NAME + ":" + PORT_NUMBER + APPSERVER_API_PREFIX + channelID;

	uaid_to_channel[client.UAID] = append(uaid_to_channel[client.UAID], Channel{channelID, ""});
	channel_to_client[channelID] = client;

	type RegisterResponse struct {
		Name string          `json:"messageType"`
		Status int           `json:"status"`
		PushEndpoint string  `json:"pushEndpoint"`
		ChannelID string     `json:"channelID"`
	}

	register := RegisterResponse{"register", 200, pushEndpoint, channelID}

	j, err := json.Marshal(register);
	if err != nil {
		log.Println("Could not convert hello response to json %s",err)
		return;
        }

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		// we could not send the message to a peer
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleHello(client *Client, f map[string]interface{}) {
	log.Println(" -> handleHello");

	status := 200;

	if (f["uaid"] == nil) {
		uaid, err := uuid.GenUUID()
		if  err != nil {
			status = 400;
			log.Println("GenUUID error %s",err)
		}
		client.UAID = uaid;
	} else {
		client.UAID = f["uaid"].(string)
	}

	type HelloResponse struct {
		Name string          `json:"messageType"`
		Status int           `json:"status"`
		UAID string          `json:"uaid"`
		Channels []Channel   `json:"channelIDs"`
	}

	hello := HelloResponse{"hello", status, client.UAID, uaid_to_channel[client.UAID]}

	j, err := json.Marshal(hello);
	if err != nil {
		log.Println("Could not convert hello response to json %s",err)
		return;
        }

	// update the channel_to_client table
	for i := range uaid_to_channel[client.UAID] {
		channel_to_client[uaid_to_channel[client.UAID][i].ChannelID] = client;
	}

	log.Println("going to send:  \n  ", string(j));
	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleAck(client *Client, f map[string]interface{}) {
	log.Println(" -> ack");
}

func pushHandler(ws *websocket.Conn) {

	client := Client{ws, ""}

	for {
		var f map[string]interface{}

		var err error
		if err = websocket.JSON.Receive(ws, &f); err != nil {
			log.Println("Websocket Disconnected.", err.Error())
			ws.Close();
			return;
		}

		switch f["messageType"] {
		case "hello":
			handleHello(&client, f);
			break;
		case "register":
			handleRegister(&client, f);
			break;

		case "ack":
			handleAck(&client, f);
			break;
		default:
			log.Println(" -> Unknown", f);
			break;
		}
	}
}

func notifyHandler(w http.ResponseWriter, r *http.Request) {

	if r.Method != "PUT" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Method must be PUT."))
		return
	}

	channelID := strings.Trim(r.URL.Path, APPSERVER_API_PREFIX)

	if (strings.Contains(channelID, "/") || len(channelID) != (32+4)) {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find a valid channelID."))
		return;
	}

	if err := r.ParseForm(); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find a valid version."))
		return;
	}

	values := r.Form["version"]

	if (len(values) != 1) {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find one version."))
		return;
	}

	version := values[0];
	client := channel_to_client[channelID];
	if (client == nil) {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Bad Channel ID."))
		return;
	}

	uaid := client.UAID;
	log.Println("uaid: ", uaid, "channelID: ", channelID, " version: ", version);

	// update the channel with the new version.
	for i := range uaid_to_channel[uaid] {
		if (uaid_to_channel[uaid][i].ChannelID == channelID) {
			uaid_to_channel[uaid][i].Version = version;

			sendNotificationToClient(client, uaid_to_channel[uaid][i])
			break;
		}
	}
}

func sendNotificationToClient(client *Client, channel Channel)  {

	type NotificationResponse struct {
		Name string          `json:"messageType"`
		Channels []Channel   `json:"updates"`
	}

	var channels []Channel;
	channels = append(channels, channel);

	notification := NotificationResponse{"notification",  channels}

	j, err := json.Marshal(notification);
	if err != nil {
		log.Println("Could not convert hello response to json %s",err)
		return;
        }

	log.Println("going to send:  \n  ", string(j));
	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}

}

func main() {

	uaid_to_channel = make(map[string][]Channel)
	channel_to_client = make(map[string] *Client);

	http.Handle("/", http.FileServer(http.Dir(".")))

	http.Handle("/push", websocket.Handler(pushHandler))
	http.HandleFunc(APPSERVER_API_PREFIX, notifyHandler);

	log.Println("Listening on ", HOST_NAME + ":" + PORT_NUMBER );
	log.Fatal(http.ListenAndServe(HOST_NAME  + ":" + PORT_NUMBER , nil))
}

<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package web

import (
	"net/http"

	"github.com/issue9/logs/v2"

	"github.com/issue9/web/server"
)

type (
	Server      = server.Server
	Context     = server.Context
	Options     = server.Options
	Filter      = server.Filter
	Module      = server.Module
	Tag         = server.Tag
	HandlerFunc = server.HandlerFunc
)

// New 返回 *Server 实例
func NewServer(name, version string, logs *logs.Logs, o *Options) (*Server, error) {
	return server.New(name, version, logs, o)
}

// GetServer 从请求中获取 *Server 实例
func GetServer(r *http.Request) *Server {
	return server.GetServer(r)
}

// NewContext 构建 *Context 实例
func NewContext(w http.ResponseWriter, r *http.Request) *Context {
	return server.NewContext(w, r)
}

// NewModule 声明一个新的模块
func NewModule(id, desc string, deps ...string) *Module {
	return server.NewModule(id, desc, deps...)
}
<commit_msg>feat: 添加 DefaultServer 用于返回一个全部采用默认值初始化的 Server 实例<commit_after>// SPDX-License-Identifier: MIT

package web

import (
	"net/http"

	"github.com/issue9/logs/v2"

	"github.com/issue9/web/server"
)

type (
	Server      = server.Server
	Context     = server.Context
	Options     = server.Options
	Filter      = server.Filter
	Module      = server.Module
	Tag         = server.Tag
	HandlerFunc = server.HandlerFunc
)

// DefaultServer 返回一个采用默认值进初始化的 *Server 实例
func DefaultServer(name, version, url string) (*Server, error) {
	return NewServer(name, version, logs.New(), &Options{Root: url})
}

// New 返回 *Server 实例
func NewServer(name, version string, logs *logs.Logs, o *Options) (*Server, error) {
	return server.New(name, version, logs, o)
}

// GetServer 从请求中获取 *Server 实例
func GetServer(r *http.Request) *Server {
	return server.GetServer(r)
}

// NewContext 构建 *Context 实例
func NewContext(w http.ResponseWriter, r *http.Request) *Context {
	return server.NewContext(w, r)
}

// NewModule 声明一个新的模块
func NewModule(id, desc string, deps ...string) *Module {
	return server.NewModule(id, desc, deps...)
}
<|endoftext|>


<commit_before>package main
import (
	"os/user"
	"strconv"
	"os"
	"net"
	"time"
	"fmt"
	"bufio"
	"io/ioutil"
	"errors"
	"strings"
	"os/exec"
	"syscall"
	"path/filepath"
)

type Server struct {
	Name     string `json:"name"`
	Ip       string `json:"ip"`
	Port     int `json:"port"`
	Mod      string `json:"mod"`
	BasePath string `json:"basePath"`
	HomePath string `json:"homePath"`
	Configs  []string `json:"configs"`
	User     string `json:"user"`
	Running  bool `json:"running"`
	Pid      int `json:"pid"`
	Env 	 []string `json:env`
}

// GetStatus
// Returns the status response of the server in a parsed format
func (s *Server) GetStatus() (*StatusResponse, error) {
	conn, err := net.DialTimeout("udp", fmt.Sprintf("%s:%d", s.Ip, s.Port), 1000 * time.Millisecond)
	if err != nil {
		return &StatusResponse{}, err
	}
	conn.SetReadDeadline(time.Now().Add(1 * time.Second))
	defer conn.Close()

	if err != nil {
		return &StatusResponse{}, err
	}

	fmt.Fprintf(conn, "\xff\xff\xff\xffgetstatus")
	p := make([]byte, 1024)
	_, err = bufio.NewReader(conn).Read(p)

	if err != nil {
		return &StatusResponse{}, err
	}

	sr := ParseStatusResponse(string(p))
	return sr, nil
}

// PrintStatus
// Prints the server status
func (s *Server) PrintStatus(verbose bool) {
	if !s.Running {
		fmt.Printf("Server \"%s\" is not running.\n", s.Name)
		return
	}

	if (verbose) {
		status, err := s.GetStatus()
		if err != nil {
			fmt.Printf("Could not get server status: %s\n", err)
			return
		}

		fmt.Println("----------------------------------------")
		fmt.Printf("Server:\t\t%s\n", s.Name)
		fmt.Println("----------------------------------------")
		fmt.Printf("Address:\t%s:%d\n", s.Ip, s.Port)
		fmt.Println("----------------------------------------")
		fmt.Printf("Hostname:\t%s\n", StripColors(status.Keys["sv_hostname"]))
		fmt.Printf("Players:\t%d/%s\n", len(status.Players), status.Keys["sv_maxclients"])
		fmt.Printf("Map:\t\t%s\n", status.Keys["mapname"])
		fmt.Println("----------------------------------------")
		if len(status.Players) > 0 {
			fmt.Printf("Players:\t%s\n", status.Players[0])
			for _, player := range status.Players[1:] {
				fmt.Printf("\t\t%s\n", StripColors(player))
			}
			fmt.Println("----------------------------------------")
		}

	} else {
		fmt.Println("----------------------------------------")
		fmt.Printf("Server:\t\t%s\n", s.Name)
		fmt.Println("----------------------------------------")
		fmt.Printf("Address:\t%s:%d\n", s.Ip, s.Port)
		fmt.Printf("Running:\t%t\n", s.Running)
		fmt.Println("----------------------------------------")
	}
}

// Start
// Checks if the server is running and starts the server process if not
// Informs the user of the current server status
func (s *Server) Start() (error) {
	contains, err := s.basePathContainsNecessaryFiles()
	if !contains {
		return errors.New(fmt.Sprintf("Server \"%s\" could not be started: %s\n", s.Name, err))
	}

	if !s.homePathExistsInFS() {
		return errors.New(fmt.Sprintf("Server \"%s\" has an invalid homepath\n", s.Name))
	}

	if s.Running {
		if !s.CheckServer() {
			fmt.Printf("Server \"%s\" should be running, but it is not. Restarting server.\n", s.Name)
			_, err := s.StartServerProcess()
			return err
		} else {
			fmt.Printf("Server \"%s\" is already running. (Process ID: %d)\n", s.Name, s.Pid)
			fmt.Printf("Run \"screen -drS %s\" to open server console.\n", s.Name + strconv.Itoa(s.Port))
			return nil
		}
	}

	serverProcess, err := s.StartServerProcess()
	if err != nil {
		fmt.Printf("Could not start server process: %s\n", err)
		return err
	}

	err = SaveConfig(configFile, globalConfiguration)
	if err != nil {
		fmt.Printf("Error: could not save config \"%s\": %s\n", err)
	}

	fmt.Printf("Started server \"%s\" with process ID: %d\n", s.Name, serverProcess.Process.Pid + 1)
	fmt.Printf("%v\n", serverProcess)
	return nil
}

// StartServerProcess
// Starts the server process if it's not already running
func (s *Server) StartServerProcess() (*exec.Cmd, error) {
	user, err := user.Lookup(s.User)
	if err != nil {
		return &exec.Cmd{}, err
	}

	parameters := []string{
		"-dmS",
		s.Name + strconv.Itoa(s.Port),
		globalConfiguration.ExecutablePath,
		fmt.Sprintf("+set fs_game \"%s\"", s.Mod),
	}

	parameters = append(parameters, fmt.Sprintf("+set com_hunkmegs \"128\""))
	parameters = append(parameters, fmt.Sprintf("+set fs_basepath \"%s\"", s.BasePath))
	parameters = append(parameters, fmt.Sprintf("+set fs_homepath \"%s\"", s.HomePath))
	parameters = append(parameters, fmt.Sprintf("+set net_ip \"%s\"", s.Ip))
	parameters = append(parameters, fmt.Sprintf("+set net_port \"%d\"", s.Port))
	parameters = append(parameters, fmt.Sprintf("+map oasis"))

	for _, config := range s.Configs {
		parameters = append(parameters, fmt.Sprintf("+exec %s", config))
	}

	serverProcess := exec.Command(globalConfiguration.ScreenPath, parameters...)
	serverProcess.Dir = filepath.Dir(globalConfiguration.ExecutablePath)
	// Needed for ET Process, saves .etwolf folder here
	serverProcess.Env = []string{fmt.Sprintf("HOME=%s", user.HomeDir)}
	for _, envVar := range s.Env {
		serverProcess.Env = append(serverProcess.Env, envVar)
	}

	uid, err := strconv.Atoi(user.Uid)
	if err != nil {
		return serverProcess, err
	}
	gid, err := strconv.Atoi(user.Gid)
	if err != nil {
		return serverProcess, err
	}

	serverProcess.SysProcAttr = &syscall.SysProcAttr{}
	serverProcess.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
	err = serverProcess.Run()
	if err != nil {
		return serverProcess, err
	}
	s.Running = true
	s.Pid = serverProcess.Process.Pid + 1
	return serverProcess, nil

}

// Stop
// Stops the server if it's running
func (s *Server) Stop() (err error) {
	err = nil
	if s.Running {
		fmt.Printf("Stopping server \"%s\"\n", s.Name)

		proc := exec.Command(globalConfiguration.KillPath, strconv.Itoa(s.Pid))
		err := proc.Run()
		fmt.Printf("Stopping server with pid: %d\n", s.Pid)
		if err != nil {
			fmt.Printf("Could not stop server: %s\n", err)
			return err
		}
		s.Running = false
		err = SaveConfig(configFile, globalConfiguration)
		fmt.Printf("Stopped server \"%s\"\n", s.Name)
	} else {
		fmt.Printf("Server \"%s\" is not running.\n", s.Name)
	}
	return err
}

// Restart
// Restarts the server
func (s *Server) Restart() {
	s.Stop()
	fmt.Printf("Waiting 2 seconds")

	fmt.Printf(".")
	time.Sleep(700 * time.Millisecond)
	fmt.Printf(".")
	time.Sleep(700 * time.Millisecond)
	fmt.Printf(".\n")
	time.Sleep(600 * time.Millisecond)

	s.Start()
}

// CheckServer
// Checks that the server is still running
func (s *Server) CheckServer() (bool) {
	if s.Running {
		pgrep := exec.Command(globalConfiguration.PgrepPath, "-f", s.Name + strconv.Itoa(s.Port))

		_, err := pgrep.Output()
		if err != nil {
			return false
		}
	}
	return true
}

// SecurityCheck
// Checks that the calling user is allowed to execute the script
func (s *Server) SecurityCheck() (bool, error) {
	owner, err := user.Lookup(s.User)
	if err != nil {
		return false, err
	}

	serverOwnerUid, err := strconv.Atoi(owner.Uid)
	if err != nil {
		return false, err
	}

	currentUid := os.Geteuid()
	if currentUid == serverOwnerUid || currentUid == 0 {
		return true, nil
	}
	return false, nil
}

//
// Checks if the basepath contains necessary files to host an ET server
// pak0.pk3, pak1.pk3, pak2.pk3 and mp_bin.pk3
func (s *Server) basePathContainsNecessaryFiles() (bool, error) {
	fileInfos, err := ioutil.ReadDir(filepath.Join(s.BasePath, "etmain"))
	if err != nil {
		return false, errors.New(fmt.Sprintf("Basepath check failed: %s", err))
	}

	requiredFiles := map[string]bool{
		"pak0.pk3": false,
		"pak1.pk3": false,
		"pak2.pk3": false,
		"mp_bin.pk3": false,
	}

	// Check that required files can be found at the basepath etmain
	for _, fi := range fileInfos {
		if _, ok := requiredFiles[fi.Name()]; ok {
			requiredFiles[fi.Name()] = true
		}
	}

	missingFiles := []string{}
	for file, exists := range requiredFiles {
		if !exists {
			missingFiles = append(missingFiles, file)
		}
	}

	if len(missingFiles) > 0 {
		return false, errors.New(fmt.Sprintf("Missing the following files: %s", strings.Join(missingFiles, ", ")))
	}
	return true, nil
}

//
// Checks if server's homepath actually exists in file system
func (s *Server) homePathExistsInFS() (bool) {
	fileInfo, err := os.Stat(s.HomePath)

	if err != nil {
		return false
	}

	return fileInfo.IsDir()
}<commit_msg>Changed the way env vars are set<commit_after>package main
import (
	"os/user"
	"strconv"
	"os"
	"net"
	"time"
	"fmt"
	"bufio"
	"io/ioutil"
	"errors"
	"strings"
	"os/exec"
	"syscall"
	"path/filepath"
)

type Server struct {
	Name     string `json:"name"`
	Ip       string `json:"ip"`
	Port     int `json:"port"`
	Mod      string `json:"mod"`
	BasePath string `json:"basePath"`
	HomePath string `json:"homePath"`
	Configs  []string `json:"configs"`
	User     string `json:"user"`
	Running  bool `json:"running"`
	Pid      int `json:"pid"`
	Env 	 []string `json:env`
}

// GetStatus
// Returns the status response of the server in a parsed format
func (s *Server) GetStatus() (*StatusResponse, error) {
	conn, err := net.DialTimeout("udp", fmt.Sprintf("%s:%d", s.Ip, s.Port), 1000 * time.Millisecond)
	if err != nil {
		return &StatusResponse{}, err
	}
	conn.SetReadDeadline(time.Now().Add(1 * time.Second))
	defer conn.Close()

	if err != nil {
		return &StatusResponse{}, err
	}

	fmt.Fprintf(conn, "\xff\xff\xff\xffgetstatus")
	p := make([]byte, 1024)
	_, err = bufio.NewReader(conn).Read(p)

	if err != nil {
		return &StatusResponse{}, err
	}

	sr := ParseStatusResponse(string(p))
	return sr, nil
}

// PrintStatus
// Prints the server status
func (s *Server) PrintStatus(verbose bool) {
	if !s.Running {
		fmt.Printf("Server \"%s\" is not running.\n", s.Name)
		return
	}

	if (verbose) {
		status, err := s.GetStatus()
		if err != nil {
			fmt.Printf("Could not get server status: %s\n", err)
			return
		}

		fmt.Println("----------------------------------------")
		fmt.Printf("Server:\t\t%s\n", s.Name)
		fmt.Println("----------------------------------------")
		fmt.Printf("Address:\t%s:%d\n", s.Ip, s.Port)
		fmt.Println("----------------------------------------")
		fmt.Printf("Hostname:\t%s\n", StripColors(status.Keys["sv_hostname"]))
		fmt.Printf("Players:\t%d/%s\n", len(status.Players), status.Keys["sv_maxclients"])
		fmt.Printf("Map:\t\t%s\n", status.Keys["mapname"])
		fmt.Println("----------------------------------------")
		if len(status.Players) > 0 {
			fmt.Printf("Players:\t%s\n", status.Players[0])
			for _, player := range status.Players[1:] {
				fmt.Printf("\t\t%s\n", StripColors(player))
			}
			fmt.Println("----------------------------------------")
		}

	} else {
		fmt.Println("----------------------------------------")
		fmt.Printf("Server:\t\t%s\n", s.Name)
		fmt.Println("----------------------------------------")
		fmt.Printf("Address:\t%s:%d\n", s.Ip, s.Port)
		fmt.Printf("Running:\t%t\n", s.Running)
		fmt.Println("----------------------------------------")
	}
}

// Start
// Checks if the server is running and starts the server process if not
// Informs the user of the current server status
func (s *Server) Start() (error) {
	contains, err := s.basePathContainsNecessaryFiles()
	if !contains {
		return errors.New(fmt.Sprintf("Server \"%s\" could not be started: %s\n", s.Name, err))
	}

	if !s.homePathExistsInFS() {
		return errors.New(fmt.Sprintf("Server \"%s\" has an invalid homepath\n", s.Name))
	}

	if s.Running {
		if !s.CheckServer() {
			fmt.Printf("Server \"%s\" should be running, but it is not. Restarting server.\n", s.Name)
			_, err := s.StartServerProcess()
			return err
		} else {
			fmt.Printf("Server \"%s\" is already running. (Process ID: %d)\n", s.Name, s.Pid)
			fmt.Printf("Run \"screen -drS %s\" to open server console.\n", s.Name + strconv.Itoa(s.Port))
			return nil
		}
	}

	serverProcess, err := s.StartServerProcess()
	if err != nil {
		fmt.Printf("Could not start server process: %s\n", err)
		return err
	}

	err = SaveConfig(configFile, globalConfiguration)
	if err != nil {
		fmt.Printf("Error: could not save config \"%s\": %s\n", err)
	}

	fmt.Printf("Started server \"%s\" with process ID: %d\n", s.Name, serverProcess.Process.Pid + 1)
	fmt.Printf("%v\n", serverProcess)
	return nil
}

// StartServerProcess
// Starts the server process if it's not already running
func (s *Server) StartServerProcess() (*exec.Cmd, error) {
	user, err := user.Lookup(s.User)
	if err != nil {
		return &exec.Cmd{}, err
	}

	parameters := []string{
		"-dmS",
		s.Name + strconv.Itoa(s.Port),
	}

	for _, envVar := range s.Env {
		parameters = append(parameters, envVar)
	}

	parameters = append(parameters, globalConfiguration.ExecutablePath)
	parameters = append(parameters, fmt.Sprintf("+set fs_game \"%s\"", s.Mod))

	parameters = append(parameters, fmt.Sprintf("+set com_hunkmegs \"128\""))
	parameters = append(parameters, fmt.Sprintf("+set fs_basepath \"%s\"", s.BasePath))
	parameters = append(parameters, fmt.Sprintf("+set fs_homepath \"%s\"", s.HomePath))
	parameters = append(parameters, fmt.Sprintf("+set net_ip \"%s\"", s.Ip))
	parameters = append(parameters, fmt.Sprintf("+set net_port \"%d\"", s.Port))
	parameters = append(parameters, fmt.Sprintf("+map oasis"))

	for _, config := range s.Configs {
		parameters = append(parameters, fmt.Sprintf("+exec %s", config))
	}

	serverProcess := exec.Command(globalConfiguration.ScreenPath, parameters...)
	serverProcess.Dir = filepath.Dir(globalConfiguration.ExecutablePath)
	// Needed for ET Process, saves .etwolf folder here
	serverProcess.Env = []string{fmt.Sprintf("HOME=%s", user.HomeDir)}

	uid, err := strconv.Atoi(user.Uid)
	if err != nil {
		return serverProcess, err
	}
	gid, err := strconv.Atoi(user.Gid)
	if err != nil {
		return serverProcess, err
	}

	serverProcess.SysProcAttr = &syscall.SysProcAttr{}
	serverProcess.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
	err = serverProcess.Run()
	if err != nil {
		return serverProcess, err
	}
	s.Running = true
	s.Pid = serverProcess.Process.Pid + 1
	return serverProcess, nil

}

// Stop
// Stops the server if it's running
func (s *Server) Stop() (err error) {
	err = nil
	if s.Running {
		fmt.Printf("Stopping server \"%s\"\n", s.Name)

		proc := exec.Command(globalConfiguration.KillPath, strconv.Itoa(s.Pid))
		err := proc.Run()
		fmt.Printf("Stopping server with pid: %d\n", s.Pid)
		if err != nil {
			fmt.Printf("Could not stop server: %s\n", err)
			return err
		}
		s.Running = false
		err = SaveConfig(configFile, globalConfiguration)
		fmt.Printf("Stopped server \"%s\"\n", s.Name)
	} else {
		fmt.Printf("Server \"%s\" is not running.\n", s.Name)
	}
	return err
}

// Restart
// Restarts the server
func (s *Server) Restart() {
	s.Stop()
	fmt.Printf("Waiting 2 seconds")

	fmt.Printf(".")
	time.Sleep(700 * time.Millisecond)
	fmt.Printf(".")
	time.Sleep(700 * time.Millisecond)
	fmt.Printf(".\n")
	time.Sleep(600 * time.Millisecond)

	s.Start()
}

// CheckServer
// Checks that the server is still running
func (s *Server) CheckServer() (bool) {
	if s.Running {
		pgrep := exec.Command(globalConfiguration.PgrepPath, "-f", s.Name + strconv.Itoa(s.Port))

		_, err := pgrep.Output()
		if err != nil {
			return false
		}
	}
	return true
}

// SecurityCheck
// Checks that the calling user is allowed to execute the script
func (s *Server) SecurityCheck() (bool, error) {
	owner, err := user.Lookup(s.User)
	if err != nil {
		return false, err
	}

	serverOwnerUid, err := strconv.Atoi(owner.Uid)
	if err != nil {
		return false, err
	}

	currentUid := os.Geteuid()
	if currentUid == serverOwnerUid || currentUid == 0 {
		return true, nil
	}
	return false, nil
}

//
// Checks if the basepath contains necessary files to host an ET server
// pak0.pk3, pak1.pk3, pak2.pk3 and mp_bin.pk3
func (s *Server) basePathContainsNecessaryFiles() (bool, error) {
	fileInfos, err := ioutil.ReadDir(filepath.Join(s.BasePath, "etmain"))
	if err != nil {
		return false, errors.New(fmt.Sprintf("Basepath check failed: %s", err))
	}

	requiredFiles := map[string]bool{
		"pak0.pk3": false,
		"pak1.pk3": false,
		"pak2.pk3": false,
		"mp_bin.pk3": false,
	}

	// Check that required files can be found at the basepath etmain
	for _, fi := range fileInfos {
		if _, ok := requiredFiles[fi.Name()]; ok {
			requiredFiles[fi.Name()] = true
		}
	}

	missingFiles := []string{}
	for file, exists := range requiredFiles {
		if !exists {
			missingFiles = append(missingFiles, file)
		}
	}

	if len(missingFiles) > 0 {
		return false, errors.New(fmt.Sprintf("Missing the following files: %s", strings.Join(missingFiles, ", ")))
	}
	return true, nil
}

//
// Checks if server's homepath actually exists in file system
func (s *Server) homePathExistsInFS() (bool) {
	fileInfo, err := os.Stat(s.HomePath)

	if err != nil {
		return false
	}

	return fileInfo.IsDir()
}<|endoftext|>


<commit_before>// Copyright 2015-2016 Zack Scholl. All rights reserved.
// Use of this source code is governed by a AGPL
// license that can be found in the LICENSE file.

// server.go handles Flag parsing and starts the Gin-Tonic webserver.

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"strings"

	"github.com/gin-gonic/contrib/sessions"
	"github.com/gin-gonic/gin"
)

// RuntimeArgs contains all runtime
// arguments available
var RuntimeArgs struct {
	ExternalIP        string
	Port              string
	ServerCRT         string
	ServerKey         string
	SourcePath        string
	Socket            string
	Cwd               string
	MqttServer        string
	MqttAdmin         string
	MosquittoPID      string
	MqttAdminPassword string
	Dump              string
	Message           string
	Mqtt              bool
	Svm               bool
}

// VersionNum keeps track of the version
var VersionNum string
var BuildTime string
var Build string

// init initiates the paths in RuntimeArgs
func init() {
	cwd, _ := os.Getwd()
	RuntimeArgs.Cwd = cwd
	RuntimeArgs.SourcePath = path.Join(RuntimeArgs.Cwd, "data")
	RuntimeArgs.Message = ""
}

func main() {
	// _, executableFile, _, _ := runtime.Caller(0) // get full path of this file
	if len(Build) == 0 {
		Build = "devdevdevdevdevdevdev"
	}
	// Bing flags for changing parameters of FIND
	flag.StringVar(&RuntimeArgs.Port, "p", ":8003", "port to bind")
	flag.StringVar(&RuntimeArgs.Socket, "s", "", "unix socket")
	flag.StringVar(&RuntimeArgs.ServerCRT, "crt", "", "location of ssl crt")
	flag.StringVar(&RuntimeArgs.ServerKey, "key", "", "location of ssl key")
	flag.StringVar(&RuntimeArgs.MqttServer, "mqtt", "", "ADDRESS:PORT of mosquitto server")
	flag.StringVar(&RuntimeArgs.MqttAdmin, "mqttadmin", "", "admin to read all messages")
	flag.StringVar(&RuntimeArgs.MqttAdminPassword, "mqttadminpass", "", "admin to read all messages")
	flag.StringVar(&RuntimeArgs.MosquittoPID, "mosquitto", "", "mosquitto PID (`pgrep mosquitto`)")
	flag.StringVar(&RuntimeArgs.Dump, "dump", "", "group to dump to folder")
	flag.StringVar(&RuntimeArgs.Message, "message", "", "message to display to all users")
	flag.StringVar(&RuntimeArgs.SourcePath, "data", "", "path to data folder")
	flag.CommandLine.Usage = func() {
		fmt.Println(`find (version ` + VersionNum + ` (` + Build[0:8] + `), built ` + BuildTime + `)
run this to start the server and then visit localhost at the port you specify
(see parameters).
Example: 'find yourserver.com'
Example: 'find -p :8080 localhost:8080'
Example: 'find -s /var/run/find.sock'
Example: 'find -p :8080 -crt ssl/server.crt -key ssl/server.key localhost:8080'
Options:`)
		flag.CommandLine.PrintDefaults()
	}
	flag.Parse()
	RuntimeArgs.ExternalIP = flag.Arg(0)
	if RuntimeArgs.ExternalIP == "" {
		RuntimeArgs.ExternalIP = GetLocalIP() + RuntimeArgs.Port
	}

	if RuntimeArgs.SourcePath == "" {
		RuntimeArgs.SourcePath = path.Join(RuntimeArgs.Cwd, "data")
	}
	fmt.Println(RuntimeArgs.SourcePath)

	// Check whether all the MQTT variables are passed to initiate the MQTT routines
	if len(RuntimeArgs.MqttServer) > 0 && len(RuntimeArgs.MqttAdmin) > 0 && len(RuntimeArgs.MosquittoPID) > 0 {
		RuntimeArgs.Mqtt = true
		setupMqtt()
	} else {
		RuntimeArgs.Mqtt = false
	}

	// Check whether we are just dumping the database
	if len(RuntimeArgs.Dump) > 0 {
		err := dumpFingerprints(strings.ToLower(RuntimeArgs.Dump))
		if err == nil {
			fmt.Println("Successfully dumped.")
		} else {
			log.Fatal(err)
		}
		os.Exit(1)
	}

	// Check if there is a message from the admin
	if _, err := os.Stat(path.Join(RuntimeArgs.Cwd, "message.txt")); err == nil {
		messageByte, _ := ioutil.ReadFile(path.Join(RuntimeArgs.Cwd, "message.txt"))
		RuntimeArgs.Message = string(messageByte)
	}

	// Check whether SVM libraries are available
	cmdOut, _ := exec.Command("svm-scale", "").CombinedOutput()
	if len(cmdOut) == 0 {
		RuntimeArgs.Svm = false
		fmt.Println("SVM is not detected.")
		fmt.Println(`To install:
sudo apt-get install g++
wget http://www.csie.ntu.edu.tw/~cjlin/cgi-bin/libsvm.cgi?+http://www.csie.ntu.edu.tw/~cjlin/libsvm+tar.gz
tar -xvf libsvm-*.tar.gz
cd libsvm-*
make
cp svm-scale /usr/local/bin/
cp svm-predict /usr/local/bin/
cp svm-train /usr/local/bin/`)
	} else {
		RuntimeArgs.Svm = true
	}

	// Setup Gin-Gonic
	gin.SetMode(gin.ReleaseMode)
	r := gin.Default()

	// Load templates
	r.LoadHTMLGlob(path.Join(RuntimeArgs.Cwd, "templates/*"))

	// Load static files (if they are not hosted by external service)
	r.Static("static/", path.Join(RuntimeArgs.Cwd, "static/"))

	// Create cookie store to keep track of logged in user
	store := sessions.NewCookieStore([]byte("secret"))
	r.Use(sessions.Sessions("mysession", store))

	// 404-page redirects to login
	r.NoRoute(func(c *gin.Context) {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"ErrorMessage": "Please login first.",
		})
	})

	// r.PUT("/message", putMessage)

	// Routes for logging in and viewing dashboards (routes.go)
	r.GET("/", slash)
	r.GET("/login", slashLogin)
	r.POST("/login", slashLoginPOST)
	r.GET("/logout", slashLogout)
	r.GET("/dashboard/:group", slashDashboard)
	r.GET("/explore/:group/:network/:location", slashExplore2)
	r.GET("/pie/:group/:network/:location", slashPie)

	// Routes for performing fingerprinting (fingerprint.go)
	r.POST("/learn", learnFingerprintPOST)
	r.POST("/track", trackFingerprintPOST)

	// Routes for MQTT (mqtt.go)
	r.PUT("/mqtt", putMQTT)

	// Routes for API access (api.go)
	r.GET("/location", getUserLocations)
	r.GET("/editname", editName)
	r.GET("/editusername", editUserName)
	r.GET("/editnetworkname", editNetworkName)
	r.DELETE("/location", deleteLocation)
	r.DELETE("/locations", deleteLocations)
	r.DELETE("/user", deleteUser)
	r.DELETE("/database", deleteDatabase)
	r.GET("/calculate", calculate)
	r.GET("/status", getStatus)
	r.GET("/userlocs", userLocations) // to be deprecated
	r.GET("/whereami", whereAmI)      // to be deprecated
	r.PUT("/mixin", putMixinOverride)
	r.PUT("/database", migrateDatabase)

	// Load and display the logo
	dat, _ := ioutil.ReadFile("./static/logo.txt")
	fmt.Println(string(dat))

	// Check whether user is providing certificates
	if RuntimeArgs.Socket != "" {
		r.RunUnix(RuntimeArgs.Socket)
	} else if RuntimeArgs.ServerCRT != "" && RuntimeArgs.ServerKey != "" {
		fmt.Println(`(version ` + VersionNum + ` build ` + Build[0:8] + `) is up and running on https://` + RuntimeArgs.ExternalIP)
		fmt.Println("-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----")
		r.RunTLS(RuntimeArgs.Port, RuntimeArgs.ServerCRT, RuntimeArgs.ServerKey)
	} else {
		fmt.Println(`(version ` + VersionNum + ` build ` + Build[0:8] + `) is up and running on http://` + RuntimeArgs.ExternalIP)
		fmt.Println("-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----")
		r.Run(RuntimeArgs.Port)
	}
}

// // putMessage usage: curl -G -X PUT "http://localhost:8003/message" --data-urlencode "text=hello world"
// func putMessage(c *gin.Context) {
// 	newText := c.DefaultQuery("text", "none")
// 	if newText != "none" {
// 		RuntimeArgs.Message = newText
// 		c.JSON(http.StatusOK, gin.H{"success": true, "message": "Message set as '" + newText + "'"})
// 	} else {
// 		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Error parsing request"})
// 	}
// }
<commit_msg>Updated help message<commit_after>// Copyright 2015-2016 Zack Scholl. All rights reserved.
// Use of this source code is governed by a AGPL
// license that can be found in the LICENSE file.

// server.go handles Flag parsing and starts the Gin-Tonic webserver.

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"strings"

	"github.com/gin-gonic/contrib/sessions"
	"github.com/gin-gonic/gin"
)

// RuntimeArgs contains all runtime
// arguments available
var RuntimeArgs struct {
	ExternalIP        string
	Port              string
	ServerCRT         string
	ServerKey         string
	SourcePath        string
	Socket            string
	Cwd               string
	MqttServer        string
	MqttAdmin         string
	MosquittoPID      string
	MqttAdminPassword string
	Dump              string
	Message           string
	Mqtt              bool
	Svm               bool
}

// VersionNum keeps track of the version
var VersionNum string
var BuildTime string
var Build string

// init initiates the paths in RuntimeArgs
func init() {
	cwd, _ := os.Getwd()
	RuntimeArgs.Cwd = cwd
	RuntimeArgs.SourcePath = path.Join(RuntimeArgs.Cwd, "data")
	RuntimeArgs.Message = ""
}

func main() {
	// _, executableFile, _, _ := runtime.Caller(0) // get full path of this file
	if len(Build) == 0 {
		Build = "devdevdevdevdevdevdev"
	}
	// Bing flags for changing parameters of FIND
	flag.StringVar(&RuntimeArgs.Port, "p", ":8003", "port to bind")
	flag.StringVar(&RuntimeArgs.Socket, "s", "", "unix socket")
	flag.StringVar(&RuntimeArgs.ServerCRT, "crt", "", "location of ssl crt")
	flag.StringVar(&RuntimeArgs.ServerKey, "key", "", "location of ssl key")
	flag.StringVar(&RuntimeArgs.MqttServer, "mqtt", "", "ADDRESS:PORT of mosquitto server")
	flag.StringVar(&RuntimeArgs.MqttAdmin, "mqttadmin", "", "admin to read all messages")
	flag.StringVar(&RuntimeArgs.MqttAdminPassword, "mqttadminpass", "", "admin to read all messages")
	flag.StringVar(&RuntimeArgs.MosquittoPID, "mosquitto", "", "mosquitto PID (`pgrep mosquitto`)")
	flag.StringVar(&RuntimeArgs.Dump, "dump", "", "group to dump to folder")
	flag.StringVar(&RuntimeArgs.Message, "message", "", "message to display to all users")
	flag.StringVar(&RuntimeArgs.SourcePath, "data", "", "path to data folder")
	flag.CommandLine.Usage = func() {
		fmt.Println(`find (version ` + VersionNum + ` (` + Build[0:8] + `), built ` + BuildTime + `)
Example: 'findserver yourserver.com'
Example: 'findserver -p :8080 localhost:8080'
Example (mosquitto): 'findserver -mqtt 127.0.0.1:1883 -mqttadmin admin -mqttadminpass somepass -mosquitto ` + "`pgrep mosquitto`" + `
Options:`)
		flag.CommandLine.PrintDefaults()
	}
	flag.Parse()
	RuntimeArgs.ExternalIP = flag.Arg(0)
	if RuntimeArgs.ExternalIP == "" {
		RuntimeArgs.ExternalIP = GetLocalIP() + RuntimeArgs.Port
	}

	if RuntimeArgs.SourcePath == "" {
		RuntimeArgs.SourcePath = path.Join(RuntimeArgs.Cwd, "data")
	}
	fmt.Println(RuntimeArgs.SourcePath)

	// Check whether all the MQTT variables are passed to initiate the MQTT routines
	if len(RuntimeArgs.MqttServer) > 0 && len(RuntimeArgs.MqttAdmin) > 0 && len(RuntimeArgs.MosquittoPID) > 0 {
		RuntimeArgs.Mqtt = true
		setupMqtt()
	} else {
		RuntimeArgs.Mqtt = false
	}

	// Check whether we are just dumping the database
	if len(RuntimeArgs.Dump) > 0 {
		err := dumpFingerprints(strings.ToLower(RuntimeArgs.Dump))
		if err == nil {
			fmt.Println("Successfully dumped.")
		} else {
			log.Fatal(err)
		}
		os.Exit(1)
	}

	// Check if there is a message from the admin
	if _, err := os.Stat(path.Join(RuntimeArgs.Cwd, "message.txt")); err == nil {
		messageByte, _ := ioutil.ReadFile(path.Join(RuntimeArgs.Cwd, "message.txt"))
		RuntimeArgs.Message = string(messageByte)
	}

	// Check whether SVM libraries are available
	cmdOut, _ := exec.Command("svm-scale", "").CombinedOutput()
	if len(cmdOut) == 0 {
		RuntimeArgs.Svm = false
		fmt.Println("SVM is not detected.")
		fmt.Println(`To install:
sudo apt-get install g++
wget http://www.csie.ntu.edu.tw/~cjlin/cgi-bin/libsvm.cgi?+http://www.csie.ntu.edu.tw/~cjlin/libsvm+tar.gz
tar -xvf libsvm-*.tar.gz
cd libsvm-*
make
cp svm-scale /usr/local/bin/
cp svm-predict /usr/local/bin/
cp svm-train /usr/local/bin/`)
	} else {
		RuntimeArgs.Svm = true
	}

	// Setup Gin-Gonic
	gin.SetMode(gin.ReleaseMode)
	r := gin.Default()

	// Load templates
	r.LoadHTMLGlob(path.Join(RuntimeArgs.Cwd, "templates/*"))

	// Load static files (if they are not hosted by external service)
	r.Static("static/", path.Join(RuntimeArgs.Cwd, "static/"))

	// Create cookie store to keep track of logged in user
	store := sessions.NewCookieStore([]byte("secret"))
	r.Use(sessions.Sessions("mysession", store))

	// 404-page redirects to login
	r.NoRoute(func(c *gin.Context) {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"ErrorMessage": "Please login first.",
		})
	})

	// r.PUT("/message", putMessage)

	// Routes for logging in and viewing dashboards (routes.go)
	r.GET("/", slash)
	r.GET("/login", slashLogin)
	r.POST("/login", slashLoginPOST)
	r.GET("/logout", slashLogout)
	r.GET("/dashboard/:group", slashDashboard)
	r.GET("/explore/:group/:network/:location", slashExplore2)
	r.GET("/pie/:group/:network/:location", slashPie)

	// Routes for performing fingerprinting (fingerprint.go)
	r.POST("/learn", learnFingerprintPOST)
	r.POST("/track", trackFingerprintPOST)

	// Routes for MQTT (mqtt.go)
	r.PUT("/mqtt", putMQTT)

	// Routes for API access (api.go)
	r.GET("/location", getUserLocations)
	r.GET("/editname", editName)
	r.GET("/editusername", editUserName)
	r.GET("/editnetworkname", editNetworkName)
	r.DELETE("/location", deleteLocation)
	r.DELETE("/locations", deleteLocations)
	r.DELETE("/user", deleteUser)
	r.DELETE("/database", deleteDatabase)
	r.GET("/calculate", calculate)
	r.GET("/status", getStatus)
	r.GET("/userlocs", userLocations) // to be deprecated
	r.GET("/whereami", whereAmI)      // to be deprecated
	r.PUT("/mixin", putMixinOverride)
	r.PUT("/database", migrateDatabase)

	// Load and display the logo
	dat, _ := ioutil.ReadFile("./static/logo.txt")
	fmt.Println(string(dat))

	// Check whether user is providing certificates
	if RuntimeArgs.Socket != "" {
		r.RunUnix(RuntimeArgs.Socket)
	} else if RuntimeArgs.ServerCRT != "" && RuntimeArgs.ServerKey != "" {
		fmt.Println(`(version ` + VersionNum + ` build ` + Build[0:8] + `) is up and running on https://` + RuntimeArgs.ExternalIP)
		fmt.Println("-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----")
		r.RunTLS(RuntimeArgs.Port, RuntimeArgs.ServerCRT, RuntimeArgs.ServerKey)
	} else {
		fmt.Println(`(version ` + VersionNum + ` build ` + Build[0:8] + `) is up and running on http://` + RuntimeArgs.ExternalIP)
		fmt.Println("-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----")
		r.Run(RuntimeArgs.Port)
	}
}

// // putMessage usage: curl -G -X PUT "http://localhost:8003/message" --data-urlencode "text=hello world"
// func putMessage(c *gin.Context) {
// 	newText := c.DefaultQuery("text", "none")
// 	if newText != "none" {
// 		RuntimeArgs.Message = newText
// 		c.JSON(http.StatusOK, gin.H{"success": true, "message": "Message set as '" + newText + "'"})
// 	} else {
// 		c.JSON(http.StatusOK, gin.H{"success": false, "message": "Error parsing request"})
// 	}
// }
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
)

// RequestVars contain variables from the HTTP request. Variables from routing, json body decoding, and
// some headers are stored.
type RequestVars struct {
	Oid           string
	Size          int64
	User          string
	Password      string
	Repo          string
	Authorization string
	PathPrefix    string
	Status        int64
	Body          string
	RequestID     string
}

// Meta is object metadata as seen by the object and metadata stores.
type Meta struct {
	Oid        string `json:"oid"`
	Size       int64  `json:"size"`
	PathPrefix string `json:"path_prefix"`
	existing   bool
}

// Representation is object medata as seen by clients of harbour.
type Representation struct {
	Oid   string           `json:"oid"`
	Size  int64            `json:"size"`
	Links map[string]*link `json:"_links"`
}

// ObjectLink builds a URL linking to the object.
func (v *RequestVars) ObjectLink() string {
	path := fmt.Sprintf("/%s/%s/objects/%s", v.User, v.Repo, v.Oid)
	return fmt.Sprintf("%s://%s%s", Config.Scheme, Config.Host, path)
}

// link provides a structure used to build a hypermedia representation of an HTTP link.
type link struct {
	Href   string            `json:"href"`
	Header map[string]string `json:"header,omitempty"`
}

// App links a Router, ContentStore, and MetaStore to provide the LFS server.
type App struct {
	router       *Router
	contentStore *ContentStore
	metaStore    *MetaStore
}

// NewApp creates a new App using the ContentStore and MetaStore provided
func NewApp(content *ContentStore, meta *MetaStore) *App {
	app := &App{contentStore: content, metaStore: meta}

	r := NewRouter()

	s := r.Route("/{user}/{repo}/objects/{oid}")
	s.Get(contentMediaType, app.GetContentHandler)
	s.Head(contentMediaType, app.GetContentHandler)
	s.Get(metaMediaType, app.GetMetaHandler)
	s.Head(metaMediaType, app.GetMetaHandler)
	s.Put(contentMediaType, app.PutHandler)

	o := r.Route("/{user}/{repo}/objects")
	o.Post(metaMediaType, app.PostHandler)

	app.router = r

	return app
}

// Serve calls http.Serve with the provided Listener and the app's router
func (a *App) Serve(l net.Listener) error {
	return http.Serve(l, a.router)
}

// GetContentHandler gets the content from the content store
func (a *App) GetContentHandler(w http.ResponseWriter, r *http.Request) {
	rv := unpack(r)
	meta, err := a.metaStore.Get(rv)
	if err != nil {
		w.WriteHeader(404)
		logRequest(r, 404)
		return
	}

	content, err := a.contentStore.Get(meta)
	if err != nil {
		w.WriteHeader(404)
		logRequest(r, 404)
		return
	}

	io.Copy(w, content)
	logRequest(r, 200)
}

// GetMetaHandler retrieves metadata about the object
func (a *App) GetMetaHandler(w http.ResponseWriter, r *http.Request) {
	rv := unpack(r)
	meta, err := a.metaStore.Get(rv)
	if err != nil {
		if isAuthError(err) {
			w.WriteHeader(401)
			fmt.Fprintf(w, `{"message":"Forbidden"}`)
			logRequest(r, 401)
		} else {
			w.WriteHeader(404)
			fmt.Fprint(w, `{"message":"Not Found"}`)
			logRequest(r, 404)
		}
		return
	}

	w.Header().Set("Content-Type", metaMediaType)

	if r.Method == "GET" {
		enc := json.NewEncoder(w)
		enc.Encode(a.Represent(rv, meta, false))
	}

	logRequest(r, 200)
}

// PostHandler instructs the client how to upload data
func (a *App) PostHandler(w http.ResponseWriter, r *http.Request) {
	rv := unpack(r)
	meta, err := a.metaStore.Put(rv)
	if err != nil {
		if isAuthError(err) {
			w.WriteHeader(401)
			fmt.Fprint(w, `{"message":"Forbidden"}`)
			logRequest(r, 401)
		} else {
			w.WriteHeader(404)
			fmt.Fprint(w, `{"message":"Not Found"}`)
			logRequest(r, 404)
		}
		return
	}

	w.Header().Set("Content-Type", metaMediaType)

	sentStatus := 200
	if !meta.existing {
		sentStatus = 201
		w.WriteHeader(201)
	}

	enc := json.NewEncoder(w)
	enc.Encode(a.Represent(rv, meta, true))
	logRequest(r, sentStatus)
}

// PutHandler receives data from the client and puts it into the content store
func (a *App) PutHandler(w http.ResponseWriter, r *http.Request) {
	rv := unpack(r)
	meta, err := a.metaStore.Get(rv)
	if err != nil {
		if isAuthError(err) {
			w.WriteHeader(401)
			fmt.Fprint(w, `{"message":"Forbidden"}`)
			logRequest(r, 401)
		} else {
			w.WriteHeader(404)
			fmt.Fprint(w, `{"message":"Not Found"}`)
			logRequest(r, 404)
		}
		return
	}

	if err := a.contentStore.Put(meta, r.Body); err != nil {
		w.WriteHeader(500)
		fmt.Fprintf(w, `{"message":"Error: %s"}`, err)
		return
	}

	logRequest(r, 200)
}

// Represent takes a RequestVars and Meta and turns it into a Representation suitable
// for json encoding
func (a *App) Represent(rv *RequestVars, meta *Meta, upload bool) *Representation {
	rep := &Representation{
		Oid:   meta.Oid,
		Size:  meta.Size,
		Links: make(map[string]*link),
	}

	rep.Links["download"] = &link{Href: rv.ObjectLink(), Header: map[string]string{"Accept": contentMediaType}}
	if upload {
		header := make(map[string]string)
		header["Accept"] = contentMediaType
		header["Authorization"] = rv.Authorization
		rep.Links["upload"] = &link{Href: rv.ObjectLink(), Header: header}
	}
	return rep
}

func unpack(r *http.Request) *RequestVars {
	vars := Vars(r)
	user, pass, _ := r.BasicAuth()

	rv := &RequestVars{
		User:          user,
		Password:      pass,
		Repo:          vars["repo"],
		Oid:           vars["oid"],
		Authorization: r.Header.Get("Authorization"),
		PathPrefix:    r.Header.Get("PathPrefix"),
		RequestID:     vars["request_id"],
	}

	if r.Method == "POST" { // Maybe also check if +json
		var p RequestVars
		dec := json.NewDecoder(r.Body)
		err := dec.Decode(&p)
		if err != nil {
			return rv
		}

		rv.Oid = p.Oid
		rv.Size = p.Size
		rv.Status = p.Status
		rv.Body = p.Body
	}

	return rv
}

func logRequest(r *http.Request, status int) {
	logger.Log(kv{"method": r.Method, "url": r.URL, "status": status, "request_id": Vars(r)["request_id"]})
}

func isAuthError(err error) bool {
	type autherror interface {
		AuthError() bool
	}
	if ae, ok := err.(autherror); ok {
		return ae.AuthError()
	}
	return false
}
<commit_msg>Remove some unneded fields from RequestVars<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
)

// RequestVars contain variables from the HTTP request. Variables from routing, json body decoding, and
// some headers are stored.
type RequestVars struct {
	Oid           string
	Size          int64
	User          string
	Password      string
	Repo          string
	Authorization string
	RequestID     string
}

// Meta is object metadata as seen by the object and metadata stores.
type Meta struct {
	Oid      string `json:"oid"`
	Size     int64  `json:"size"`
	existing bool
}

// Representation is object medata as seen by clients of harbour.
type Representation struct {
	Oid   string           `json:"oid"`
	Size  int64            `json:"size"`
	Links map[string]*link `json:"_links"`
}

// ObjectLink builds a URL linking to the object.
func (v *RequestVars) ObjectLink() string {
	path := fmt.Sprintf("/%s/%s/objects/%s", v.User, v.Repo, v.Oid)
	return fmt.Sprintf("%s://%s%s", Config.Scheme, Config.Host, path)
}

// link provides a structure used to build a hypermedia representation of an HTTP link.
type link struct {
	Href   string            `json:"href"`
	Header map[string]string `json:"header,omitempty"`
}

// App links a Router, ContentStore, and MetaStore to provide the LFS server.
type App struct {
	router       *Router
	contentStore *ContentStore
	metaStore    *MetaStore
}

// NewApp creates a new App using the ContentStore and MetaStore provided
func NewApp(content *ContentStore, meta *MetaStore) *App {
	app := &App{contentStore: content, metaStore: meta}

	r := NewRouter()

	s := r.Route("/{user}/{repo}/objects/{oid}")
	s.Get(contentMediaType, app.GetContentHandler)
	s.Head(contentMediaType, app.GetContentHandler)
	s.Get(metaMediaType, app.GetMetaHandler)
	s.Head(metaMediaType, app.GetMetaHandler)
	s.Put(contentMediaType, app.PutHandler)

	o := r.Route("/{user}/{repo}/objects")
	o.Post(metaMediaType, app.PostHandler)

	app.router = r

	return app
}

// Serve calls http.Serve with the provided Listener and the app's router
func (a *App) Serve(l net.Listener) error {
	return http.Serve(l, a.router)
}

// GetContentHandler gets the content from the content store
func (a *App) GetContentHandler(w http.ResponseWriter, r *http.Request) {
	rv := unpack(r)
	meta, err := a.metaStore.Get(rv)
	if err != nil {
		w.WriteHeader(404)
		logRequest(r, 404)
		return
	}

	content, err := a.contentStore.Get(meta)
	if err != nil {
		w.WriteHeader(404)
		logRequest(r, 404)
		return
	}

	io.Copy(w, content)
	logRequest(r, 200)
}

// GetMetaHandler retrieves metadata about the object
func (a *App) GetMetaHandler(w http.ResponseWriter, r *http.Request) {
	rv := unpack(r)
	meta, err := a.metaStore.Get(rv)
	if err != nil {
		if isAuthError(err) {
			w.WriteHeader(401)
			fmt.Fprintf(w, `{"message":"Forbidden"}`)
			logRequest(r, 401)
		} else {
			w.WriteHeader(404)
			fmt.Fprint(w, `{"message":"Not Found"}`)
			logRequest(r, 404)
		}
		return
	}

	w.Header().Set("Content-Type", metaMediaType)

	if r.Method == "GET" {
		enc := json.NewEncoder(w)
		enc.Encode(a.Represent(rv, meta, false))
	}

	logRequest(r, 200)
}

// PostHandler instructs the client how to upload data
func (a *App) PostHandler(w http.ResponseWriter, r *http.Request) {
	rv := unpack(r)
	meta, err := a.metaStore.Put(rv)
	if err != nil {
		if isAuthError(err) {
			w.WriteHeader(401)
			fmt.Fprint(w, `{"message":"Forbidden"}`)
			logRequest(r, 401)
		} else {
			w.WriteHeader(404)
			fmt.Fprint(w, `{"message":"Not Found"}`)
			logRequest(r, 404)
		}
		return
	}

	w.Header().Set("Content-Type", metaMediaType)

	sentStatus := 200
	if !meta.existing {
		sentStatus = 201
		w.WriteHeader(201)
	}

	enc := json.NewEncoder(w)
	enc.Encode(a.Represent(rv, meta, true))
	logRequest(r, sentStatus)
}

// PutHandler receives data from the client and puts it into the content store
func (a *App) PutHandler(w http.ResponseWriter, r *http.Request) {
	rv := unpack(r)
	meta, err := a.metaStore.Get(rv)
	if err != nil {
		if isAuthError(err) {
			w.WriteHeader(401)
			fmt.Fprint(w, `{"message":"Forbidden"}`)
			logRequest(r, 401)
		} else {
			w.WriteHeader(404)
			fmt.Fprint(w, `{"message":"Not Found"}`)
			logRequest(r, 404)
		}
		return
	}

	if err := a.contentStore.Put(meta, r.Body); err != nil {
		w.WriteHeader(500)
		fmt.Fprintf(w, `{"message":"Error: %s"}`, err)
		return
	}

	logRequest(r, 200)
}

// Represent takes a RequestVars and Meta and turns it into a Representation suitable
// for json encoding
func (a *App) Represent(rv *RequestVars, meta *Meta, upload bool) *Representation {
	rep := &Representation{
		Oid:   meta.Oid,
		Size:  meta.Size,
		Links: make(map[string]*link),
	}

	rep.Links["download"] = &link{Href: rv.ObjectLink(), Header: map[string]string{"Accept": contentMediaType}}
	if upload {
		header := make(map[string]string)
		header["Accept"] = contentMediaType
		header["Authorization"] = rv.Authorization
		rep.Links["upload"] = &link{Href: rv.ObjectLink(), Header: header}
	}
	return rep
}

func unpack(r *http.Request) *RequestVars {
	vars := Vars(r)
	user, pass, _ := r.BasicAuth()

	rv := &RequestVars{
		User:          user,
		Password:      pass,
		Repo:          vars["repo"],
		Oid:           vars["oid"],
		Authorization: r.Header.Get("Authorization"),
		RequestID:     vars["request_id"],
	}

	if r.Method == "POST" { // Maybe also check if +json
		var p RequestVars
		dec := json.NewDecoder(r.Body)
		err := dec.Decode(&p)
		if err != nil {
			return rv
		}

		rv.Oid = p.Oid
		rv.Size = p.Size
	}

	return rv
}

func logRequest(r *http.Request, status int) {
	logger.Log(kv{"method": r.Method, "url": r.URL, "status": status, "request_id": Vars(r)["request_id"]})
}

func isAuthError(err error) bool {
	type autherror interface {
		AuthError() bool
	}
	if ae, ok := err.(autherror); ok {
		return ae.AuthError()
	}
	return false
}
<|endoftext|>


<commit_before>package geoy

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"googlemaps.github.io/maps"
)

const (
	defaultSRID = 4326 // https://en.wikipedia.org/wiki/World_Geodetic_System
)

type pointEWKB struct {
	Endiness byte
	Type     uint32
	SRID     uint32
	X        float64
	Y        float64
}

// LatLnger defines a struct that can convert itself to cartesian coordinates.
type LatLnger interface {
	LatLng() []float64
}

// LatLng implements LatLnger
type LatLng maps.LatLng

// LatLng returns an array of [lat, lon]
func (l LatLng) LatLng() []float64 {
	return []float64{l.Lng, l.Lat}
}

// Point is a standard GeoJSON 2d Point with x,y coordinates
type Point struct {
	Type        string    `json:"type"`
	Coordinates []float64 `json:"coordinates"`
}

// NewPoint creates a GeoJSON 2d point.
func NewPoint(x, y float64) *Point {
	p := Point{Type: "point", Coordinates: []float64{x, y}}
	return &p
}

// LatLng implements LatLngr.
func (p Point) LatLng() []float64 {
	return []float64{p.Coordinates[1], p.Coordinates[0]}
}

// MarshalDB prepares the point to be stored.
func (p Point) MarshalDB() (interface{}, error) {
	return p.WKT(), nil
}

// UnmarshalDB converts an stored point into a Point struct.
func (p *Point) UnmarshalDB(v interface{}) error {
	s := string(v.([]byte))

	b, err := hex.DecodeString(s)
	if err != nil {
		return err
	}

	buf := bytes.NewReader(b)
	var ewkb pointEWKB
	err = binary.Read(buf, binary.LittleEndian, &ewkb)
	if err != nil {
		return err
	}
	p.Type = "point"
	p.Coordinates = []float64{ewkb.X, ewkb.Y}
	return nil
}

// WKT implements Geometry.
func (p Point) WKT() string {
	return fmt.Sprintf("SRID=%d;POINT(%0.6f %0.6f)", defaultSRID, p.Coordinates[0], p.Coordinates[1])
}

// String returns a text representation of the point.
func (p Point) String() string {
	return p.WKT()
}

// PointFromLatLng converts a latLng to a cartesian Point.
func PointFromLatLng(latlon LatLnger) *Point {
	l := latlon.LatLng()
	x, y := l[1], l[0]
	return NewPoint(x, y)
}

// LatLngFromPoint converts a Point to a LatLng.
func LatLngFromPoint(p Point) *LatLng {
	return &LatLng{Lat: p.Coordinates[1], Lng: p.Coordinates[0]}
}

// InstagramLocation is an object representing the location information returned by the Instagram API
type InstagramLocation struct {
	Latitude  float64
	Longitude float64
	ID        string
	Name      string
}

// LatLng implements LatLnger
func (l InstagramLocation) LatLng() []float64 {
	return []float64{l.Latitude, l.Longitude}
}

// FacebookLocation is an object representing the location information returned
// by the Facebook API
type FacebookLocation InstagramLocation

// TwitterLocation is an object representing the location information returned
// by the Twitter API (a GeoJSON Point)
type TwitterLocation Point

// Geometry defines WKT, which provides a text representation of a vector.
type Geometry interface {
	WKT() string
}

// Envelope is a GeoJSON like shape where coordinates contains [[left, top],
// [right, bottom]]
type Envelope struct {
	Type        string      `json:"type"`
	Coordinates [][]float64 `json:"coordinates"`
}

// NewEnvelope creates an envelope.
func NewEnvelope(left, top, right, bottom float64) *Envelope {
	e := Envelope{Type: "envelope", Coordinates: [][]float64{[]float64{left, top}, []float64{right, bottom}}}
	return &e
}

// WKT implements Geometry.
func (e Envelope) WKT() string {
	l, t := e.Coordinates[0][0], e.Coordinates[0][1]
	r, b := e.Coordinates[1][0], e.Coordinates[1][1]
	return fmt.Sprintf("POLYGON((%0.6f %0.6f, %0.6f %0.6f, %0.6f %0.6f, %0.6f %0.6f, %0.6f %0.6f))", l, t, l, b, r, b, r, t, l, t)
}

// MarshalDB implements db.Marshaler
func (e Envelope) MarshalDB() (interface{}, error) {
	return e.WKT(), nil
}

// Implements fmt.Stringer
func (e Envelope) String() string {
	return e.WKT()
}
<commit_msg>Adding NewPointFromLatLng<commit_after>package geoy

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"googlemaps.github.io/maps"
)

const (
	defaultSRID = 4326 // https://en.wikipedia.org/wiki/World_Geodetic_System
)

type pointEWKB struct {
	Endiness byte
	Type     uint32
	SRID     uint32
	X        float64
	Y        float64
}

// LatLnger defines a struct that can convert itself to cartesian coordinates.
type LatLnger interface {
	LatLng() []float64
}

// LatLng implements LatLnger
type LatLng maps.LatLng

// LatLng returns an array of [lat, lon]
func (l LatLng) LatLng() []float64 {
	return []float64{l.Lng, l.Lat}
}

// Point is a standard GeoJSON 2d Point with x,y coordinates
type Point struct {
	Type        string    `json:"type"`
	Coordinates []float64 `json:"coordinates"`
}

// NewPoint creates a GeoJSON 2d point.
func NewPoint(x, y float64) *Point {
	p := Point{Type: "point", Coordinates: []float64{x, y}}
	return &p
}

// NewPointFromLatLng creates a cartesian point that represents the given
// geographic coordinates.
func NewPointFromLatLng(lat, lng float64) *Point {
	return NewPoint(lng, lat)
}

// LatLng implements LatLngr.
func (p Point) LatLng() []float64 {
	return []float64{p.Coordinates[1], p.Coordinates[0]}
}

// MarshalDB prepares the point to be stored.
func (p Point) MarshalDB() (interface{}, error) {
	return p.WKT(), nil
}

// UnmarshalDB converts an stored point into a Point struct.
func (p *Point) UnmarshalDB(v interface{}) error {
	s := string(v.([]byte))

	b, err := hex.DecodeString(s)
	if err != nil {
		return err
	}

	buf := bytes.NewReader(b)
	var ewkb pointEWKB
	err = binary.Read(buf, binary.LittleEndian, &ewkb)
	if err != nil {
		return err
	}
	p.Type = "point"
	p.Coordinates = []float64{ewkb.X, ewkb.Y}
	return nil
}

// WKT implements Geometry.
func (p Point) WKT() string {
	return fmt.Sprintf("SRID=%d;POINT(%0.6f %0.6f)", defaultSRID, p.Coordinates[0], p.Coordinates[1])
}

// String returns a text representation of the point.
func (p Point) String() string {
	return p.WKT()
}

// PointFromLatLng converts a latLng to a cartesian Point.
func PointFromLatLng(latlon LatLnger) *Point {
	l := latlon.LatLng()
	x, y := l[1], l[0]
	return NewPoint(x, y)
}

// LatLngFromPoint converts a Point to a LatLng.
func LatLngFromPoint(p Point) *LatLng {
	return &LatLng{Lat: p.Coordinates[1], Lng: p.Coordinates[0]}
}

// InstagramLocation is an object representing the location information returned by the Instagram API
type InstagramLocation struct {
	Latitude  float64
	Longitude float64
	ID        string
	Name      string
}

// LatLng implements LatLnger
func (l InstagramLocation) LatLng() []float64 {
	return []float64{l.Latitude, l.Longitude}
}

// FacebookLocation is an object representing the location information returned
// by the Facebook API
type FacebookLocation InstagramLocation

// TwitterLocation is an object representing the location information returned
// by the Twitter API (a GeoJSON Point)
type TwitterLocation Point

// Geometry defines WKT, which provides a text representation of a vector.
type Geometry interface {
	WKT() string
}

// Envelope is a GeoJSON like shape where coordinates contains [[left, top],
// [right, bottom]]
type Envelope struct {
	Type        string      `json:"type"`
	Coordinates [][]float64 `json:"coordinates"`
}

// NewEnvelope creates an envelope.
func NewEnvelope(left, top, right, bottom float64) *Envelope {
	e := Envelope{Type: "envelope", Coordinates: [][]float64{[]float64{left, top}, []float64{right, bottom}}}
	return &e
}

// WKT implements Geometry.
func (e Envelope) WKT() string {
	l, t := e.Coordinates[0][0], e.Coordinates[0][1]
	r, b := e.Coordinates[1][0], e.Coordinates[1][1]
	return fmt.Sprintf("POLYGON((%0.6f %0.6f, %0.6f %0.6f, %0.6f %0.6f, %0.6f %0.6f, %0.6f %0.6f))", l, t, l, b, r, b, r, t, l, t)
}

// MarshalDB implements db.Marshaler
func (e Envelope) MarshalDB() (interface{}, error) {
	return e.WKT(), nil
}

// Implements fmt.Stringer
func (e Envelope) String() string {
	return e.WKT()
}
<|endoftext|>


<commit_before>package shezmu

import (
	"fmt"
	"log"
	"os"
	"runtime/debug"
	"sync"
	"time"

	"github.com/localhots/shezmu/stats"
)

// Shezmu is the master daemon.
type Shezmu struct {
	Subscriber  Subscriber
	Publisher   Publisher
	DaemonStats stats.Publisher
	Logger      Logger
	NumWorkers  int

	daemons      []Daemon
	queue        chan *task
	runtimeStats stats.Manager

	wgWorkers       sync.WaitGroup
	wgSystem        sync.WaitGroup
	shutdownWorkers chan struct{}
	shutdownSystem  chan struct{}
}

// Actor is a function that could be executed by daemon workers.
type Actor func()

// Subscriber is the interface that is used by daemons to subscribe to messages.
type Subscriber interface {
	Subscribe(consumerName, topic string) Streamer
}

// Streamer is the interface that wraps message consumers. Error handling
// should be provided by the implementation. Feel free to panic.
type Streamer interface {
	Messages() <-chan []byte
	Close()
}

// Publisher is the interface that wraps message publishers. Error handling
// should be provided by the implementation. Feel free to panic.
type Publisher interface {
	Publish(topic string, msg []byte, meta interface{})
	Close()
}

// Logger is the interface that implements minimal logging functions.
type Logger interface {
	Printf(format string, v ...interface{})
	Println(v ...interface{})
}

type task struct {
	daemon    Daemon
	actor     Actor
	createdAt time.Time
	system    bool
	name      string
}

const (
	// DefaultNumWorkers is the default number of workers that would process
	// tasks.
	DefaultNumWorkers = 100
)

// Summon creates a new instance of Shezmu.
func Summon() *Shezmu {
	return &Shezmu{
		DaemonStats:     &stats.Void{},
		Logger:          log.New(os.Stdout, "", log.LstdFlags),
		NumWorkers:      DefaultNumWorkers,
		queue:           make(chan *task),
		runtimeStats:    stats.NewBasicStats(),
		shutdownWorkers: make(chan struct{}),
		shutdownSystem:  make(chan struct{}),
	}
}

// AddDaemon adds a new daemon.
func (s *Shezmu) AddDaemon(d Daemon) {
	base := d.base()
	base.self = d
	base.subscriber = s.Subscriber
	base.publisher = s.Publisher
	base.queue = s.queue
	base.logger = s.Logger
	base.shutdown = s.shutdownSystem

	s.daemons = append(s.daemons, d)
}

// ClearDaemons clears the list of added daemons. StopDaemons() function MUST be
// called before calling ClearDaemons().
func (s *Shezmu) ClearDaemons() {
	s.daemons = []Daemon{}
}

// StartDaemons starts all registered daemons.
func (s *Shezmu) StartDaemons() {
	s.Logger.Printf("Starting %d workers", s.NumWorkers)
	for i := 0; i < s.NumWorkers; i++ {
		go s.runWorker()
	}

	s.Logger.Println("Setting up daemons")
	for _, d := range s.daemons {
		s.setupDaemon(d)
	}
}

// StopDaemons stops all running daemons.
func (s *Shezmu) StopDaemons() {
	close(s.shutdownSystem)
	for _, d := range s.daemons {
		d.Shutdown()
	}

	s.wgSystem.Wait()
	close(s.shutdownWorkers)
	s.wgWorkers.Wait()
	close(s.queue)

	fmt.Println(s.runtimeStats.Fetch(stats.Latency))
}

func (s *Shezmu) setupDaemon(d Daemon) {
	defer func() {
		if err := recover(); err != nil {
			s.Logger.Printf("Failed to setup daemon %s due to process termination", d)
		}
	}()

	s.queue <- &task{
		daemon:    d,
		actor:     d.Startup,
		createdAt: time.Now(),
		system:    true,
		name:      "startup",
	}
}

func (s *Shezmu) runWorker() {
	s.wgWorkers.Add(1)
	defer s.wgWorkers.Done()
	defer func() {
		if err := recover(); err != nil {
			s.Logger.Printf("Worker crashed. Error: %v\n", err)
			debug.PrintStack()
			go s.runWorker() // Restarting worker
		}
	}()

	for {
		select {
		case t := <-s.queue:
			s.processTask(t)
		case <-s.shutdownWorkers:
			return
		}
	}
}

func (s *Shezmu) processTask(t *task) {
	dur := time.Now().Sub(t.createdAt)
	s.runtimeStats.Add(stats.Latency, dur)

	if t.system {
		s.processSystemTask(t)
	} else {
		s.processGeneralTask(t)
	}
}

func (s *Shezmu) processSystemTask(t *task) {
	// Abort starting a system task if shutdown was already called. Otherwise
	// incrementing a wait group counter will cause a panic. This should be an
	// extremely rare scenario when a system task crashes and tries to restart
	// after a shutdown call.
	select {
	case <-s.shutdownSystem:
		return
	default:
	}

	s.wgSystem.Add(1)
	defer s.wgSystem.Done()
	defer func() {
		if err := recover(); err != nil {
			s.Logger.Printf("System task %s recovered from a panic\nError: %v\n", t, err)
			debug.PrintStack()

			t.createdAt = time.Now()
			s.queue <- t // Restarting task
		} else {
			s.Logger.Printf("System task %s finished\n", t)
		}
	}()

	s.Logger.Printf("Starting system task %s\n", t)
	t.actor() // <--- ACTION STARTS HERE
}

func (s *Shezmu) processGeneralTask(t *task) {
	defer func() {
		if err := recover(); err != nil {
			s.DaemonStats.Error(t.daemon.String())
			t.daemon.base().handlePanic(err)
			s.Logger.Printf("Daemon %s recovered from a panic\nError: %v\n", t.daemon, err)
			debug.PrintStack()
		}
	}()
	defer func(start time.Time) {
		dur := time.Now().Sub(start)
		s.DaemonStats.Add(t.daemon.String(), dur)
	}(time.Now())

	t.actor() // <--- ACTION STARTS HERE
}

func (t *task) String() string {
	if t.name == "" {
		return fmt.Sprintf("[unnamed %s process]", t.daemon)
	}

	return fmt.Sprintf("%s[%s]", t.daemon, t.name)
}
<commit_msg>Re-open closed channels after stop<commit_after>package shezmu

import (
	"fmt"
	"log"
	"os"
	"runtime/debug"
	"sync"
	"time"

	"github.com/localhots/shezmu/stats"
)

// Shezmu is the master daemon.
type Shezmu struct {
	Subscriber  Subscriber
	Publisher   Publisher
	DaemonStats stats.Publisher
	Logger      Logger
	NumWorkers  int

	daemons      []Daemon
	queue        chan *task
	runtimeStats stats.Manager

	wgWorkers       sync.WaitGroup
	wgSystem        sync.WaitGroup
	shutdownWorkers chan struct{}
	shutdownSystem  chan struct{}
}

// Actor is a function that could be executed by daemon workers.
type Actor func()

// Subscriber is the interface that is used by daemons to subscribe to messages.
type Subscriber interface {
	Subscribe(consumerName, topic string) Streamer
}

// Streamer is the interface that wraps message consumers. Error handling
// should be provided by the implementation. Feel free to panic.
type Streamer interface {
	Messages() <-chan []byte
	Close()
}

// Publisher is the interface that wraps message publishers. Error handling
// should be provided by the implementation. Feel free to panic.
type Publisher interface {
	Publish(topic string, msg []byte, meta interface{})
	Close()
}

// Logger is the interface that implements minimal logging functions.
type Logger interface {
	Printf(format string, v ...interface{})
	Println(v ...interface{})
}

type task struct {
	daemon    Daemon
	actor     Actor
	createdAt time.Time
	system    bool
	name      string
}

const (
	// DefaultNumWorkers is the default number of workers that would process
	// tasks.
	DefaultNumWorkers = 100
)

// Summon creates a new instance of Shezmu.
func Summon() *Shezmu {
	return &Shezmu{
		DaemonStats:     &stats.Void{},
		Logger:          log.New(os.Stdout, "", log.LstdFlags),
		NumWorkers:      DefaultNumWorkers,
		queue:           make(chan *task),
		runtimeStats:    stats.NewBasicStats(),
		shutdownWorkers: make(chan struct{}),
		shutdownSystem:  make(chan struct{}),
	}
}

// AddDaemon adds a new daemon.
func (s *Shezmu) AddDaemon(d Daemon) {
	base := d.base()
	base.self = d
	base.subscriber = s.Subscriber
	base.publisher = s.Publisher
	base.queue = s.queue
	base.logger = s.Logger
	base.shutdown = s.shutdownSystem

	s.daemons = append(s.daemons, d)
}

// ClearDaemons clears the list of added daemons. StopDaemons() function MUST be
// called before calling ClearDaemons().
func (s *Shezmu) ClearDaemons() {
	s.daemons = []Daemon{}
}

// StartDaemons starts all registered daemons.
func (s *Shezmu) StartDaemons() {
	s.Logger.Printf("Starting %d workers", s.NumWorkers)
	for i := 0; i < s.NumWorkers; i++ {
		go s.runWorker()
	}

	s.Logger.Println("Setting up daemons")
	for _, d := range s.daemons {
		s.setupDaemon(d)
	}
}

// StopDaemons stops all running daemons.
func (s *Shezmu) StopDaemons() {
	close(s.shutdownSystem)
	for _, d := range s.daemons {
		d.Shutdown()
	}

	s.wgSystem.Wait()
	close(s.shutdownWorkers)

	s.wgWorkers.Wait()
	close(s.queue)

	// Re-open closed channels to allow starting new deamons afterwards
	s.shutdownSystem = make(chan struct{})
	s.shutdownWorkers = make(chan struct{})
	s.queue = make(chan *task)

	fmt.Println(s.runtimeStats.Fetch(stats.Latency))
}

func (s *Shezmu) setupDaemon(d Daemon) {
	defer func() {
		if err := recover(); err != nil {
			s.Logger.Printf("Failed to setup daemon %s due to process termination", d)
		}
	}()

	s.queue <- &task{
		daemon:    d,
		actor:     d.Startup,
		createdAt: time.Now(),
		system:    true,
		name:      "startup",
	}
}

func (s *Shezmu) runWorker() {
	s.wgWorkers.Add(1)
	defer s.wgWorkers.Done()
	defer func() {
		if err := recover(); err != nil {
			s.Logger.Printf("Worker crashed. Error: %v\n", err)
			debug.PrintStack()
			go s.runWorker() // Restarting worker
		}
	}()

	for {
		select {
		case t := <-s.queue:
			s.processTask(t)
		case <-s.shutdownWorkers:
			return
		}
	}
}

func (s *Shezmu) processTask(t *task) {
	dur := time.Now().Sub(t.createdAt)
	s.runtimeStats.Add(stats.Latency, dur)

	if t.system {
		s.processSystemTask(t)
	} else {
		s.processGeneralTask(t)
	}
}

func (s *Shezmu) processSystemTask(t *task) {
	// Abort starting a system task if shutdown was already called. Otherwise
	// incrementing a wait group counter will cause a panic. This should be an
	// extremely rare scenario when a system task crashes and tries to restart
	// after a shutdown call.
	select {
	case <-s.shutdownSystem:
		return
	default:
	}

	s.wgSystem.Add(1)
	defer s.wgSystem.Done()
	defer func() {
		if err := recover(); err != nil {
			s.Logger.Printf("System task %s recovered from a panic\nError: %v\n", t, err)
			debug.PrintStack()

			t.createdAt = time.Now()
			s.queue <- t // Restarting task
		} else {
			s.Logger.Printf("System task %s finished\n", t)
		}
	}()

	s.Logger.Printf("Starting system task %s\n", t)
	t.actor() // <--- ACTION STARTS HERE
}

func (s *Shezmu) processGeneralTask(t *task) {
	defer func() {
		if err := recover(); err != nil {
			s.DaemonStats.Error(t.daemon.String())
			t.daemon.base().handlePanic(err)
			s.Logger.Printf("Daemon %s recovered from a panic\nError: %v\n", t.daemon, err)
			debug.PrintStack()
		}
	}()
	defer func(start time.Time) {
		dur := time.Now().Sub(start)
		s.DaemonStats.Add(t.daemon.String(), dur)
	}(time.Now())

	t.actor() // <--- ACTION STARTS HERE
}

func (t *task) String() string {
	if t.name == "" {
		return fmt.Sprintf("[unnamed %s process]", t.daemon)
	}

	return fmt.Sprintf("%s[%s]", t.daemon, t.name)
}
<|endoftext|>