<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/mjibson/mog/server"
	"gopkg.in/fsnotify.v1"

	// codecs
	_ "github.com/mjibson/mog/codec/mpa"
	_ "github.com/mjibson/mog/codec/nsf"

	// protocols
	"github.com/mjibson/mog/protocol/drive"
	"github.com/mjibson/mog/protocol/dropbox"
	_ "github.com/mjibson/mog/protocol/file"
	_ "github.com/mjibson/mog/protocol/gmusic"
	"github.com/mjibson/mog/protocol/soundcloud"
)

var (
	flagWatch      = flag.Bool("w", false, "watch current directory and exit on changes; for use with an autorestarter")
	flagDrive      = flag.String("drive", "", "Google Drive API credentials of the form ClientID:ClientSecret")
	flagDropbox    = flag.String("dropbox", "", "Dropbox API credentials of the form ClientID:ClientSecret")
	flagSoundcloud = flag.String("soundcloud", "", "SoundCloud API credentials of the form ClientID:ClientSecret")
)

func main() {
	flag.Parse()
	if *flagWatch {
		watch(".", "*.go", quit)
		base := filepath.Join("server", "static")
		src := filepath.Join(base, "src")
		watch(src, "*.js", jsx)
		go jsx()
	}
	redir := DefaultAddr
	if strings.HasPrefix(redir, ":") {
		redir = "localhost" + redir
	}
	redir = "http://" + redir + "/api/oauth/"
	if *flagDrive != "" {
		sp := strings.Split(*flagDrive, ":")
		if len(sp) != 2 {
			log.Fatal("bad drive string %s", *flagDrive)
		}
		drive.Init(sp[0], sp[1], redir)
	}
	if *flagDropbox != "" {
		sp := strings.Split(*flagDropbox, ":")
		if len(sp) != 2 {
			log.Fatal("bad drive string %s", *flagDropbox)
		}
		dropbox.Init(sp[0], sp[1], redir)
	}
	if *flagSoundcloud != "" {
		sp := strings.Split(*flagSoundcloud, ":")
		if len(sp) != 2 {
			log.Fatal("bad drive string %s", *flagSoundcloud)
		}
		soundcloud.Init(sp[0], sp[1], redir)
	}
	log.Fatal(server.ListenAndServe("mog.state", DefaultAddr))
}

const DefaultAddr = ":6601"

func quit() {
	os.Exit(0)
}

func jsx() {
	base := filepath.Join("server", "static")
	src := filepath.Join(base, "src")
	js := filepath.Join(base, "js")
	log.Println("running jsx")
	res := new(bytes.Buffer)
	files := []string{
		"mog.js",

		"list.js",
		"protocol.js",

		"nav.js",
	}
	for _, name := range files {
		fname := filepath.Join(src, name)
		f, err := os.Open(fname)
		if err != nil {
			panic(err)
		}
		c := exec.Command("jsx")
		c.Stdin = io.MultiReader(f)
		c.Stdout = res
		buf := new(bytes.Buffer)
		c.Stderr = buf
		if err := c.Start(); err != nil {
			log.Fatal(err)
		}
		if err := c.Wait(); err != nil {
			log.Printf("jsx error: %v", fname)
			fmt.Println(strip(buf.String()))
		}
		f.Close()
	}
	of, err := os.Create(filepath.Join(js, "mog.js"))
	if err != nil {
		log.Fatal(err)
	}
	of.Write(res.Bytes())
	of.Close()
	log.Println("jsx complete")
}

// strip removes non-ASCII chars from s.
func strip(s string) string {
	b := new(bytes.Buffer)
	ignore := false
	for _, c := range s {
		switch c {
		case 27:
			ignore = true
		case 'm':
			ignore = false
		default:
			if !ignore {
				b.WriteRune(c)
			}
		}
	}
	return b.String()
}

func run(name string, arg ...string) func() {
	return func() {
		log.Println("running", name)
		c := exec.Command(name, arg...)
		stdout, err := c.StdoutPipe()
		if err != nil {
			log.Fatal(err)
		}
		stderr, err := c.StderrPipe()
		if err != nil {
			log.Fatal(err)
		}
		if err := c.Start(); err != nil {
			log.Fatal(err)
		}
		go func() { io.Copy(os.Stdout, stdout) }()
		go func() { io.Copy(os.Stderr, stderr) }()
		if err := c.Wait(); err != nil {
			log.Printf("run error: %v: %v", name, err)
		}
		log.Println("run complete:", name)
	}
}

func watch(root, pattern string, f func()) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}
	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if matched, err := filepath.Match(pattern, info.Name()); err != nil {
			log.Fatal(err)
		} else if !matched {
			return nil
		}
		err = watcher.Add(path)
		if err != nil {
			log.Fatal(err)
		}
		return nil
	})
	log.Println("watching", pattern, "in", root)
	wait := time.Now()
	go func() {
		for {
			select {
			case event := <-watcher.Events:
				if wait.After(time.Now()) {
					continue
				}
				if event.Op&fsnotify.Write == fsnotify.Write {
					f()
					wait = time.Now().Add(time.Second * 2)
				}
			case err := <-watcher.Errors:
				log.Println("error:", err)
			}
		}
	}()
}

//go:generate esc -o server/static.go -pkg server -prefix server server/static/index.html server/static/css server/static/fonts server/static/js
<commit_msg>Don't strip 'm'<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/mjibson/mog/server"
	"gopkg.in/fsnotify.v1"

	// codecs
	_ "github.com/mjibson/mog/codec/mpa"
	_ "github.com/mjibson/mog/codec/nsf"

	// protocols
	"github.com/mjibson/mog/protocol/drive"
	"github.com/mjibson/mog/protocol/dropbox"
	_ "github.com/mjibson/mog/protocol/file"
	_ "github.com/mjibson/mog/protocol/gmusic"
	"github.com/mjibson/mog/protocol/soundcloud"
)

var (
	flagWatch      = flag.Bool("w", false, "watch current directory and exit on changes; for use with an autorestarter")
	flagDrive      = flag.String("drive", "", "Google Drive API credentials of the form ClientID:ClientSecret")
	flagDropbox    = flag.String("dropbox", "", "Dropbox API credentials of the form ClientID:ClientSecret")
	flagSoundcloud = flag.String("soundcloud", "", "SoundCloud API credentials of the form ClientID:ClientSecret")
)

func main() {
	flag.Parse()
	if *flagWatch {
		watch(".", "*.go", quit)
		base := filepath.Join("server", "static")
		src := filepath.Join(base, "src")
		watch(src, "*.js", jsx)
		go jsx()
	}
	redir := DefaultAddr
	if strings.HasPrefix(redir, ":") {
		redir = "localhost" + redir
	}
	redir = "http://" + redir + "/api/oauth/"
	if *flagDrive != "" {
		sp := strings.Split(*flagDrive, ":")
		if len(sp) != 2 {
			log.Fatal("bad drive string %s", *flagDrive)
		}
		drive.Init(sp[0], sp[1], redir)
	}
	if *flagDropbox != "" {
		sp := strings.Split(*flagDropbox, ":")
		if len(sp) != 2 {
			log.Fatal("bad drive string %s", *flagDropbox)
		}
		dropbox.Init(sp[0], sp[1], redir)
	}
	if *flagSoundcloud != "" {
		sp := strings.Split(*flagSoundcloud, ":")
		if len(sp) != 2 {
			log.Fatal("bad drive string %s", *flagSoundcloud)
		}
		soundcloud.Init(sp[0], sp[1], redir)
	}
	log.Fatal(server.ListenAndServe("mog.state", DefaultAddr))
}

const DefaultAddr = ":6601"

func quit() {
	os.Exit(0)
}

func jsx() {
	base := filepath.Join("server", "static")
	src := filepath.Join(base, "src")
	js := filepath.Join(base, "js")
	log.Println("running jsx")
	res := new(bytes.Buffer)
	files := []string{
		"mog.js",

		"list.js",
		"protocol.js",

		"nav.js",
	}
	for _, name := range files {
		fname := filepath.Join(src, name)
		f, err := os.Open(fname)
		if err != nil {
			panic(err)
		}
		c := exec.Command("jsx")
		c.Stdin = io.MultiReader(f)
		c.Stdout = res
		buf := new(bytes.Buffer)
		c.Stderr = buf
		if err := c.Start(); err != nil {
			log.Fatal(err)
		}
		if err := c.Wait(); err != nil {
			log.Printf("jsx error: %v", fname)
			fmt.Println(strip(buf.String()))
		}
		f.Close()
	}
	of, err := os.Create(filepath.Join(js, "mog.js"))
	if err != nil {
		log.Fatal(err)
	}
	of.Write(res.Bytes())
	of.Close()
	log.Println("jsx complete")
}

// strip removes non-ASCII chars from s.
func strip(s string) string {
	b := new(bytes.Buffer)
	ignore := false
	for _, c := range s {
		switch c {
		case 27:
			ignore = true
		case 'm':
			if ignore {
				ignore = false
			} else {
				b.WriteRune(c)
			}
		default:
			if !ignore {
				b.WriteRune(c)
			}
		}
	}
	return b.String()
}

func run(name string, arg ...string) func() {
	return func() {
		log.Println("running", name)
		c := exec.Command(name, arg...)
		stdout, err := c.StdoutPipe()
		if err != nil {
			log.Fatal(err)
		}
		stderr, err := c.StderrPipe()
		if err != nil {
			log.Fatal(err)
		}
		if err := c.Start(); err != nil {
			log.Fatal(err)
		}
		go func() { io.Copy(os.Stdout, stdout) }()
		go func() { io.Copy(os.Stderr, stderr) }()
		if err := c.Wait(); err != nil {
			log.Printf("run error: %v: %v", name, err)
		}
		log.Println("run complete:", name)
	}
}

func watch(root, pattern string, f func()) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}
	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if matched, err := filepath.Match(pattern, info.Name()); err != nil {
			log.Fatal(err)
		} else if !matched {
			return nil
		}
		err = watcher.Add(path)
		if err != nil {
			log.Fatal(err)
		}
		return nil
	})
	log.Println("watching", pattern, "in", root)
	wait := time.Now()
	go func() {
		for {
			select {
			case event := <-watcher.Events:
				if wait.After(time.Now()) {
					continue
				}
				if event.Op&fsnotify.Write == fsnotify.Write {
					f()
					wait = time.Now().Add(time.Second * 2)
				}
			case err := <-watcher.Errors:
				log.Println("error:", err)
			}
		}
	}()
}

//go:generate esc -o server/static.go -pkg server -prefix server server/static/index.html server/static/css server/static/fonts server/static/js
<|endoftext|>


<commit_before>package main

import (
    "os"
    "fmt"
    "strings"
    "bytes"
    "net/http"
    "log"
    "io/ioutil"
    "encoding/json"
)

func HandleToggles(res http.ResponseWriter, req *http.Request) {
    if req.URL.Path != "/" {
        res.WriteHeader(http.StatusNotFound)
        fmt.Fprint(res, "404 page not found")
        return
    }

    config := getConfig()
    var buffer bytes.Buffer

    for _,Feature := range config.Features {
        var Toggle bool;
        if Feature.Persistent {
            cookie, err := req.Cookie("toogles-" + Feature.Name)

            if err == nil {
                Toggle = cookie.Value == "1"
            } else {
                Toggle = Feature.Toggle(req)

                Value := "0"
                if Toggle {
                    Value = "1"
                }
                cookie := http.Cookie{
                    Name: "toogles-" + Feature.Name,
                    Value: Value,
                    MaxAge: Feature.Expire,
                }
                http.SetCookie(res, &cookie)
            }
        } else {
            Toggle = Feature.Toggle(req)
        }

        if Toggle {
            buffer.WriteString(Feature.Name)
            buffer.WriteString(",")
        }
    }

    featuresString := strings.TrimRight(buffer.String(), ",")

    if len(featuresString) == 0 {
        fmt.Fprintf(res, "[]")
        return
    }

    features, err := json.Marshal(strings.Split(featuresString, ","))

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

    res.Header().Set("Content-Type", "application/json")
    res.Header().Set("Access-Control-Allow-Origin", "*")

    result := string(features[:])
    fmt.Fprintf(res, result)

    StatsIncrementConnections()
}

func isAuthed(req *http.Request) bool {
    apiKey := os.Getenv("API_KEY")
    Query := req.URL.Query()

    if apiKey == "" {
        log.Print("API_KEY not set, not allowed to update features")
        return false
    }

    return apiKey == Query.Get("key")
}

func HandleFeatures(res http.ResponseWriter, req *http.Request) {
    if isAuthed(req) == false {
        res.WriteHeader(http.StatusUnauthorized)
        fmt.Fprint(res, "")

        return
    }
    config := getConfig()
    if req.Method == http.MethodGet {
        configBytes, _ := json.Marshal(config)
        configJson := string(configBytes[:])

        res.Header().Set("Content-Type", "application/json")
        res.Header().Set("Access-Control-Allow-Origin", "*")
        fmt.Fprintf(res, configJson)
    } else if req.Method == http.MethodPost {
        body, err := ioutil.ReadAll(req.Body)
        if err != nil {
            log.Fatal(err)
        }
        log.Print("Setting features")
        log.Print(string(body))
        setConfigString(string(body))

        saveConfigToRedis()
    }
}

func HandleFeature(res http.ResponseWriter, req *http.Request) {
    if isAuthed(req) == false {
        res.WriteHeader(http.StatusUnauthorized)
        fmt.Fprint(res, "")

        return
    }

    config := getConfig()
    if req.Method == http.MethodGet || req.Method == http.MethodPut {
        Query := req.URL.Query()
        name := Query.Get("name")

        if name == "" {
            res.WriteHeader(http.StatusNotFound)
            fmt.Fprint(res, "")

            return
        }

        var feature Feature
        var featureIndex int

        for i, _feature := range config.Features {
            if _feature.Name == name {
                feature = _feature
                featureIndex = i
                break
            }
        }

        if &feature == nil {
            res.WriteHeader(http.StatusNotFound)
            fmt.Fprint(res, "")

            return
        }

        if req.Method == http.MethodPut {
            body, err := ioutil.ReadAll(req.Body)
            if err != nil {
                log.Fatal(err)
            }

            if err := json.Unmarshal(body, &feature); err != nil {
                log.Fatal(err)
            }
        }

        config.Features[featureIndex] = feature

        saveConfigToRedis()

        res.Header().Set("Content-Type", "application/json")
        res.Header().Set("Access-Control-Allow-Origin", "*")

        featureBytes, _ := json.Marshal(feature)
        fmt.Fprintf(res, string(featureBytes[:]))
    } else if req.Method == http.MethodPost {
        body, err := ioutil.ReadAll(req.Body)
        if err != nil {
            log.Fatal(err)
        }
        var feature Feature
        if err := json.Unmarshal(body, &feature); err != nil {
            log.Fatal(err)
        }

        var _feature *Feature = nil
        for _, f := range config.Features {
            if f.Name == feature.Name {
                _feature = &f
                break
            }
        }

        if _feature != nil {
            res.WriteHeader(http.StatusConflict)
            fmt.Fprint(res, "Feature with the same name already exists: " + _feature.Name)

            return
        }

        config.Features = append(config.Features, feature)

        setConfig(config)
        saveConfigToRedis()

        res.Header().Set("Content-Type", "application/json")
        res.Header().Set("Access-Control-Allow-Origin", "*")

        featureBytes, _ := json.Marshal(feature)
        fmt.Fprintf(res, string(featureBytes[:]))
    }

    fmt.Fprint(res, "")
}

func HandleHealthCheck(res http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(res, "")
}

func HandleStats(res http.ResponseWriter, req *http.Request) {
    stats = GetStats()

    statsBytes, _ := json.Marshal(stats)
    res.Header().Set("Content-Type", "application/json")
    res.Header().Set("Access-Control-Allow-Origin", "*")
    fmt.Fprintf(res, string(statsBytes[:]))
}

func main() {
    /*shareStrategy := ShareStrategy{
        Share: 50,
    }
    dummyFeature1 := Feature{
        Name: "half-n-half",
        Persistent: true,
        ShareStrategy: &shareStrategy,
    }

    firstStrategy := FirstStrategy{
        First: 3,
    }
    dummyFeature2 := Feature{
        Name: "first-users",
        Persistent: true,
        FirstStrategy: &firstStrategy,
    }

    userStrategy := QueryStrategy{
        Key: "user-id",
        Values: []string{"1234", "5678"},
    }
    dummyFeature3 := Feature{
        Name: "users-ids",
        Persistent: false,
        QueryStrategy: &userStrategy,
    }

    config := Configuration{
        Features: []Feature {
            dummyFeature1,
            dummyFeature2,
            dummyFeature3,
        },
    }

    setConfig(config)*/

    loadConfigFromRedis()

    http.HandleFunc("/", HandleToggles)
    http.HandleFunc("/stats", HandleStats)
    http.HandleFunc("/health-check", HandleHealthCheck)
    http.HandleFunc("/features", HandleFeatures)
    http.HandleFunc("/feature", HandleFeature)

    Port := os.Getenv("PORT")
    if Port == "" {
        Port = "8080"
    }
    log.Printf("App is listening on port: %s", Port)

    log.Fatal(http.ListenAndServe(":" + Port, nil))
}<commit_msg>Use servermux<commit_after>package main

import (
    "os"
    "fmt"
    "strings"
    "bytes"
    "net/http"
    "log"
    "io/ioutil"
    "encoding/json"
)

func HandlerNotFound(res http.ResponseWriter, req *http.Request) {
    res.WriteHeader(http.StatusNotFound)
    fmt.Fprint(res, "404 page not found")
}

func HandleToggles(res http.ResponseWriter, req *http.Request) {
    if req.URL.Path != "/" {
        HandlerNotFound(res, req)
        return
    }

    config := getConfig()
    var buffer bytes.Buffer

    for _,Feature := range config.Features {
        var Toggle bool;
        if Feature.Persistent {
            cookie, err := req.Cookie("toogles-" + Feature.Name)

            if err == nil {
                Toggle = cookie.Value == "1"
            } else {
                Toggle = Feature.Toggle(req)

                Value := "0"
                if Toggle {
                    Value = "1"
                }
                cookie := http.Cookie{
                    Name: "toogles-" + Feature.Name,
                    Value: Value,
                    MaxAge: Feature.Expire,
                }
                http.SetCookie(res, &cookie)
            }
        } else {
            Toggle = Feature.Toggle(req)
        }

        if Toggle {
            buffer.WriteString(Feature.Name)
            buffer.WriteString(",")
        }
    }

    featuresString := strings.TrimRight(buffer.String(), ",")

    if len(featuresString) == 0 {
        fmt.Fprintf(res, "[]")
        return
    }

    features, err := json.Marshal(strings.Split(featuresString, ","))

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

    res.Header().Set("Content-Type", "application/json")
    res.Header().Set("Access-Control-Allow-Origin", "*")

    result := string(features[:])
    fmt.Fprintf(res, result)

    StatsIncrementConnections()
}

func isAuthed(req *http.Request) bool {
    apiKey := os.Getenv("API_KEY")
    Query := req.URL.Query()

    if apiKey == "" {
        log.Print("API_KEY not set, not allowed to update features")
        return false
    }

    return apiKey == Query.Get("key")
}

func HandleFeatures(res http.ResponseWriter, req *http.Request) {
    if isAuthed(req) == false {
        res.WriteHeader(http.StatusUnauthorized)
        fmt.Fprint(res, "")

        return
    }
    config := getConfig()
    if req.Method == http.MethodGet {
        configBytes, _ := json.Marshal(config)
        configJson := string(configBytes[:])

        res.Header().Set("Content-Type", "application/json")
        res.Header().Set("Access-Control-Allow-Origin", "*")
        fmt.Fprintf(res, configJson)
    } else if req.Method == http.MethodPost {
        body, err := ioutil.ReadAll(req.Body)
        if err != nil {
            log.Fatal(err)
        }
        log.Print("Setting features")
        log.Print(string(body))
        setConfigString(string(body))

        saveConfigToRedis()
    }
}

func HandleFeature(res http.ResponseWriter, req *http.Request) {
    if isAuthed(req) == false {
        res.WriteHeader(http.StatusUnauthorized)
        fmt.Fprint(res, "")

        return
    }

    config := getConfig()
    if req.Method == http.MethodGet || req.Method == http.MethodPut {
        Query := req.URL.Query()
        name := Query.Get("name")

        if name == "" {
            res.WriteHeader(http.StatusNotFound)
            fmt.Fprint(res, "")

            return
        }

        var feature Feature
        var featureIndex int

        for i, _feature := range config.Features {
            if _feature.Name == name {
                feature = _feature
                featureIndex = i
                break
            }
        }

        if &feature == nil {
            res.WriteHeader(http.StatusNotFound)
            fmt.Fprint(res, "")

            return
        }

        if req.Method == http.MethodPut {
            body, err := ioutil.ReadAll(req.Body)
            if err != nil {
                log.Fatal(err)
            }

            if err := json.Unmarshal(body, &feature); err != nil {
                log.Fatal(err)
            }
        }

        config.Features[featureIndex] = feature

        saveConfigToRedis()

        res.Header().Set("Content-Type", "application/json")
        res.Header().Set("Access-Control-Allow-Origin", "*")

        featureBytes, _ := json.Marshal(feature)
        fmt.Fprintf(res, string(featureBytes[:]))
    } else if req.Method == http.MethodPost {
        body, err := ioutil.ReadAll(req.Body)
        if err != nil {
            log.Fatal(err)
        }
        var feature Feature
        if err := json.Unmarshal(body, &feature); err != nil {
            log.Fatal(err)
        }

        var _feature *Feature = nil
        for _, f := range config.Features {
            if f.Name == feature.Name {
                _feature = &f
                break
            }
        }

        if _feature != nil {
            res.WriteHeader(http.StatusConflict)
            fmt.Fprint(res, "Feature with the same name already exists: " + _feature.Name)

            return
        }

        config.Features = append(config.Features, feature)

        setConfig(config)
        saveConfigToRedis()

        res.Header().Set("Content-Type", "application/json")
        res.Header().Set("Access-Control-Allow-Origin", "*")

        featureBytes, _ := json.Marshal(feature)
        fmt.Fprintf(res, string(featureBytes[:]))
    }

    fmt.Fprint(res, "")
}

func HandleHealthCheck(res http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(res, "")
}

func HandleStats(res http.ResponseWriter, req *http.Request) {
    stats = GetStats()

    statsBytes, _ := json.Marshal(stats)
    res.Header().Set("Content-Type", "application/json")
    res.Header().Set("Access-Control-Allow-Origin", "*")
    fmt.Fprintf(res, string(statsBytes[:]))
}

var mux map[string]func(http.ResponseWriter, *http.Request)
type myHandler struct{}

func main() {
    /*shareStrategy := ShareStrategy{
        Share: 50,
    }
    dummyFeature1 := Feature{
        Name: "half-n-half",
        Persistent: true,
        ShareStrategy: &shareStrategy,
    }

    firstStrategy := FirstStrategy{
        First: 3,
    }
    dummyFeature2 := Feature{
        Name: "first-users",
        Persistent: true,
        FirstStrategy: &firstStrategy,
    }

    userStrategy := QueryStrategy{
        Key: "user-id",
        Values: []string{"1234", "5678"},
    }
    dummyFeature3 := Feature{
        Name: "users-ids",
        Persistent: false,
        QueryStrategy: &userStrategy,
    }

    config := Configuration{
        Features: []Feature {
            dummyFeature1,
            dummyFeature2,
            dummyFeature3,
        },
    }

    setConfig(config)*/

    loadConfigFromRedis()

    Port := os.Getenv("PORT")
    if Port == "" {
        Port = "8080"
    }
    log.Printf("App is listening on port: %s", Port)

    h := http.NewServeMux()
    h.HandleFunc("/", HandleToggles)
    h.HandleFunc("/stats", HandleStats)
    h.HandleFunc("/health-check", HandleHealthCheck)
    h.HandleFunc("/features", HandleFeatures)
    h.HandleFunc("/feature", HandleFeature)

    http.ListenAndServe(":" + Port, h)
}
<|endoftext|>


<commit_before>package main

import (
	"./dialects"
	"./payload"
	"crypto/md5"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"flag"
	"fmt"
	"github.com/golang/protobuf/proto"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"syscall"
	"time"
)

var config *Config
var jobQueue chan *Job
var storageClient dialects.StorageClient
var verbose bool
var isTerminating = false
var dispatcher *Dispatcher
var JobQueue chan Job

// Return the request's signature
func GetSignature(body []byte, time string) string {
	bodyHash := md5.New()
	io.WriteString(bodyHash, string(body[:]))

	requestHash := sha256.New()
	io.WriteString(requestHash, time)
	io.WriteString(requestHash, "|")
	io.WriteString(requestHash, hex.EncodeToString(bodyHash.Sum(nil)))
	io.WriteString(requestHash, "|")
	io.WriteString(requestHash, config.SharedSecret)

	return base64.StdEncoding.EncodeToString(requestHash.Sum(nil))
}

// Controller for `/api/v1/track`
func TrackHandler(w http.ResponseWriter, r *http.Request) {
	// Do not accept new events while the server is shutting down.
	if isTerminating {
		if verbose {
			fmt.Println("Request was dropped: the server is currenly shutting down")
			log.Println("Request was dropped: the server is currenly shutting down")
		}
		w.WriteHeader(http.StatusServiceUnavailable)
		return
	}
	// Ignore not POST messages.
	if r.Method != "POST" {
		if verbose {
			log.Println("Request was dropped: the sending method was not POST")
		}
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	// If the client did not send time, we ignore
	if r.Header.Get("X-Hamustro-Time") == "" {
		if verbose {
			log.Println("Request was dropped: `X-Hamustro-Time` header is missing")
		}
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	// If the client did not send signature of the message, we ignore
	if r.Header.Get("X-Hamustro-Signature") == "" {
		if verbose {
			log.Println("Request was dropped: `X-Hamustro-Signature` header is missing")
		}
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	// Read the requests body into a variable.
	body, _ := ioutil.ReadAll(r.Body)

	// Calculate the request's signature
	if r.Header.Get("X-Hamustro-Signature") != GetSignature(body, r.Header.Get("X-Hamustro-Time")) {
		if verbose {
			log.Println("Request was dropped: `X-Hamustro-Signature` is not valid")
		}
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	// Read the body into protobuf decoding.
	collection := &payload.Collection{}
	if err := proto.Unmarshal(body, collection); err != nil {
		log.Printf("Protobuf unmarshal is failed: %s", err.Error())
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// Creates a Job and put into the JobQueue for processing.
	for _, payload := range collection.Payloads {
		job := Job{dialects.NewEvent(collection, payload), 1}
		jobQueue <- &job
	}

	// Returns with 200.
	w.WriteHeader(http.StatusOK)
}

// Runs before the program starts
func init() {
	// Parse the CLI's attributes
	var filename = flag.String("config", "", "configuration `file` for the dialect")
	flag.BoolVar(&verbose, "verbose", false, "verbose mode for debugging")
	flag.Parse()

	if *filename == "" {
		flag.PrintDefaults()
		os.Exit(1)
	}

	// Read and parse the configuration file
	config = NewConfig(*filename)
	dialect, err := config.DialectConfig()
	if err != nil {
		log.Fatalf("Loading dialect configuration is failed: %s", err.Error())
	}
	if !dialect.IsValid() {
		log.Fatalf("Dialect configuration is incorrect or incomplete: %s", err.Error())
	}

	// Construct the dialect's client
	storageClient, err = dialect.NewClient()
	if err != nil {
		log.Fatalf("Client initialization is failed: %s", err.Error())
	}

	// Create the background workers
	jobQueue = make(chan *Job, config.GetMaxQueueSize())
	dispatcher = NewDispatcher(config.GetMaxWorkerSize(), config.BufferSize, config.SpreadBufferSize)
	dispatcher.Run()

	// Capture SIGINT and SIGTERM events to finish the ongoing work
	signalChannel := make(chan os.Signal, 1)
	signal.Notify(signalChannel, os.Interrupt)
	signal.Notify(signalChannel, syscall.SIGTERM)
	go func() {
		<-signalChannel
		cleanup()
		os.Exit(1)
	}()
}

// Web server
func main() {
	// Set the log's output
	if config.LogFile != "" {
		logFile, err := os.OpenFile(config.LogFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("Can't open logfile %s", err.Error())
		}
		defer logFile.Close()
		log.SetOutput(logFile)
	}

	// Start the server
	log.Printf("Starting server at %s", config.GetAddress())
	http.HandleFunc("/api/v1/track", TrackHandler)
	http.ListenAndServe(config.GetAddress(), nil)
}

// Runs after the server was shut down
func cleanup() {
	// Do not accept new requests
	isTerminating = true
	log.Println("Shutting down server ...")

	// Set a timeout interval to force stop (avoid hanging out)
	go func() {
		c := time.Tick(90 * time.Second)
		for range c {
			log.Fatal("Server shut down is taking too long, force quit immediately.")
		}
	}()

	// Try to stop every worker
	dispatcher.Stop()
}
<commit_msg>remove not necessary line<commit_after>package main

import (
	"./dialects"
	"./payload"
	"crypto/md5"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"flag"
	"fmt"
	"github.com/golang/protobuf/proto"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"syscall"
	"time"
)

var config *Config
var jobQueue chan *Job
var storageClient dialects.StorageClient
var verbose bool
var isTerminating = false
var dispatcher *Dispatcher
var JobQueue chan Job

// Return the request's signature
func GetSignature(body []byte, time string) string {
	bodyHash := md5.New()
	io.WriteString(bodyHash, string(body[:]))

	requestHash := sha256.New()
	io.WriteString(requestHash, time)
	io.WriteString(requestHash, "|")
	io.WriteString(requestHash, hex.EncodeToString(bodyHash.Sum(nil)))
	io.WriteString(requestHash, "|")
	io.WriteString(requestHash, config.SharedSecret)

	return base64.StdEncoding.EncodeToString(requestHash.Sum(nil))
}

// Controller for `/api/v1/track`
func TrackHandler(w http.ResponseWriter, r *http.Request) {
	// Do not accept new events while the server is shutting down.
	if isTerminating {
		if verbose {
			log.Println("Request was dropped: the server is currenly shutting down")
		}
		w.WriteHeader(http.StatusServiceUnavailable)
		return
	}
	// Ignore not POST messages.
	if r.Method != "POST" {
		if verbose {
			log.Println("Request was dropped: the sending method was not POST")
		}
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	// If the client did not send time, we ignore
	if r.Header.Get("X-Hamustro-Time") == "" {
		if verbose {
			log.Println("Request was dropped: `X-Hamustro-Time` header is missing")
		}
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	// If the client did not send signature of the message, we ignore
	if r.Header.Get("X-Hamustro-Signature") == "" {
		if verbose {
			log.Println("Request was dropped: `X-Hamustro-Signature` header is missing")
		}
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	// Read the requests body into a variable.
	body, _ := ioutil.ReadAll(r.Body)

	// Calculate the request's signature
	if r.Header.Get("X-Hamustro-Signature") != GetSignature(body, r.Header.Get("X-Hamustro-Time")) {
		if verbose {
			log.Println("Request was dropped: `X-Hamustro-Signature` is not valid")
		}
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	// Read the body into protobuf decoding.
	collection := &payload.Collection{}
	if err := proto.Unmarshal(body, collection); err != nil {
		log.Printf("Protobuf unmarshal is failed: %s", err.Error())
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// Creates a Job and put into the JobQueue for processing.
	for _, payload := range collection.Payloads {
		job := Job{dialects.NewEvent(collection, payload), 1}
		jobQueue <- &job
	}

	// Returns with 200.
	w.WriteHeader(http.StatusOK)
}

// Runs before the program starts
func init() {
	// Parse the CLI's attributes
	var filename = flag.String("config", "", "configuration `file` for the dialect")
	flag.BoolVar(&verbose, "verbose", false, "verbose mode for debugging")
	flag.Parse()

	if *filename == "" {
		flag.PrintDefaults()
		os.Exit(1)
	}

	// Read and parse the configuration file
	config = NewConfig(*filename)
	dialect, err := config.DialectConfig()
	if err != nil {
		log.Fatalf("Loading dialect configuration is failed: %s", err.Error())
	}
	if !dialect.IsValid() {
		log.Fatalf("Dialect configuration is incorrect or incomplete: %s", err.Error())
	}

	// Construct the dialect's client
	storageClient, err = dialect.NewClient()
	if err != nil {
		log.Fatalf("Client initialization is failed: %s", err.Error())
	}

	// Create the background workers
	jobQueue = make(chan *Job, config.GetMaxQueueSize())
	dispatcher = NewDispatcher(config.GetMaxWorkerSize(), config.BufferSize, config.SpreadBufferSize)
	dispatcher.Run()

	// Capture SIGINT and SIGTERM events to finish the ongoing work
	signalChannel := make(chan os.Signal, 1)
	signal.Notify(signalChannel, os.Interrupt)
	signal.Notify(signalChannel, syscall.SIGTERM)
	go func() {
		<-signalChannel
		cleanup()
		os.Exit(1)
	}()
}

// Web server
func main() {
	// Set the log's output
	if config.LogFile != "" {
		logFile, err := os.OpenFile(config.LogFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("Can't open logfile %s", err.Error())
		}
		defer logFile.Close()
		log.SetOutput(logFile)
	}

	// Start the server
	log.Printf("Starting server at %s", config.GetAddress())
	http.HandleFunc("/api/v1/track", TrackHandler)
	http.ListenAndServe(config.GetAddress(), nil)
}

// Runs after the server was shut down
func cleanup() {
	// Do not accept new requests
	isTerminating = true
	log.Println("Shutting down server ...")

	// Set a timeout interval to force stop (avoid hanging out)
	go func() {
		c := time.Tick(90 * time.Second)
		for range c {
			log.Fatal("Server shut down is taking too long, force quit immediately.")
		}
	}()

	// Try to stop every worker
	dispatcher.Stop()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"

	"github.com/googollee/go-socket.io"

	"github.com/jesusrmoreno/toto/domain"
	"github.com/jesusrmoreno/toto/implementations"
	"github.com/jesusrmoreno/toto/utils"

	"github.com/BurntSushi/toml"
	"github.com/jesusrmoreno/sad-squid"
)

// Events that are exposed to the client
const (
	groupAssignment = "group-assignment"
	roomMessage     = "room-message"
	leaveGroup      = "leave-group"
	joinGroup       = "join-group"
	joinGame        = "join-game"
	getPeers        = "get-peers"
	makeMove        = "make-move"
	moveMade        = "move-made"
	inQueue         = "in-queue"

	serverError = "server-error"
	clientError = "client-error"
)

// Response ...
type Response struct {
	Kind string                 `json:"kind"`
	Data map[string]interface{} `json:"data"`
}

// Method for creating errors more quickly.
func errorResponse(kind, err string) Response {
	d := map[string]interface{}{}
	d["error"] = err
	r := Response{
		Kind: kind,
		Data: d,
	}
	return r
}

// GamesInfo serves to store the metadata for different games
type GamesInfo struct {
	// These must be thread safe so we use the ConcurrentMap types
	TurnMap *utils.ConcurrentStringIntMap
	// Maps the player id to the room
	RoomMap *utils.ConcurrentStringMap
}

// ReadGameFiles reads the provided directory for files that conform to the
// game struct definition, these must be json files, and loads them into our
// game map.
func ReadGameFiles(gameDir string) (domain.GameMap, error) {
	files, err := filepath.Glob(gameDir + "/*.toml")
	gm := domain.GameMap{}
	if err != nil {
		return nil, err
	}
	for _, f := range files {
		raw, err := os.Open(f)
		defer raw.Close()
		if err != nil {
			return nil, err
		}
		r := io.Reader(raw)
		dummy := domain.Game{}
		if meta, err := toml.DecodeReader(r, &dummy); err != nil {
			log.Println(meta)
			return nil, errors.New("Invalid configuration in file: " + f)
		}
		g := domain.Game{
			Players: dummy.Players,
			Title:   dummy.Title,
			UUID:    dummy.UUID,
			Lobby: impl.Lobby{
				// We implement our lobby as a buffered channel that takes the number of
				// players specified in the config file for the game and makes that the
				// cap.
				Queue: make(chan domain.Player, dummy.Players),
			},
		}
		g.FileName = f
		if _, exists := gm[g.UUID]; exists {
			return nil, errors.New("uniqueKey conflict between: " + f + " and " + gm[g.UUID].FileName)
		}
		gm[g.UUID] = g
	}
	return gm, nil
}

// QueuePlayers adds players to the game's lobby to wait for a partner.
// Players are queued on a first come first serve basis.
func QueuePlayers(g domain.Game, p domain.Player) {
	data := map[string]interface{}{}
	data["message"] = "You are in the queue for game: " + g.Title
	r := Response{
		Kind: inQueue,
		Data: data,
	}
	p.Comm.Emit(inQueue, r)
	pq := g.Lobby
	pq.AddToQueue(p)
}

// GroupPlayers creates groups of players of size PlayerSize as defined in the
// game files.
func GroupPlayers(g domain.Game, server *socketio.Server, gi *GamesInfo) {
	pq := g.Lobby
	n := g.Players
	for {
		select {
		default:
			if g.Lobby.Size() >= n {
				team := []domain.Player{}
				// Generate a room id
				roomName := squid.GenerateSimpleID()
				for i := 0; i < n; i++ {
					p := pq.PopFromQueue()
					pID := p.Comm.Id()
					team = append(team, p)

					// Set the metadata
					gi.RoomMap.Set(pID, roomName)
					gi.TurnMap.Set(roomName+":"+pID, i)

					// Place the player in the created room.
					p.Comm.Join(roomName)

					// Create the response
					data := map[string]interface{}{}
					data["roomName"] = roomName
					data["turnNumber"] = i
					r := Response{
						Kind: inQueue,
						Data: data,
					}
					p.Comm.Emit(groupAssignment, r)
				}

				// Create the response
				data := map[string]interface{}{}
				data["message"] = fmt.Sprintf("Welcome to %s", roomName)
				r := Response{
					Kind: roomMessage,
					Data: data,
				}
				server.BroadcastTo(roomName, roomMessage, r)
			}
		}
	}
}

func main() {
	games, err := ReadGameFiles("./games")
	for key, game := range games {
		log.Println("Loaded:", key, "from", game.FileName)
	}
	if err != nil {
		log.Fatal(err)
	}

	server, err := socketio.NewServer(nil)
	if err != nil {
		log.Fatal(err)
	}

	info := GamesInfo{
		RoomMap: utils.NewConcurrentStringMap(),
		TurnMap: utils.NewConcurrentStringIntMap(),
	}
	for key := range games {
		go GroupPlayers(games[key], server, &info)
	}
	server.On("connection", func(so socketio.Socket) {
		// Makes it so that the player joins a room with his/her unique id.
		so.Join(so.Id())
		so.On(joinGame, func(req json.RawMessage) {
			m := map[string]string{}
			if err := json.Unmarshal(req, &m); err != nil {
				so.Emit(clientError, errorResponse(clientError, "Invalid JSON"))
			}
			gameID, exists := m["gameId"]
			if !exists {
				so.Emit(clientError, errorResponse(clientError, "Must include GameID"))
			}
			// If the player attempts to connect to a game we first have to make
			// sure that they are joining a game that is registered with our server.
			if g, exists := games[gameID]; exists {
				QueuePlayers(g, domain.Player{
					Comm: so,
				})
			} else {
				so.Emit(clientError, errorResponse(clientError, "Invalid GameID"))
			}
		})
		so.On(makeMove, func(move json.RawMessage) {
			room, exists := info.RoomMap.Get(so.Id())
			if exists {
				m := map[string]interface{}{}
				if err := json.Unmarshal(move, &m); err != nil {
					so.Emit(clientError, errorResponse(clientError, "Invalid JSON"))
				}
				turn, exists := info.TurnMap.Get(room + ":" + so.Id())
				if !exists {
					so.Emit(serverError, errorResponse(serverError, "No turn assigned"))
				}
				// Overwrites who's turn it is using the turn map assigned at join.
				m["madeBy"] = turn
				m["madeById"] = so.Id()
				r := Response{
					Kind: moveMade,
					Data: m,
				}
				so.BroadcastTo(room, moveMade, r)
			} else {
				so.Emit(serverError, errorResponse(serverError, "Not in any Room"))
			}
		})
	})

	port := os.Getenv("PORT")
	if port == "" {
		port = "3000"
	}

	http.Handle("/socket.io/", server)
	http.Handle("/", http.FileServer(http.Dir("./asset")))
	log.Println("Serving at localhost:" + port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}
<commit_msg>Added CLI<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"

	"github.com/googollee/go-socket.io"

	"github.com/jesusrmoreno/toto/domain"
	"github.com/jesusrmoreno/toto/implementations"
	"github.com/jesusrmoreno/toto/utils"

	"github.com/BurntSushi/toml"
	"github.com/jesusrmoreno/sad-squid"

	"github.com/codegangsta/cli"
)

const Version = "No Version Provided"

// Events that are exposed to the client
const (
	groupAssignment = "group-assignment"
	roomMessage     = "room-message"
	leaveGroup      = "leave-group"
	joinGroup       = "join-group"
	joinGame        = "join-game"
	getPeers        = "get-peers"
	makeMove        = "make-move"
	moveMade        = "move-made"
	inQueue         = "in-queue"

	serverError = "server-error"
	clientError = "client-error"
)

// Response ...
type Response struct {
	Kind string                 `json:"kind"`
	Data map[string]interface{} `json:"data"`
}

// Method for creating errors more quickly.
func errorResponse(kind, err string) Response {
	d := map[string]interface{}{}
	d["error"] = err
	r := Response{
		Kind: kind,
		Data: d,
	}
	return r
}

// GamesInfo serves to store the metadata for different games
type GamesInfo struct {
	// These must be thread safe so we use the ConcurrentMap types
	TurnMap *utils.ConcurrentStringIntMap
	// Maps the player id to the room
	RoomMap *utils.ConcurrentStringMap
}

// ReadGameFiles reads the provided directory for files that conform to the
// game struct definition, these must be json files, and loads them into our
// game map.
func ReadGameFiles(gameDir string) (domain.GameMap, error) {
	files, err := filepath.Glob(gameDir + "/*.toml")
	gm := domain.GameMap{}
	if err != nil {
		return nil, err
	}
	for _, f := range files {
		raw, err := os.Open(f)
		defer raw.Close()
		if err != nil {
			return nil, err
		}
		r := io.Reader(raw)
		dummy := domain.Game{}
		if meta, err := toml.DecodeReader(r, &dummy); err != nil {
			log.Println(meta)
			return nil, errors.New("Invalid configuration in file: " + f)
		}
		g := domain.Game{
			Players: dummy.Players,
			Title:   dummy.Title,
			UUID:    dummy.UUID,
			Lobby: impl.Lobby{
				// We implement our lobby as a buffered channel that takes the number of
				// players specified in the config file for the game and makes that the
				// cap.
				Queue: make(chan domain.Player, dummy.Players),
			},
		}
		g.FileName = f
		if _, exists := gm[g.UUID]; exists {
			return nil, errors.New("uniqueKey conflict between: " + f + " and " + gm[g.UUID].FileName)
		}
		gm[g.UUID] = g
	}
	return gm, nil
}

// QueuePlayers adds players to the game's lobby to wait for a partner.
// Players are queued on a first come first serve basis.
func QueuePlayers(g domain.Game, p domain.Player) {
	data := map[string]interface{}{}
	data["message"] = "You are in the queue for game: " + g.Title
	r := Response{
		Kind: inQueue,
		Data: data,
	}
	p.Comm.Emit(inQueue, r)
	pq := g.Lobby
	pq.AddToQueue(p)
}

// GroupPlayers creates groups of players of size PlayerSize as defined in the
// game files.
func GroupPlayers(g domain.Game, server *socketio.Server, gi *GamesInfo) {
	pq := g.Lobby
	n := g.Players
	for {
		select {
		default:
			if g.Lobby.Size() >= n {
				team := []domain.Player{}
				// Generate a room id
				roomName := squid.GenerateSimpleID()
				for i := 0; i < n; i++ {
					p := pq.PopFromQueue()
					pID := p.Comm.Id()
					team = append(team, p)

					// Set the metadata
					gi.RoomMap.Set(pID, roomName)
					gi.TurnMap.Set(roomName+":"+pID, i)

					// Place the player in the created room.
					p.Comm.Join(roomName)

					// Create the response
					data := map[string]interface{}{}
					data["roomName"] = roomName
					data["turnNumber"] = i
					r := Response{
						Kind: inQueue,
						Data: data,
					}
					p.Comm.Emit(groupAssignment, r)
				}

				// Create the response
				data := map[string]interface{}{}
				data["message"] = fmt.Sprintf("Welcome to %s", roomName)
				r := Response{
					Kind: roomMessage,
					Data: data,
				}
				server.BroadcastTo(roomName, roomMessage, r)
			}
		}
	}
}

// StartServer ...
func StartServer(c *cli.Context) {
	games, err := ReadGameFiles("./games")
	for key, game := range games {
		log.Println("Loaded:", key, "from", game.FileName)
	}
	if err != nil {
		log.Fatal(err)
	}

	server, err := socketio.NewServer(nil)
	if err != nil {
		log.Fatal(err)
	}

	info := GamesInfo{
		RoomMap: utils.NewConcurrentStringMap(),
		TurnMap: utils.NewConcurrentStringIntMap(),
	}
	for key := range games {
		go GroupPlayers(games[key], server, &info)
	}
	server.On("connection", func(so socketio.Socket) {
		// Makes it so that the player joins a room with his/her unique id.
		so.Join(so.Id())
		so.On(joinGame, func(req json.RawMessage) {
			m := map[string]string{}
			if err := json.Unmarshal(req, &m); err != nil {
				so.Emit(clientError, errorResponse(clientError, "Invalid JSON"))
			}
			gameID, exists := m["gameId"]
			if !exists {
				so.Emit(clientError, errorResponse(clientError, "Must include GameID"))
			}
			// If the player attempts to connect to a game we first have to make
			// sure that they are joining a game that is registered with our server.
			if g, exists := games[gameID]; exists {
				QueuePlayers(g, domain.Player{
					Comm: so,
				})
			} else {
				so.Emit(clientError, errorResponse(clientError, "Invalid GameID"))
			}
		})
		so.On(makeMove, func(move json.RawMessage) {
			room, exists := info.RoomMap.Get(so.Id())
			if exists {
				m := map[string]interface{}{}
				if err := json.Unmarshal(move, &m); err != nil {
					so.Emit(clientError, errorResponse(clientError, "Invalid JSON"))
				}
				turn, exists := info.TurnMap.Get(room + ":" + so.Id())
				if !exists {
					so.Emit(serverError, errorResponse(serverError, "No turn assigned"))
				}
				// Overwrites who's turn it is using the turn map assigned at join.
				m["madeBy"] = turn
				m["madeById"] = so.Id()
				r := Response{
					Kind: moveMade,
					Data: m,
				}
				so.BroadcastTo(room, moveMade, r)
			} else {
				so.Emit(serverError, errorResponse(serverError, "Not in any Room"))
			}
		})
	})

	port := c.String("port")
	http.Handle("/socket.io/", server)
	http.Handle("/", http.FileServer(http.Dir("./asset")))
	log.Println("Serving at localhost:" + port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

func main() {
	app := cli.NewApp()
	app.Name = "Toto"
	app.Usage = "a server for creating quick prototype websocket based games."
	app.Action = StartServer
	app.Version = Version
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "port, p",
			Value: "3000",
			Usage: "The port to run the server on",
		},
	}
	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"os"

	"github.com/codegangsta/cli"
)

const (
	appName    = "hranoprovod-cli"
	appUsage   = "Lifestyle tracker"
	appVersion = "2.0.0"
	appAuthor  = "aquilax"
	appEmail   = "aquilax@gmail.com"

	defaultDbFilename       = "food.yaml"
	defaultLogFilename      = "log.yaml"
	defaultResolverMaxDepth = 10
)

func main() {
	app := cli.NewApp()
	app.Name = appName
	app.Usage = appUsage
	app.Version = appVersion
	app.Author = appAuthor
	app.Email = appEmail
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "database, d",
			Value:  defaultDbFilename,
			Usage:  "database file name",
			EnvVar: "HR_DATABASE",
		},
		cli.StringFlag{
			Name:   "logfile, l",
			Value:  defaultLogFilename,
			Usage:  "log file name",
			EnvVar: "HR_LOGFILE",
		},
		cli.StringFlag{
			Name:   "config, c",
			Value:  GetDefaultFileName(),
			Usage:  "Configuration file",
			EnvVar: "HR_CONFIG",
		},
		cli.StringFlag{
			Name:   "date-format",
			Value:  "2006/01/02",
			Usage:  "Date format for parsing and printing dates",
			EnvVar: "HR_DATE_FORMAT",
		},
	}
	app.Commands = []cli.Command{
		{
			Name:      "register",
			ShortName: "reg",
			Usage:     "Shows the log register report",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "begin, b",
					Usage: "Beginning of period",
				},
				cli.StringFlag{
					Name:  "end, e",
					Usage: "End of period",
				},
				cli.StringFlag{
					Name:  "single-food, f",
					Usage: "Show only single element",
				},
				cli.StringFlag{
					Name:  "single-element, s",
					Usage: "Show only single element",
				},

				cli.BoolFlag{
					Name:  "csv",
					Usage: "Export as CSV",
				},
				cli.BoolFlag{
					Name:  "no-color",
					Usage: "Disable color output",
				},
				cli.BoolFlag{
					Name:  "no-totals",
					Usage: "Disable totals",
				},
				cli.BoolFlag{
					Name:  "unresolved",
					Usage: "Show unresolved elements only",
				},
				cli.IntFlag{
					Name:   "maxdepth",
					Value:  defaultResolverMaxDepth,
					Usage:  "resolve depth",
					EnvVar: "HR_MAXDEPTH",
				},
			},
			Action: func(c *cli.Context) {
				o := NewOptions()
				if err := o.Load(c); err != nil {
					handleExit(err)
				}
				handleExit(NewHranoprovod(o).Register())
			},
		},
		{
			Name:  "add",
			Usage: "Adds new item to the log",
			Action: func(c *cli.Context) {
				o := NewOptions()
				if err := o.Load(c); err != nil {
					handleExit(err)
				}
				handleExit(NewHranoprovod(o).Add(c.Args().First(), c.Args().Get(1)))
			},
		},
		{
			Name:  "api",
			Usage: "Service API commands",
			Subcommands: []cli.Command{
				{
					Name:  "search",
					Usage: "Search for food online",
					Action: func(c *cli.Context) {
						o := NewOptions()
						if err := o.Load(c); err != nil {
							handleExit(err)
						}
						handleExit(NewHranoprovod(o).Search(c.Args().First()))
					},
				},
			},
		},
		{
			Name:  "lint",
			Usage: "Lints file",
			Action: func(c *cli.Context) {
				o := NewOptions()
				if err := o.Load(c); err != nil {
					handleExit(err)
				}
				handleExit(NewHranoprovod(o).Lint(c.Args().First()))
			},
		},
	}
	app.Run(os.Args)
}

func handleExit(err error) {
	if err != nil {
		println(err.Error())
		os.Exit(1)
	}
}
<commit_msg>Bumped version<commit_after>package main

import (
	"os"

	"github.com/codegangsta/cli"
)

const (
	appName    = "hranoprovod-cli"
	appUsage   = "Lifestyle tracker"
	appVersion = "2.0.1"
	appAuthor  = "aquilax"
	appEmail   = "aquilax@gmail.com"

	defaultDbFilename       = "food.yaml"
	defaultLogFilename      = "log.yaml"
	defaultResolverMaxDepth = 10
)

func main() {
	app := cli.NewApp()
	app.Name = appName
	app.Usage = appUsage
	app.Version = appVersion
	app.Author = appAuthor
	app.Email = appEmail
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "database, d",
			Value:  defaultDbFilename,
			Usage:  "database file name",
			EnvVar: "HR_DATABASE",
		},
		cli.StringFlag{
			Name:   "logfile, l",
			Value:  defaultLogFilename,
			Usage:  "log file name",
			EnvVar: "HR_LOGFILE",
		},
		cli.StringFlag{
			Name:   "config, c",
			Value:  GetDefaultFileName(),
			Usage:  "Configuration file",
			EnvVar: "HR_CONFIG",
		},
		cli.StringFlag{
			Name:   "date-format",
			Value:  "2006/01/02",
			Usage:  "Date format for parsing and printing dates",
			EnvVar: "HR_DATE_FORMAT",
		},
	}
	app.Commands = []cli.Command{
		{
			Name:      "register",
			ShortName: "reg",
			Usage:     "Shows the log register report",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "begin, b",
					Usage: "Beginning of period",
				},
				cli.StringFlag{
					Name:  "end, e",
					Usage: "End of period",
				},
				cli.StringFlag{
					Name:  "single-food, f",
					Usage: "Show only single element",
				},
				cli.StringFlag{
					Name:  "single-element, s",
					Usage: "Show only single element",
				},

				cli.BoolFlag{
					Name:  "csv",
					Usage: "Export as CSV",
				},
				cli.BoolFlag{
					Name:  "no-color",
					Usage: "Disable color output",
				},
				cli.BoolFlag{
					Name:  "no-totals",
					Usage: "Disable totals",
				},
				cli.BoolFlag{
					Name:  "unresolved",
					Usage: "Show unresolved elements only",
				},
				cli.IntFlag{
					Name:   "maxdepth",
					Value:  defaultResolverMaxDepth,
					Usage:  "resolve depth",
					EnvVar: "HR_MAXDEPTH",
				},
			},
			Action: func(c *cli.Context) {
				o := NewOptions()
				if err := o.Load(c); err != nil {
					handleExit(err)
				}
				handleExit(NewHranoprovod(o).Register())
			},
		},
		{
			Name:  "add",
			Usage: "Adds new item to the log",
			Action: func(c *cli.Context) {
				o := NewOptions()
				if err := o.Load(c); err != nil {
					handleExit(err)
				}
				handleExit(NewHranoprovod(o).Add(c.Args().First(), c.Args().Get(1)))
			},
		},
		{
			Name:  "api",
			Usage: "Service API commands",
			Subcommands: []cli.Command{
				{
					Name:  "search",
					Usage: "Search for food online",
					Action: func(c *cli.Context) {
						o := NewOptions()
						if err := o.Load(c); err != nil {
							handleExit(err)
						}
						handleExit(NewHranoprovod(o).Search(c.Args().First()))
					},
				},
			},
		},
		{
			Name:  "lint",
			Usage: "Lints file",
			Action: func(c *cli.Context) {
				o := NewOptions()
				if err := o.Load(c); err != nil {
					handleExit(err)
				}
				handleExit(NewHranoprovod(o).Lint(c.Args().First()))
			},
		},
	}
	app.Run(os.Args)
}

func handleExit(err error) {
	if err != nil {
		println(err.Error())
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	neural "github.com/poseidon4o/go-neural/src/neural"
	flappy "github.com/poseidon4o/go-neural/src/problems/flappy"
	mario "github.com/poseidon4o/go-neural/src/problems/mario"
	util "github.com/poseidon4o/go-neural/src/util"
	sdl "github.com/veandco/go-sdl2/sdl"
	"math"
	"runtime"
	"time"
)

type DrawableProblem interface {
	SetDrawRectCb(cb func(pos, size *util.Vector, color uint32))
	LogicTick(dt float64)
	DrawTick()
	StatsReportTick()
	Complete() float64
	Done() bool
	Jump()
	Move(int)
	SaveNetsToFile()
	LoadNetsFromFile()
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	fmt.Println("Controls:")
	fmt.Println("end:\tfurthest action in the level")
	fmt.Println("home:\tmove back to level begining")
	fmt.Println("left:\tmove screen to the left")
	fmt.Println("right:\tmove screen to the right")
	fmt.Println("1:\tswitch to flappy")
	fmt.Println("2:\tswitch to mario")
	fmt.Println("s:\tsave all nets to file")
	fmt.Println("f:\tturn on fast-forward mode")
	fmt.Println("p:\ttake screenshot in format \"SS-#d.bmp\", where #d is [0,inf)")
	fmt.Println("enter:\tcycle trough mario/flappy")
	fmt.Println("esc:\teixt")
	fmt.Println("")

	doDraw := true
	doDev := false
	doFastForward := false

	W := 1300
	H := 700
	LVL_W := W * 50

	var FPS float64 = 60.0
	FRAME_TIME_MS := 1000 / FPS
	FRAME_TIME := time.Millisecond * time.Duration(FRAME_TIME_MS)

	sdl.Init(sdl.INIT_EVERYTHING)

	window, err := sdl.CreateWindow("test", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED,
		int(W), int(H), sdl.WINDOW_SHOWN)
	if err != nil {
		panic(err)
	}
	defer window.Destroy()

	surface, err := window.GetSurface()
	if err != nil {
		panic(err)
	}

	clearRect := sdl.Rect{0, 0, int32(W), int32(H)}
	surface.FillRect(&clearRect, 0xffffffff)

	figCount := 1000
	if doDev {
		figCount = 1
	}

	offset := 0
	visible := func(pos, size *util.Vector) bool {

		// r1 := sdl.Rect{int32(pos.X), int32(pos.Y), int32(pos.X + size.X), int32(pos.Y + size.Y)}
		// r2 := sdl.Rect{int32(offset), 0, int32(offset + W), int32(H)}
		// return !(r2.X > r1.W || r2.W < r1.X || r2.Y > r1.H || r2.H < r1.Y)

		// so beautiful
		return !(float64(offset) > pos.X+size.X ||
			float64(offset+W) < pos.X ||
			0 > pos.Y+size.Y ||
			float64(H) < pos.Y)
	}

	toScreen := func(pos, size *util.Vector) *sdl.Rect {
		return &sdl.Rect{
			X: int32(pos.X - float64(offset)),
			Y: int32(pos.Y),
			W: int32(size.X),
			H: int32(size.Y),
		}
	}

	drawCb := func(pos, size *util.Vector, color uint32) {
		if visible(pos, size) {
			surface.FillRect(toScreen(pos, size), color)
		}
	}

	fl := flappy.NewFlappy(figCount, util.NewVector(float64(LVL_W), float64(H)))
	mr := mario.NewMario(figCount, util.NewVector(float64(LVL_W), float64(250)))

	mr.LoadNetsFromFile()
	fl.LoadNetsFromFile()

	fl.SetDrawRectCb(drawCb)
	mr.SetDrawRectCb(drawCb)

	imageCounter := 0

	var game DrawableProblem = fl

	step := 65

	frame := 0
	var averageFrameTime float64 = FRAME_TIME_MS * 1000000 // in nanosec
	start := time.Now()
	lastDrawTime := time.Now()
	lastReportTime := time.Now()
	for {
		start = time.Now()

		if doDraw {
			if doFastForward {
				if !lastDrawTime.Add(FRAME_TIME).After(start) {
					lastDrawTime = start
					window.UpdateSurface()
					surface.FillRect(&clearRect, 0xffffffff)
					game.DrawTick()
				}
			} else {
				lastDrawTime = start
				window.UpdateSurface()
				surface.FillRect(&clearRect, 0xffffffff)
				game.DrawTick()
			}

		} else if frame%10 == 0 {
			// update only 10% of the frames
			window.UpdateSurface()
		}

		game.LogicTick(1 / FPS)

		stop := false
		for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
			switch t := event.(type) {
			case *sdl.QuitEvent:
				stop = true
			case *sdl.KeyDownEvent:
				switch t.Keysym.Sym {
				case sdl.K_LEFT:
					if doDev {
						game.Move(-1)
					} else {
						offset = int(math.Max(0, float64(offset-step)))
					}
				case sdl.K_RIGHT:
					if doDev {
						game.Move(1)
					} else {
						offset = int(math.Min(float64(LVL_W-W), float64(offset+step)))
					}
				case sdl.K_SPACE:
					if doDev {
						game.Jump()
					} else {
						doDraw = !doDraw
						surface.FillRect(&clearRect, 0xffaaaaaa)
						window.UpdateSurface()
					}
				case sdl.K_1:
					game = fl
					window.SetSize(W, 700)
					surface, _ = window.GetSurface()
				case sdl.K_2:
					game = mr
					window.SetSize(W, 250)
					surface, _ = window.GetSurface()
				case sdl.K_RETURN:
					if game == fl {
						game = mr
						window.SetSize(W, 250)
						surface, _ = window.GetSurface()
					} else {
						game = fl
						window.SetSize(W, 700)
						surface, _ = window.GetSurface()
					}
				case sdl.K_ESCAPE:
					stop = true
				case sdl.K_END:
					offset = int(math.Max(math.Min(float64(LVL_W-W), game.Complete()*float64(LVL_W)-float64(W)/2), 0))
				case sdl.K_HOME:
					offset = 0
				case sdl.K_f:
					doFastForward = !doFastForward
				case sdl.K_s:
					game.SaveNetsToFile()
				case sdl.K_p:
					h := int32(H)
					w := int32(float64(LVL_W) * (game.Complete() + 0.001))

					_, rmask, gmask, bmask, amask, err := sdl.PixelFormatEnumToMasks(sdl.PIXELFORMAT_RGB24)
					image, err := sdl.CreateRGBSurface(0, w, h, 24, rmask, gmask, bmask, amask)

					if err == nil {
						name := fmt.Sprintf("SS-%d.bmp", imageCounter)
						imageCounter++

						rect := sdl.Rect{0, 0, w, h}
						image.FillRect(&rect, 0xffffffff)
						game.SetDrawRectCb(func(pos, size *util.Vector, color uint32) {
							rect.X = int32(pos.X)
							rect.Y = int32(pos.Y)
							rect.W = int32(size.X)
							rect.H = int32(size.Y)
							image.FillRect(&rect, color)
						})
						game.DrawTick()
						e := image.SaveBMP(name)
						if e == nil {
							fmt.Println("Saving image: ", name, "Size: ", w, "x", h)
						} else {
							fmt.Println("Failed to save image!", e)
						}
						game.SetDrawRectCb(drawCb)
					}
				}
			}
		}

		if stop {
			break
		}

		frame++

		elapsed := time.Since(start)
		frameMs := float64(elapsed) / 1000000

		averageFrameTime = averageFrameTime*0.9 + float64(elapsed.Nanoseconds())*0.1

		if game.Done() {
			fmt.Println("Done")
			break
		}

		if !lastReportTime.Add(time.Second).After(start) {
			fmt.Println("")
			game.StatsReportTick()
			lastReportTime = start
			fmt.Printf("Last FrameTime: %f\tAverage FrameTime %f\tCompletion %f%%\n", frameMs, averageFrameTime/1000000, game.Complete()*100)
			fmt.Printf("FastForward %t\t Rand buffer refils %f\n", doFastForward, neural.BUFFER_REFILS)
		}

		// sleep only if drawing and there is time to sleep more than 3ms
		if !doFastForward && doDraw && frameMs < FRAME_TIME_MS && FRAME_TIME_MS-frameMs > 3.0 {
			time.Sleep(time.Millisecond * time.Duration(FRAME_TIME_MS-frameMs))
		}
	}

	sdl.Quit()
}
<commit_msg>Add frame rendered counter<commit_after>package main

import (
	"fmt"
	neural "github.com/poseidon4o/go-neural/src/neural"
	flappy "github.com/poseidon4o/go-neural/src/problems/flappy"
	mario "github.com/poseidon4o/go-neural/src/problems/mario"
	util "github.com/poseidon4o/go-neural/src/util"
	sdl "github.com/veandco/go-sdl2/sdl"
	"math"
	"runtime"
	"time"
)

type DrawableProblem interface {
	SetDrawRectCb(cb func(pos, size *util.Vector, color uint32))
	LogicTick(dt float64)
	DrawTick()
	StatsReportTick()
	Complete() float64
	Done() bool
	Jump()
	Move(int)
	SaveNetsToFile()
	LoadNetsFromFile()
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	fmt.Println("Controls:")
	fmt.Println("end:\tfurthest action in the level")
	fmt.Println("home:\tmove back to level begining")
	fmt.Println("left:\tmove screen to the left")
	fmt.Println("right:\tmove screen to the right")
	fmt.Println("1:\tswitch to flappy")
	fmt.Println("2:\tswitch to mario")
	fmt.Println("s:\tsave all nets to file")
	fmt.Println("f:\tturn on fast-forward mode")
	fmt.Println("p:\ttake screenshot in format \"SS-#d.bmp\", where #d is [0,inf)")
	fmt.Println("enter:\tcycle trough mario/flappy")
	fmt.Println("esc:\teixt")
	fmt.Println("")

	doDraw := true
	doDev := false
	doFastForward := false

	W := 1300
	H := 700
	LVL_W := W * 50

	var FPS float64 = 60.0
	FRAME_TIME_MS := 1000 / FPS
	FRAME_TIME := time.Millisecond * time.Duration(FRAME_TIME_MS)

	sdl.Init(sdl.INIT_EVERYTHING)

	window, err := sdl.CreateWindow("test", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED,
		int(W), int(H), sdl.WINDOW_SHOWN)
	if err != nil {
		panic(err)
	}
	defer window.Destroy()

	surface, err := window.GetSurface()
	if err != nil {
		panic(err)
	}

	clearRect := sdl.Rect{0, 0, int32(W), int32(H)}
	surface.FillRect(&clearRect, 0xffffffff)

	figCount := 1000
	if doDev {
		figCount = 1
	}

	offset := 0
	visible := func(pos, size *util.Vector) bool {

		// r1 := sdl.Rect{int32(pos.X), int32(pos.Y), int32(pos.X + size.X), int32(pos.Y + size.Y)}
		// r2 := sdl.Rect{int32(offset), 0, int32(offset + W), int32(H)}
		// return !(r2.X > r1.W || r2.W < r1.X || r2.Y > r1.H || r2.H < r1.Y)

		// so beautiful
		return !(float64(offset) > pos.X+size.X ||
			float64(offset+W) < pos.X ||
			0 > pos.Y+size.Y ||
			float64(H) < pos.Y)
	}

	toScreen := func(pos, size *util.Vector) *sdl.Rect {
		return &sdl.Rect{
			X: int32(pos.X - float64(offset)),
			Y: int32(pos.Y),
			W: int32(size.X),
			H: int32(size.Y),
		}
	}

	drawCb := func(pos, size *util.Vector, color uint32) {
		if visible(pos, size) {
			surface.FillRect(toScreen(pos, size), color)
		}
	}

	fl := flappy.NewFlappy(figCount, util.NewVector(float64(LVL_W), float64(H)))
	mr := mario.NewMario(figCount, util.NewVector(float64(LVL_W), float64(250)))

	mr.LoadNetsFromFile()
	fl.LoadNetsFromFile()

	fl.SetDrawRectCb(drawCb)
	mr.SetDrawRectCb(drawCb)

	imageCounter := 0

	var game DrawableProblem = fl

	step := 65

	frame := 0
	var averageFrameTime float64 = FRAME_TIME_MS * 1000000 // in nanosec
	start := time.Now()
	lastDrawTime := time.Now()
	lastReportTime := time.Now()
	loops := 0
	for {
		start = time.Now()
		loops++

		if doDraw {
			if doFastForward {
				if !lastDrawTime.Add(FRAME_TIME).After(start) {
					lastDrawTime = start
					window.UpdateSurface()
					frame++
					surface.FillRect(&clearRect, 0xffffffff)
					game.DrawTick()
				}
			} else {
				lastDrawTime = start
				window.UpdateSurface()
				frame++
				surface.FillRect(&clearRect, 0xffffffff)
				game.DrawTick()
			}

		} else if loops%10 == 0 {
			// update only 10% of the frames
			window.UpdateSurface()
			frame++
		}

		game.LogicTick(1 / FPS)

		stop := false
		for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
			switch t := event.(type) {
			case *sdl.QuitEvent:
				stop = true
			case *sdl.KeyDownEvent:
				switch t.Keysym.Sym {
				case sdl.K_LEFT:
					if doDev {
						game.Move(-1)
					} else {
						offset = int(math.Max(0, float64(offset-step)))
					}
				case sdl.K_RIGHT:
					if doDev {
						game.Move(1)
					} else {
						offset = int(math.Min(float64(LVL_W-W), float64(offset+step)))
					}
				case sdl.K_SPACE:
					if doDev {
						game.Jump()
					} else {
						doDraw = !doDraw
						surface.FillRect(&clearRect, 0xffaaaaaa)
						window.UpdateSurface()
					}
				case sdl.K_1:
					game = fl
					window.SetSize(W, 700)
					surface, _ = window.GetSurface()
				case sdl.K_2:
					game = mr
					window.SetSize(W, 250)
					surface, _ = window.GetSurface()
				case sdl.K_RETURN:
					if game == fl {
						game = mr
						window.SetSize(W, 250)
						surface, _ = window.GetSurface()
					} else {
						game = fl
						window.SetSize(W, 700)
						surface, _ = window.GetSurface()
					}
				case sdl.K_ESCAPE:
					stop = true
				case sdl.K_END:
					offset = int(math.Max(math.Min(float64(LVL_W-W), game.Complete()*float64(LVL_W)-float64(W)/2), 0))
				case sdl.K_HOME:
					offset = 0
				case sdl.K_f:
					doFastForward = !doFastForward
				case sdl.K_s:
					game.SaveNetsToFile()
				case sdl.K_p:
					h := int32(H)
					w := int32(float64(LVL_W) * (game.Complete() + 0.001))

					_, rmask, gmask, bmask, amask, err := sdl.PixelFormatEnumToMasks(sdl.PIXELFORMAT_RGB24)
					image, err := sdl.CreateRGBSurface(0, w, h, 24, rmask, gmask, bmask, amask)

					if err == nil {
						name := fmt.Sprintf("SS-%d.bmp", imageCounter)
						imageCounter++

						rect := sdl.Rect{0, 0, w, h}
						image.FillRect(&rect, 0xffffffff)
						game.SetDrawRectCb(func(pos, size *util.Vector, color uint32) {
							rect.X = int32(pos.X)
							rect.Y = int32(pos.Y)
							rect.W = int32(size.X)
							rect.H = int32(size.Y)
							image.FillRect(&rect, color)
						})
						game.DrawTick()
						e := image.SaveBMP(name)
						if e == nil {
							fmt.Println("Saving image: ", name, "Size: ", w, "x", h)
						} else {
							fmt.Println("Failed to save image!", e)
						}
						game.SetDrawRectCb(drawCb)
					}
				}
			}
		}

		if stop {
			break
		}

		elapsed := time.Since(start)
		frameMs := float64(elapsed) / 1000000

		averageFrameTime = averageFrameTime*0.9 + float64(elapsed.Nanoseconds())*0.1

		if game.Done() {
			fmt.Println("Done")
			break
		}

		if !lastReportTime.Add(time.Second).After(start) {
			fmt.Println("")
			game.StatsReportTick()
			fmt.Printf("Last FrameTime: %f\tAverage FrameTime %f\tCompletion %f%%\n", frameMs, averageFrameTime/1000000, game.Complete()*100)
			fmt.Printf("FastForward %t\t Rand buffer refils %f\tFrames for last second %d\n", doFastForward, neural.BUFFER_REFILS, frame)

			lastReportTime = start
			frame = 0
		}

		// sleep only if drawing and there is time to sleep more than 3ms
		if !doFastForward && doDraw && frameMs < FRAME_TIME_MS && FRAME_TIME_MS-frameMs > 3.0 {
			time.Sleep(time.Millisecond * time.Duration(FRAME_TIME_MS-frameMs))
		}
	}

	sdl.Quit()
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"os"
)

type TokenType int

const (
	TOctoHeadingLine TokenType = iota
	TAtSignHeadingLine

	TExclaimLine
	TQuestionLine
	TDashLine
	TColonLine

	TAstrisk
	TSigned
	TNullable

	TWhitespace
	TNewLine
	TString
	TComment

	TIllegal
	TEof
)

var typeNames = map[TokenType]string{
	TOctoHeadingLine:   "TOctoHeading",
	TAtSignHeadingLine: "TAtSignHeading",

	TExclaimLine:  "TExclaimLine",
	TQuestionLine: "TQuestionLine",
	TDashLine:     "TDashLine",
	TColonLine:    "TColonLine",

	TSigned:   "TSigned",
	TNullable: "TNullable",

	TWhitespace: "TWhitespace",
	TNewLine:    "TNewLine",
	TString:     "TString",
	TComment:    "TComment",

	TAstrisk: "TAstrisk",

	TIllegal: "TIllegal",
	TEof:     "TEof",
}

type Token struct {
	Type TokenType
}

func main() {
	r := bufio.NewReader(os.Stdin)
	s := NewScanner(r)

	for {
		tok, lit := s.Scan()
		fmt.Printf("%s: '%s'\n", typeNames[tok], lit)
		if tok == TEof {
			break
		}
	}

}
<commit_msg>Makes TIllegal the first value<commit_after>package main

import (
	"bufio"
	"fmt"
	"os"
)

type TokenType int

const (
	TIllegal TokenType = iota
	TEof

	TOctoHeadingLine
	TAtSignHeadingLine

	TExclaimLine
	TQuestionLine
	TDashLine
	TColonLine

	TAstrisk
	TSigned
	TNullable

	TWhitespace
	TNewLine
	TString
	TComment
)

var typeNames = map[TokenType]string{
	TIllegal: "TIllegal",
	TEof:     "TEof",

	TOctoHeadingLine:   "TOctoHeading",
	TAtSignHeadingLine: "TAtSignHeading",

	TExclaimLine:  "TExclaimLine",
	TQuestionLine: "TQuestionLine",
	TDashLine:     "TDashLine",
	TColonLine:    "TColonLine",

	TSigned:   "TSigned",
	TNullable: "TNullable",

	TWhitespace: "TWhitespace",
	TNewLine:    "TNewLine",
	TString:     "TString",
	TComment:    "TComment",

	TAstrisk: "TAstrisk",
}

type Token struct {
	Type TokenType
}

func main() {
	r := bufio.NewReader(os.Stdin)
	s := NewScanner(r)

	for {
		tok, lit := s.Scan()
		fmt.Printf("%s: '%s'\n", typeNames[tok], lit)
		if tok == TEof {
			break
		}
	}

}
<|endoftext|>


<commit_before>// Accept requests to combine PDF documents located on Amazon S3 into
// a single document, upload back to S3, and notify a callback URL.
// TODO fail fast if S3 connection is not usable.
// TODO need a way to enable auth in responses
package main

import (
	"flag"
	"fmt"
	"launchpad.net/goamz/aws"
	"os"
	"pdfcombiner/job"
	"pdfcombiner/server"
)

var (
	port       string
	serverMode bool
	bucket     string
	employerId int
)

func init() {
	flag.BoolVar(&serverMode, "server", false, "run in server mode")
	flag.StringVar(&port, "port", "8080", "port to listen on for server mode")
	flag.StringVar(&bucket, "bucket", "", "bucket name to use in standalone mode")
	flag.IntVar(&employerId, "employer", 0, "id number of the employer to combine for in standalone mode")
	flag.Parse()
	flag.Usage = func() {
		println("Usage: pdfcombiner [OPTS] [FILE...]")
		flag.PrintDefaults()
		os.Exit(1)
	}
}

func main() {
	verifyAws()
	switch {
	case serverMode:
		server.ListenOn(port)
	default:
		combineSynchronously()
	}
}

// Combine the requested files and return the status to standard out.
func combineSynchronously() {
	checkArgs()
	pdfFiles := flag.Args()
	println(pdfFiles)
	j, err := job.New(bucket, employerId, pdfFiles)
	if err != nil {
		panic(err)
	}
	fmt.Println(j)
}

func checkArgs() {
	switch {
	case flag.NArg() < 1:
		println("Cannot start in standalone mode with no files to combine.")
		flag.Usage()
	case (employerId == 0 || bucket == ""):
		println("Missing argument")
		flag.Usage()
	}
}

// Verify AWS credentials are set in the environment:
// - AWS_ACCESS_KEY_ID
// - AWS_SECRET_ACCESS_KEY
func verifyAws() {
	_, err := aws.EnvAuth()
	if err != nil {
		panic(err)
	}
}
<commit_msg>Actually combine instead of printing args<commit_after>// Accept requests to combine PDF documents located on Amazon S3 into
// a single document, upload back to S3, and notify a callback URL.
// TODO fail fast if S3 connection is not usable.
// TODO need a way to enable auth in responses
package main

import (
	"flag"
	"launchpad.net/goamz/aws"
	"os"
	"pdfcombiner/combiner"
	"pdfcombiner/job"
	"pdfcombiner/server"
)

var (
	port       string
	serverMode bool
	bucket     string
	employerId int
)

func init() {
	flag.BoolVar(&serverMode, "server", false, "run in server mode")
	flag.StringVar(&port, "port", "8080", "port to listen on for server mode")
	flag.StringVar(&bucket, "bucket", "", "bucket name to use in standalone mode")
	flag.IntVar(&employerId, "employer", 0, "id number of the employer to combine for in standalone mode")
	flag.Parse()
	flag.Usage = func() {
		println("Usage: pdfcombiner [OPTS] [FILE...]")
		flag.PrintDefaults()
		os.Exit(1)
	}
}

func main() {
	verifyAws()
	switch {
	case serverMode:
		server.ListenOn(port)
	default:
		combineSynchronously()
	}
}

// Combine the requested files and return the status to standard out.
func combineSynchronously() {
	checkArgs()
	pdfFiles := flag.Args()
	j, err := job.New(bucket, employerId, pdfFiles)
	if err != nil {
		panic(err)
	}
	combiner.Combine(j)
}

func checkArgs() {
	switch {
	case flag.NArg() < 1:
		println("Cannot start in standalone mode with no files to combine.")
		flag.Usage()
	case (employerId == 0 || bucket == ""):
		println("Missing argument")
		flag.Usage()
	}
}

// Verify AWS credentials are set in the environment:
// - AWS_ACCESS_KEY_ID
// - AWS_SECRET_ACCESS_KEY
func verifyAws() {
	_, err := aws.EnvAuth()
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"crypto/subtle"
	"encoding/base64"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/julienschmidt/httprouter"
	"github.com/rlmcpherson/s3gof3r"
	"gopkg.in/h2non/bimg.v1"
)

var (
	listenInterface  string
	maxAge           int
	securityKey      []byte
	resultBucketName string
	useRRS           bool
	unsafeMode       bool

	httpClient   = http.DefaultClient
	resultBucket *s3gof3r.Bucket
)

type ByteSize int64

const (
	_           = iota // ignore first value by assigning to blank identifier
	KB ByteSize = 1 << (10 * iota)
	MB
)

func main() {
	log.SetFlags(0) // hide timestamps from Go logs

	parseFlags()

	resultBucketName = os.Getenv("RESULT_STORAGE_BUCKET")
	if resultBucketName != "" {
		keys, err := s3gof3r.EnvKeys()
		if err != nil {
			log.Fatal(err)
		}
		resultBucket = s3gof3r.New(s3gof3r.DefaultDomain, keys).Bucket(resultBucketName)
		resultBucket.Concurrency = 4
		resultBucket.PartSize = int64(2 * MB)
		resultBucket.Md5Check = false
		httpClient = resultBucket.Client

		if rrs := os.Getenv("USE_RRS"); rrs == "true" || rrs == "1" {
			useRRS = true
		}
	}

	router := httprouter.New()
	router.HEAD("/:signature/:size/*source", handleResize)
	router.GET("/:signature/:size/*source", handleResize)
	log.Fatal(http.ListenAndServe(listenInterface, router))
}

func parseFlags() {
	securityKeyStr := ""

	port := os.Getenv("PORT")
	if port == "" {
		port = "8888"
	}

	if maxAgeStr := os.Getenv("MAX_AGE"); maxAgeStr != "" {
		var err error
		if maxAge, err = strconv.Atoi(maxAgeStr); err != nil {
			log.Fatal("invalid MAX_AGE setting")
		}
	}

	flag.StringVar(&listenInterface, "l", ":"+port, "listen address")
	flag.IntVar(&maxAge, "max-age", maxAge, "the maximum HTTP caching age to use on returned images")
	flag.StringVar(&securityKeyStr, "k", os.Getenv("SECURITY_KEY"), "security key")
	flag.BoolVar(&unsafeMode, "unsafe", false, "whether to allow /unsafe URLs")

	flag.Parse()

	if securityKeyStr == "" && !unsafeMode {
		log.Fatalf("must provide a security key with -k or allow unsafe URLs")
	}
	securityKey = []byte(securityKeyStr)
}

func handleResize(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
	reqPath := req.URL.EscapedPath()
	log.Printf("%s %s", req.Method, reqPath)
	sourceURL, err := url.Parse(strings.TrimPrefix(params.ByName("source"), "/"))
	if err != nil || !(sourceURL.Scheme == "http" || sourceURL.Scheme == "https") {
		http.Error(w, "invalid source URL", 400)
		return
	}

	sig := params.ByName("signature")
	pathToVerify := strings.TrimPrefix(reqPath, "/"+sig+"/")
	if err := validateSignature(sig, pathToVerify); err != nil {
		http.Error(w, "invalid signature", 401)
		return
	}

	width, height, err := parseWidthAndHeight(params.ByName("size"))
	if err != nil {
		http.Error(w, "invalid height requested", 400)
		return
	}

	resultPath := normalizePath(strings.TrimPrefix(reqPath, "/"+sig))

	// TODO(bgentry): everywhere that switches on resultBucket should switch on
	// something like resultStorage instead.
	if resultBucket == nil {
		// no result storage, just generate the thumbnail
		generateThumbnail(w, req.Method, resultPath, sourceURL.String(), width, height)
		return
	}

	// try to get stored result
	r, h, err := getStoredResult(req.Method, resultPath)
	if err != nil {
		log.Printf("getting stored result: %s", err)
		generateThumbnail(w, req.Method, resultPath, sourceURL.String(), width, height)
		return
	}
	defer r.Close()

	// return stored result
	length, err := strconv.Atoi(h.Get("Content-Length"))
	if err != nil {
		log.Printf("invalid result content-length: %s", err)
		// TODO: try to generate instead of erroring w/ 500?
		http.Error(w, err.Error(), 500)
		return
	}

	setResultHeaders(w, &result{
		ContentType:   h.Get("Content-Type"),
		ContentLength: length,
		ETag:          strings.Trim(h.Get("Etag"), `"`),
		Path:          resultPath,
	})
	if _, err = io.Copy(w, r); err != nil {
		log.Printf("copying from stored result: %s", err)
		return
	}
	if err = r.Close(); err != nil {
		log.Printf("closing stored result copy: %s", err)
	}
}

type result struct {
	Data          []byte
	ContentType   string
	ContentLength int
	ETag          string
	Path          string
}

func computeHexMD5(data []byte) string {
	h := md5.New()
	h.Write(data)
	return fmt.Sprintf("%x", h.Sum(nil))
}

func generateThumbnail(w http.ResponseWriter, rmethod, rpath string, sourceURL string, width, height uint) {
	log.Printf("generating %s", rpath)
	resp, err := httpClient.Get(sourceURL)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		log.Printf("unexpected status code from source: %d", resp.StatusCode)
		http.Error(w, "", resp.StatusCode)
		return
	}

	img, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	buf, err := bimg.Resize(img, bimg.Options{
		Height:       int(height),
		Width:        int(width),
		Gravity:      bimg.GravityCentre,
		Interpolator: bimg.Bicubic,
		Quality:      50,
	})

	if err != nil {
		responseCode := 500
		if err.Error() == "unknown image format" {
			responseCode = 400
		}
		http.Error(w, fmt.Sprintf("resizing image: %s", err.Error()), responseCode)
		return
	}

	res := &result{
		ContentType:   "image/jpeg", // TODO: support PNGs as well
		ContentLength: len(buf),
		Data:          buf, // TODO: check if I need to copy this
		ETag:          computeHexMD5(buf),
		Path:          rpath,
	}
	setResultHeaders(w, res)
	if rmethod != "HEAD" {
		if _, err = w.Write(buf); err != nil {
			log.Printf("writing buffer to response: %s", err)
		}
	}

	if resultBucket != nil {
		go storeResult(res)
	}
}

// caller is responsible for closing the returned ReadCloser
func getStoredResult(method, path string) (io.ReadCloser, http.Header, error) {
	if method != "HEAD" {
		return resultBucket.GetReader(path, nil)
	}

	s3URL := fmt.Sprintf("https://%s.s3.amazonaws.com%s", resultBucketName, path)
	req, err := http.NewRequest(method, s3URL, nil)
	if err != nil {
		return nil, nil, err
	}

	resultBucket.Sign(req)
	res, err := httpClient.Do(req)
	if err != nil {
		return nil, nil, err
	}
	if res.StatusCode < 200 || res.StatusCode >= 300 {
		// TODO: drain res.Body to ioutil.Discard before closing?
		res.Body.Close()
		return nil, nil, fmt.Errorf("unexpected status code %d", res.StatusCode)
	}
	res.Header.Set("Content-Length", strconv.FormatInt(res.ContentLength, 10))
	return res.Body, res.Header, err
}

func mustGetenv(name string) string {
	value := os.Getenv(name)
	if value == "" {
		log.Fatalf("missing %s env", name)
	}
	return value
}

func normalizePath(p string) string {
	// TODO(bgentry): Support for custom root path? ala RESULT_STORAGE_AWS_STORAGE_ROOT_PATH
	return path.Clean(p)
}

func parseWidthAndHeight(str string) (width, height uint, err error) {
	sizeParts := strings.Split(str, "x")
	if len(sizeParts) != 2 {
		err = fmt.Errorf("invalid size requested")
		return
	}
	width64, err := strconv.ParseUint(sizeParts[0], 10, 64)
	if err != nil {
		err = fmt.Errorf("invalid width requested")
		return
	}
	height64, err := strconv.ParseUint(sizeParts[1], 10, 64)
	if err != nil {
		err = fmt.Errorf("invalid height requested")
		return
	}
	return uint(width64), uint(height64), nil
}

func setCacheHeaders(w http.ResponseWriter) {
	w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d,public", maxAge))
	w.Header().Set("Expires", time.Now().UTC().Add(time.Duration(maxAge)*time.Second).Format(http.TimeFormat))
}

func setResultHeaders(w http.ResponseWriter, result *result) {
	w.Header().Set("Content-Type", result.ContentType)
	w.Header().Set("Content-Length", strconv.Itoa(result.ContentLength))
	w.Header().Set("ETag", `"`+result.ETag+`"`)
	setCacheHeaders(w)
}

func storeResult(res *result) {
	h := make(http.Header)
	h.Set("Content-Type", res.ContentType)
	if useRRS {
		h.Set("x-amz-storage-class", "REDUCED_REDUNDANCY")
	}
	w, err := resultBucket.PutWriter(res.Path, h, nil)
	if err != nil {
		log.Printf("storing result for %s: %s", res.Path, err)
		return
	}
	defer w.Close()
	if _, err = w.Write(res.Data); err != nil {
		log.Printf("storing result for %s: %s", res.Path, err)
		return
	}
	if err = w.Close(); err != nil {
		log.Printf("storing result for %s: %s", res.Path, err)
	}
}

func validateSignature(sig, pathPart string) error {
	if unsafeMode && sig == "unsafe" {
		return nil
	}

	h := hmac.New(sha1.New, securityKey)
	if _, err := h.Write([]byte(pathPart)); err != nil {
		return err
	}
	actualSig := base64.URLEncoding.EncodeToString(h.Sum(nil))
	// constant-time string comparison
	if subtle.ConstantTimeCompare([]byte(sig), []byte(actualSig)) != 1 {
		return fmt.Errorf("signature mismatch")
	}
	return nil
}
<commit_msg>update error check to match bimg syntax<commit_after>package main

import (
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"crypto/subtle"
	"encoding/base64"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/julienschmidt/httprouter"
	"github.com/rlmcpherson/s3gof3r"
	"gopkg.in/h2non/bimg.v1"
)

var (
	listenInterface  string
	maxAge           int
	securityKey      []byte
	resultBucketName string
	useRRS           bool
	unsafeMode       bool

	httpClient   = http.DefaultClient
	resultBucket *s3gof3r.Bucket
)

type ByteSize int64

const (
	_           = iota // ignore first value by assigning to blank identifier
	KB ByteSize = 1 << (10 * iota)
	MB
)

func main() {
	log.SetFlags(0) // hide timestamps from Go logs

	parseFlags()

	resultBucketName = os.Getenv("RESULT_STORAGE_BUCKET")
	if resultBucketName != "" {
		keys, err := s3gof3r.EnvKeys()
		if err != nil {
			log.Fatal(err)
		}
		resultBucket = s3gof3r.New(s3gof3r.DefaultDomain, keys).Bucket(resultBucketName)
		resultBucket.Concurrency = 4
		resultBucket.PartSize = int64(2 * MB)
		resultBucket.Md5Check = false
		httpClient = resultBucket.Client

		if rrs := os.Getenv("USE_RRS"); rrs == "true" || rrs == "1" {
			useRRS = true
		}
	}

	router := httprouter.New()
	router.HEAD("/:signature/:size/*source", handleResize)
	router.GET("/:signature/:size/*source", handleResize)
	log.Fatal(http.ListenAndServe(listenInterface, router))
}

func parseFlags() {
	securityKeyStr := ""

	port := os.Getenv("PORT")
	if port == "" {
		port = "8888"
	}

	if maxAgeStr := os.Getenv("MAX_AGE"); maxAgeStr != "" {
		var err error
		if maxAge, err = strconv.Atoi(maxAgeStr); err != nil {
			log.Fatal("invalid MAX_AGE setting")
		}
	}

	flag.StringVar(&listenInterface, "l", ":"+port, "listen address")
	flag.IntVar(&maxAge, "max-age", maxAge, "the maximum HTTP caching age to use on returned images")
	flag.StringVar(&securityKeyStr, "k", os.Getenv("SECURITY_KEY"), "security key")
	flag.BoolVar(&unsafeMode, "unsafe", false, "whether to allow /unsafe URLs")

	flag.Parse()

	if securityKeyStr == "" && !unsafeMode {
		log.Fatalf("must provide a security key with -k or allow unsafe URLs")
	}
	securityKey = []byte(securityKeyStr)
}

func handleResize(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
	reqPath := req.URL.EscapedPath()
	log.Printf("%s %s", req.Method, reqPath)
	sourceURL, err := url.Parse(strings.TrimPrefix(params.ByName("source"), "/"))
	if err != nil || !(sourceURL.Scheme == "http" || sourceURL.Scheme == "https") {
		http.Error(w, "invalid source URL", 400)
		return
	}

	sig := params.ByName("signature")
	pathToVerify := strings.TrimPrefix(reqPath, "/"+sig+"/")
	if err := validateSignature(sig, pathToVerify); err != nil {
		http.Error(w, "invalid signature", 401)
		return
	}

	width, height, err := parseWidthAndHeight(params.ByName("size"))
	if err != nil {
		http.Error(w, "invalid height requested", 400)
		return
	}

	resultPath := normalizePath(strings.TrimPrefix(reqPath, "/"+sig))

	// TODO(bgentry): everywhere that switches on resultBucket should switch on
	// something like resultStorage instead.
	if resultBucket == nil {
		// no result storage, just generate the thumbnail
		generateThumbnail(w, req.Method, resultPath, sourceURL.String(), width, height)
		return
	}

	// try to get stored result
	r, h, err := getStoredResult(req.Method, resultPath)
	if err != nil {
		log.Printf("getting stored result: %s", err)
		generateThumbnail(w, req.Method, resultPath, sourceURL.String(), width, height)
		return
	}
	defer r.Close()

	// return stored result
	length, err := strconv.Atoi(h.Get("Content-Length"))
	if err != nil {
		log.Printf("invalid result content-length: %s", err)
		// TODO: try to generate instead of erroring w/ 500?
		http.Error(w, err.Error(), 500)
		return
	}

	setResultHeaders(w, &result{
		ContentType:   h.Get("Content-Type"),
		ContentLength: length,
		ETag:          strings.Trim(h.Get("Etag"), `"`),
		Path:          resultPath,
	})
	if _, err = io.Copy(w, r); err != nil {
		log.Printf("copying from stored result: %s", err)
		return
	}
	if err = r.Close(); err != nil {
		log.Printf("closing stored result copy: %s", err)
	}
}

type result struct {
	Data          []byte
	ContentType   string
	ContentLength int
	ETag          string
	Path          string
}

func computeHexMD5(data []byte) string {
	h := md5.New()
	h.Write(data)
	return fmt.Sprintf("%x", h.Sum(nil))
}

func generateThumbnail(w http.ResponseWriter, rmethod, rpath string, sourceURL string, width, height uint) {
	log.Printf("generating %s", rpath)
	resp, err := httpClient.Get(sourceURL)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		log.Printf("unexpected status code from source: %d", resp.StatusCode)
		http.Error(w, "", resp.StatusCode)
		return
	}

	img, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	buf, err := bimg.Resize(img, bimg.Options{
		Height:       int(height),
		Width:        int(width),
		Gravity:      bimg.GravityCentre,
		Interpolator: bimg.Bicubic,
		Quality:      50,
	})

	if err != nil {
		responseCode := 500
		if err.Error() == "Unsupported image format" {
			responseCode = 415 // Unsupported Media Type
		}
		http.Error(w, fmt.Sprintf("resizing image: %s", err.Error()), responseCode)
		return
	}

	res := &result{
		ContentType:   "image/jpeg", // TODO: support PNGs as well
		ContentLength: len(buf),
		Data:          buf, // TODO: check if I need to copy this
		ETag:          computeHexMD5(buf),
		Path:          rpath,
	}
	setResultHeaders(w, res)
	if rmethod != "HEAD" {
		if _, err = w.Write(buf); err != nil {
			log.Printf("writing buffer to response: %s", err)
		}
	}

	if resultBucket != nil {
		go storeResult(res)
	}
}

// caller is responsible for closing the returned ReadCloser
func getStoredResult(method, path string) (io.ReadCloser, http.Header, error) {
	if method != "HEAD" {
		return resultBucket.GetReader(path, nil)
	}

	s3URL := fmt.Sprintf("https://%s.s3.amazonaws.com%s", resultBucketName, path)
	req, err := http.NewRequest(method, s3URL, nil)
	if err != nil {
		return nil, nil, err
	}

	resultBucket.Sign(req)
	res, err := httpClient.Do(req)
	if err != nil {
		return nil, nil, err
	}
	if res.StatusCode < 200 || res.StatusCode >= 300 {
		// TODO: drain res.Body to ioutil.Discard before closing?
		res.Body.Close()
		return nil, nil, fmt.Errorf("unexpected status code %d", res.StatusCode)
	}
	res.Header.Set("Content-Length", strconv.FormatInt(res.ContentLength, 10))
	return res.Body, res.Header, err
}

func mustGetenv(name string) string {
	value := os.Getenv(name)
	if value == "" {
		log.Fatalf("missing %s env", name)
	}
	return value
}

func normalizePath(p string) string {
	// TODO(bgentry): Support for custom root path? ala RESULT_STORAGE_AWS_STORAGE_ROOT_PATH
	return path.Clean(p)
}

func parseWidthAndHeight(str string) (width, height uint, err error) {
	sizeParts := strings.Split(str, "x")
	if len(sizeParts) != 2 {
		err = fmt.Errorf("invalid size requested")
		return
	}
	width64, err := strconv.ParseUint(sizeParts[0], 10, 64)
	if err != nil {
		err = fmt.Errorf("invalid width requested")
		return
	}
	height64, err := strconv.ParseUint(sizeParts[1], 10, 64)
	if err != nil {
		err = fmt.Errorf("invalid height requested")
		return
	}
	return uint(width64), uint(height64), nil
}

func setCacheHeaders(w http.ResponseWriter) {
	w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d,public", maxAge))
	w.Header().Set("Expires", time.Now().UTC().Add(time.Duration(maxAge)*time.Second).Format(http.TimeFormat))
}

func setResultHeaders(w http.ResponseWriter, result *result) {
	w.Header().Set("Content-Type", result.ContentType)
	w.Header().Set("Content-Length", strconv.Itoa(result.ContentLength))
	w.Header().Set("ETag", `"`+result.ETag+`"`)
	setCacheHeaders(w)
}

func storeResult(res *result) {
	h := make(http.Header)
	h.Set("Content-Type", res.ContentType)
	if useRRS {
		h.Set("x-amz-storage-class", "REDUCED_REDUNDANCY")
	}
	w, err := resultBucket.PutWriter(res.Path, h, nil)
	if err != nil {
		log.Printf("storing result for %s: %s", res.Path, err)
		return
	}
	defer w.Close()
	if _, err = w.Write(res.Data); err != nil {
		log.Printf("storing result for %s: %s", res.Path, err)
		return
	}
	if err = w.Close(); err != nil {
		log.Printf("storing result for %s: %s", res.Path, err)
	}
}

func validateSignature(sig, pathPart string) error {
	if unsafeMode && sig == "unsafe" {
		return nil
	}

	h := hmac.New(sha1.New, securityKey)
	if _, err := h.Write([]byte(pathPart)); err != nil {
		return err
	}
	actualSig := base64.URLEncoding.EncodeToString(h.Sum(nil))
	// constant-time string comparison
	if subtle.ConstantTimeCompare([]byte(sig), []byte(actualSig)) != 1 {
		return fmt.Errorf("signature mismatch")
	}
	return nil
}
<|endoftext|>