<commit_before>package main

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"strings"

	"github.com/gorilla/context"
	"github.com/gorilla/mux"
)

// 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
}

type BatchVars struct {
	Objects []*RequestVars `json:"objects"`
}

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

// Representation is object medata as seen by clients of the lfs server.
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)

	if Config.IsHTTPS() {
		return fmt.Sprintf("%s://%s%s", Config.Scheme, Config.Host, path)
	}

	return fmt.Sprintf("http://%s%s", 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       *mux.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 := mux.NewRouter()

	r.HandleFunc("/{user}/{repo}/objects/batch", app.BatchHandler).Methods("POST").MatcherFunc(MetaMatcher)
	route := "/{user}/{repo}/objects/{oid}"
	r.HandleFunc(route, app.GetContentHandler).Methods("GET", "HEAD").MatcherFunc(ContentMatcher)
	r.HandleFunc(route, app.GetMetaHandler).Methods("GET", "HEAD").MatcherFunc(MetaMatcher)
	r.HandleFunc(route, app.PutHandler).Methods("PUT").MatcherFunc(ContentMatcher)

	r.HandleFunc("/{user}/{repo}/objects", app.PostHandler).Methods("POST").MatcherFunc(MetaMatcher)

	app.addMgmt(r)

	app.router = r

	return app
}

func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	b := make([]byte, 16)
	_, err := rand.Read(b)
	if err == nil {
		context.Set(r, "RequestID", fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]))
	}

	a.router.ServeHTTP(w, r)
}

// 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)
}

// 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 {
		if isAuthError(err) {
			requireAuth(w, r)
		} else {
			writeStatus(w, r, 404)
		}
		return
	}

	content, err := a.contentStore.Get(meta)
	if err != nil {
		writeStatus(w, 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) {
			requireAuth(w, r)
		} else {
			writeStatus(w, r, 404)
		}
		return
	}

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

	if r.Method == "GET" {
		enc := json.NewEncoder(w)
		enc.Encode(a.Represent(rv, meta, true, 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) {
			requireAuth(w, r)
		} else {
			writeStatus(w, r, 404)
		}
		return
	}

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

	sentStatus := 202
	if meta.Existing && a.contentStore.Exists(meta) {
		sentStatus = 200
	}
	w.WriteHeader(sentStatus)

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

// BatchHandler provides the batch api
func (a *App) BatchHandler(w http.ResponseWriter, r *http.Request) {
	bv := unpackbatch(r)

	var responseObjects []*Representation

	// Create a response object
	for _, object := range bv.Objects {
		meta, err := a.metaStore.Get(object)
		if err == nil { // Object is found
			responseObjects = append(responseObjects, a.Represent(object, meta, true, false))
			continue
		}

		if isAuthError(err) {
			requireAuth(w, r)
			return
		}

		// Object is not found
		meta, err = a.metaStore.Put(object)
		if err == nil {
			responseObjects = append(responseObjects, a.Represent(object, meta, meta.Existing, true))
		}
	}

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

	type ro struct {
		Objects []*Representation `json:"objects"`
	}

	respobj := &ro{responseObjects}

	enc := json.NewEncoder(w)
	enc.Encode(respobj)
	logRequest(r, 200)
}

// 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) {
			requireAuth(w, r)
		} else {
			writeStatus(w, r, 404)
		}
		return
	}

	if err := a.contentStore.Put(meta, r.Body); err != nil {
		w.WriteHeader(500)
		fmt.Fprintf(w, `{"message":"%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 *MetaObject, download, upload bool) *Representation {
	rep := &Representation{
		Oid:   meta.Oid,
		Size:  meta.Size,
		Links: make(map[string]*link),
	}

	if download {
		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
}

// ContentMatcher provides a mux.MatcherFunc that only allows requests that contain
// an Accept header with the contentMediaType
func ContentMatcher(r *http.Request, m *mux.RouteMatch) bool {
	mediaParts := strings.Split(r.Header.Get("Accept"), ";")
	mt := mediaParts[0]
	return mt == contentMediaType
}

// MetaMatcher provides a mux.MatcherFunc that only allows requests that contain
// an Accept header with the metaMediaType
func MetaMatcher(r *http.Request, m *mux.RouteMatch) bool {
	mediaParts := strings.Split(r.Header.Get("Accept"), ";")
	mt := mediaParts[0]
	return mt == metaMediaType
}

func unpack(r *http.Request) *RequestVars {
	vars := mux.Vars(r)
	rv := &RequestVars{
		User:          vars["user"],
		Repo:          vars["repo"],
		Oid:           vars["oid"],
		Authorization: r.Header.Get("Authorization"),
	}

	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
}

// TODO cheap hack, unify with unpack
func unpackbatch(r *http.Request) *BatchVars {
	vars := mux.Vars(r)

	var bv BatchVars

	dec := json.NewDecoder(r.Body)
	err := dec.Decode(&bv)
	if err != nil {
		return &bv
	}

	for i := 0; i < len(bv.Objects); i++ {
		bv.Objects[i].User = vars["user"]
		bv.Objects[i].Repo = vars["repo"]
		bv.Objects[i].Authorization = r.Header.Get("Authorization")
	}

	return &bv
}

func writeStatus(w http.ResponseWriter, r *http.Request, status int) {
	message := http.StatusText(status)

	mediaParts := strings.Split(r.Header.Get("Accept"), ";")
	mt := mediaParts[0]
	if strings.HasSuffix(mt, "+json") {
		message = `{"message":"` + message + `"}`
	}

	w.WriteHeader(status)
	fmt.Fprint(w, message)
	logRequest(r, status)
}

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

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

func requireAuth(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("WWW-Authenticate", "Basic realm=git-lfs-server")
	writeStatus(w, r, 401)
}
<commit_msg>Fix download authorization issue<commit_after>package main

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"strings"

	"github.com/gorilla/context"
	"github.com/gorilla/mux"
)

// 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
}

type BatchVars struct {
	Objects []*RequestVars `json:"objects"`
}

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

// Representation is object medata as seen by clients of the lfs server.
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)

	if Config.IsHTTPS() {
		return fmt.Sprintf("%s://%s%s", Config.Scheme, Config.Host, path)
	}

	return fmt.Sprintf("http://%s%s", 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       *mux.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 := mux.NewRouter()

	r.HandleFunc("/{user}/{repo}/objects/batch", app.BatchHandler).Methods("POST").MatcherFunc(MetaMatcher)
	route := "/{user}/{repo}/objects/{oid}"
	r.HandleFunc(route, app.GetContentHandler).Methods("GET", "HEAD").MatcherFunc(ContentMatcher)
	r.HandleFunc(route, app.GetMetaHandler).Methods("GET", "HEAD").MatcherFunc(MetaMatcher)
	r.HandleFunc(route, app.PutHandler).Methods("PUT").MatcherFunc(ContentMatcher)

	r.HandleFunc("/{user}/{repo}/objects", app.PostHandler).Methods("POST").MatcherFunc(MetaMatcher)

	app.addMgmt(r)

	app.router = r

	return app
}

func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	b := make([]byte, 16)
	_, err := rand.Read(b)
	if err == nil {
		context.Set(r, "RequestID", fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]))
	}

	a.router.ServeHTTP(w, r)
}

// 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)
}

// 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 {
		if isAuthError(err) {
			requireAuth(w, r)
		} else {
			writeStatus(w, r, 404)
		}
		return
	}

	content, err := a.contentStore.Get(meta)
	if err != nil {
		writeStatus(w, 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) {
			requireAuth(w, r)
		} else {
			writeStatus(w, r, 404)
		}
		return
	}

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

	if r.Method == "GET" {
		enc := json.NewEncoder(w)
		enc.Encode(a.Represent(rv, meta, true, 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) {
			requireAuth(w, r)
		} else {
			writeStatus(w, r, 404)
		}
		return
	}

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

	sentStatus := 202
	if meta.Existing && a.contentStore.Exists(meta) {
		sentStatus = 200
	}
	w.WriteHeader(sentStatus)

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

// BatchHandler provides the batch api
func (a *App) BatchHandler(w http.ResponseWriter, r *http.Request) {
	bv := unpackbatch(r)

	var responseObjects []*Representation

	// Create a response object
	for _, object := range bv.Objects {
		meta, err := a.metaStore.Get(object)
		if err == nil { // Object is found
			responseObjects = append(responseObjects, a.Represent(object, meta, true, false))
			continue
		}

		if isAuthError(err) {
			requireAuth(w, r)
			return
		}

		// Object is not found
		meta, err = a.metaStore.Put(object)
		if err == nil {
			responseObjects = append(responseObjects, a.Represent(object, meta, meta.Existing, true))
		}
	}

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

	type ro struct {
		Objects []*Representation `json:"objects"`
	}

	respobj := &ro{responseObjects}

	enc := json.NewEncoder(w)
	enc.Encode(respobj)
	logRequest(r, 200)
}

// 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) {
			requireAuth(w, r)
		} else {
			writeStatus(w, r, 404)
		}
		return
	}

	if err := a.contentStore.Put(meta, r.Body); err != nil {
		w.WriteHeader(500)
		fmt.Fprintf(w, `{"message":"%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 *MetaObject, download, upload bool) *Representation {
	rep := &Representation{
		Oid:   meta.Oid,
		Size:  meta.Size,
		Links: make(map[string]*link),
	}

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

	if upload {
		rep.Links["upload"] = &link{Href: rv.ObjectLink(), Header: header}
	}
	return rep
}

// ContentMatcher provides a mux.MatcherFunc that only allows requests that contain
// an Accept header with the contentMediaType
func ContentMatcher(r *http.Request, m *mux.RouteMatch) bool {
	mediaParts := strings.Split(r.Header.Get("Accept"), ";")
	mt := mediaParts[0]
	return mt == contentMediaType
}

// MetaMatcher provides a mux.MatcherFunc that only allows requests that contain
// an Accept header with the metaMediaType
func MetaMatcher(r *http.Request, m *mux.RouteMatch) bool {
	mediaParts := strings.Split(r.Header.Get("Accept"), ";")
	mt := mediaParts[0]
	return mt == metaMediaType
}

func unpack(r *http.Request) *RequestVars {
	vars := mux.Vars(r)
	rv := &RequestVars{
		User:          vars["user"],
		Repo:          vars["repo"],
		Oid:           vars["oid"],
		Authorization: r.Header.Get("Authorization"),
	}

	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
}

// TODO cheap hack, unify with unpack
func unpackbatch(r *http.Request) *BatchVars {
	vars := mux.Vars(r)

	var bv BatchVars

	dec := json.NewDecoder(r.Body)
	err := dec.Decode(&bv)
	if err != nil {
		return &bv
	}

	for i := 0; i < len(bv.Objects); i++ {
		bv.Objects[i].User = vars["user"]
		bv.Objects[i].Repo = vars["repo"]
		bv.Objects[i].Authorization = r.Header.Get("Authorization")
	}

	return &bv
}

func writeStatus(w http.ResponseWriter, r *http.Request, status int) {
	message := http.StatusText(status)

	mediaParts := strings.Split(r.Header.Get("Accept"), ";")
	mt := mediaParts[0]
	if strings.HasSuffix(mt, "+json") {
		message = `{"message":"` + message + `"}`
	}

	w.WriteHeader(status)
	fmt.Fprint(w, message)
	logRequest(r, status)
}

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

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

func requireAuth(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("WWW-Authenticate", "Basic realm=git-lfs-server")
	writeStatus(w, r, 401)
}
<|endoftext|>


<commit_before>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package charmstore

import (
	"fmt"
	"net/http"
	"sort"

	"labix.org/v2/mgo"

	"github.com/juju/charmstore/internal/charmstore"
	"github.com/juju/charmstore/internal/v4"
)

const (
	V4 = "v4"
)

var versions = map[string]func(*charmstore.Store) http.Handler{
	V4: v4.New,
}

func Versions() []string {
	vs := make([]string, 0, len(versions))
	for v := range versions {
		vs = append(vs, v)
	}
	sort.Strings(vs)
	return vs
}

// NewServer returns a new handler that handles
// charm store requests and stores its data in the given database.
func NewServer(db *mgo.Database, serveVersions ...string) (http.Handler, error) {
	newAPIs := make(map[string]charmstore.NewAPIHandler)
	for _, vers := range serveVersions {
		newAPI := versions[vers]
		if newAPI == nil {
			return nil, fmt.Errorf("unknown version %q", vers)
		}
		newAPIs[vers] = newAPI
	}

	return charmstore.NewServer(db, newAPIs)
}
<commit_msg>add doc comments<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package charmstore

import (
	"fmt"
	"net/http"
	"sort"

	"labix.org/v2/mgo"

	"github.com/juju/charmstore/internal/charmstore"
	"github.com/juju/charmstore/internal/v4"
)

// Versions of the API that can be served.
const (
	V4 = "v4"
)

var versions = map[string]func(*charmstore.Store) http.Handler{
	V4: v4.New,
}

// Versions returns all known API versions.
func Versions() []string {
	vs := make([]string, 0, len(versions))
	for v := range versions {
		vs = append(vs, v)
	}
	sort.Strings(vs)
	return vs
}

// NewServer returns a new handler that handles
// charm store requests and stores its data in the given database.
func NewServer(db *mgo.Database, serveVersions ...string) (http.Handler, error) {
	newAPIs := make(map[string]charmstore.NewAPIHandler)
	for _, vers := range serveVersions {
		newAPI := versions[vers]
		if newAPI == nil {
			return nil, fmt.Errorf("unknown version %q", vers)
		}
		newAPIs[vers] = newAPI
	}

	return charmstore.NewServer(db, newAPIs)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"strconv"

	"github.com/UniversityRadioYork/2016-site/controllers"
	"github.com/UniversityRadioYork/2016-site/structs"
	"github.com/UniversityRadioYork/myradio-go"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
)

// Server is the type of the main 2016site web application.
type Server struct {
	*negroni.Negroni
}

// NewServer creates a 2016site server based on the config c.
func NewServer(c *structs.Config) (*Server, error) {

	s := Server{negroni.Classic()}

	session, err := myradio.NewSessionFromKeyFile()

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

	router := mux.NewRouter().StrictSlash(true)

	getRouter := router.Methods("GET").Subrouter()
	postRouter := router.Methods("POST").Subrouter()

	// Routes go in here
	nfc := controllers.NewNotFoundController(c)
	router.NotFoundHandler = http.HandlerFunc(nfc.Get)

	ic := controllers.NewIndexController(session, c)
	getRouter.HandleFunc("/", ic.Get)
	postRouter.HandleFunc("/", ic.Post)

	sc := controllers.NewSearchController(session, c)
	getRouter.HandleFunc("/search/", sc.Get)

	showC := controllers.NewShowController(session, c)
	getRouter.HandleFunc("/schedule/shows/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/schedule/thisweek/", 301)
	})
	getRouter.HandleFunc("/schedule/shows/{id:[0-9]+}/", showC.GetShow).Name("show")
	getRouter.HandleFunc("/schedule/shows/timeslots/{id:[0-9]+}/", showC.GetTimeslot).Name("timeslot")
	getRouter.HandleFunc("/schedule/shows/seasons/{id:[0-9]+}/", showC.GetSeason).Name("season")

	getRouter.HandleFunc("/schedule/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/schedule/thisweek/", 301)
	})
	// NOTE: NewScheduleWeekController assumes 'timeslot' is installed BEFORE it is called.
	schedWeekC := controllers.NewScheduleWeekController(session, getRouter, c)
	getRouter.HandleFunc("/schedule/thisweek/", schedWeekC.GetThisWeek).Name("schedule-thisweek")

	getRouter.HandleFunc("/schedule/{year:[1-9][0-9][0-9][0-9]}/w{week:[0-5]?[0-9]}/", schedWeekC.GetByYearWeek).Name("schedule-week")
	// This route exists so that day schedule links from the previous website aren't broken.
	getRouter.HandleFunc("/schedule/{year:[1-9][0-9][0-9][0-9]}/w{week:[0-5]?[0-9]}/{day:[1-7]}/", schedWeekC.GetByYearWeek).Name("schedule-week-day-compat")

	onDemandC := controllers.NewOnDemandController(session, c)
	getRouter.HandleFunc("/ontap/", onDemandC.Get)

	podcastsC := controllers.NewPodcastController(session, c)
	getRouter.HandleFunc("/podcasts/", podcastsC.GetAllPodcasts)
	getRouter.HandleFunc("/podcasts/page/{page:[0-9]+}", podcastsC.GetAllPodcasts)
	getRouter.HandleFunc("/podcasts/{id:[0-9]+}/", podcastsC.Get)
	getRouter.HandleFunc("/podcasts/{id:[0-9]+}/player/", podcastsC.GetEmbed)
	// Redirect old podcast URLs
	getRouter.HandleFunc("/uryplayer/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/ontap/", 301)
	})
	getRouter.HandleFunc("/uryplayer/podcasts/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/podcasts/", 301)
	})
	getRouter.HandleFunc("/uryplayer/podcasts/{id:[0-9]+}/", func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		id, _ := strconv.Atoi(vars["id"])
		http.Redirect(w, r, fmt.Sprintf("/podcasts/%d/", id), 301)
	})
	getRouter.HandleFunc("/uryplayer/podcasts/{id:[0-9]+}/player/", func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		id, _ := strconv.Atoi(vars["id"])
		http.Redirect(w, r, fmt.Sprintf("/podcasts/%d/player/", id), 301)
	})

	pc := controllers.NewPeopleController(session, c)
	getRouter.HandleFunc("/people/{id:[0-9]+}/", pc.Get)

	teamC := controllers.NewTeamController(session, c)
	getRouter.HandleFunc("/teams/", teamC.GetAll)
	getRouter.HandleFunc("/teams/{alias}/", teamC.Get)

	getinvolvedC := controllers.NewGetInvolvedController(session, c)
	getRouter.HandleFunc("/getinvolved/", getinvolvedC.Get)

	signupC := controllers.NewSignUpController(session, c)
	postRouter.HandleFunc("/signup/", signupC.Post)

	staticC := controllers.NewStaticController(c)
	getRouter.HandleFunc("/about/", staticC.GetAbout)
	getRouter.HandleFunc("/contact/", staticC.GetContact)
	getRouter.HandleFunc("/competitions/", staticC.GetCompetitions)

	// End routes

	s.UseHandler(router)

	return &s, nil

}
<commit_msg>Add /listen redirect to server<commit_after>package main

import (
	"fmt"
	"net/http"
	"strconv"

	"github.com/UniversityRadioYork/2016-site/controllers"
	"github.com/UniversityRadioYork/2016-site/structs"
	"github.com/UniversityRadioYork/myradio-go"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
)

// Server is the type of the main 2016site web application.
type Server struct {
	*negroni.Negroni
}

// NewServer creates a 2016site server based on the config c.
func NewServer(c *structs.Config) (*Server, error) {

	s := Server{negroni.Classic()}

	session, err := myradio.NewSessionFromKeyFile()

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

	router := mux.NewRouter().StrictSlash(true)

	getRouter := router.Methods("GET").Subrouter()
	postRouter := router.Methods("POST").Subrouter()

	// Routes go in here
	nfc := controllers.NewNotFoundController(c)
	router.NotFoundHandler = http.HandlerFunc(nfc.Get)

	ic := controllers.NewIndexController(session, c)
	getRouter.HandleFunc("/", ic.Get)
	postRouter.HandleFunc("/", ic.Post)

	sc := controllers.NewSearchController(session, c)
	getRouter.HandleFunc("/search/", sc.Get)

	showC := controllers.NewShowController(session, c)
	getRouter.HandleFunc("/schedule/shows/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/schedule/thisweek/", 301)
	})
	getRouter.HandleFunc("/schedule/shows/{id:[0-9]+}/", showC.GetShow).Name("show")
	getRouter.HandleFunc("/schedule/shows/timeslots/{id:[0-9]+}/", showC.GetTimeslot).Name("timeslot")
	getRouter.HandleFunc("/schedule/shows/seasons/{id:[0-9]+}/", showC.GetSeason).Name("season")

	getRouter.HandleFunc("/schedule/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/schedule/thisweek/", 301)
	})
	// NOTE: NewScheduleWeekController assumes 'timeslot' is installed BEFORE it is called.
	schedWeekC := controllers.NewScheduleWeekController(session, getRouter, c)
	getRouter.HandleFunc("/schedule/thisweek/", schedWeekC.GetThisWeek).Name("schedule-thisweek")

	getRouter.HandleFunc("/schedule/{year:[1-9][0-9][0-9][0-9]}/w{week:[0-5]?[0-9]}/", schedWeekC.GetByYearWeek).Name("schedule-week")
	// This route exists so that day schedule links from the previous website aren't broken.
	getRouter.HandleFunc("/schedule/{year:[1-9][0-9][0-9][0-9]}/w{week:[0-5]?[0-9]}/{day:[1-7]}/", schedWeekC.GetByYearWeek).Name("schedule-week-day-compat")

	onDemandC := controllers.NewOnDemandController(session, c)
	getRouter.HandleFunc("/ontap/", onDemandC.Get)

	podcastsC := controllers.NewPodcastController(session, c)
	getRouter.HandleFunc("/podcasts/", podcastsC.GetAllPodcasts)
	getRouter.HandleFunc("/podcasts/page/{page:[0-9]+}", podcastsC.GetAllPodcasts)
	getRouter.HandleFunc("/podcasts/{id:[0-9]+}/", podcastsC.Get)
	getRouter.HandleFunc("/podcasts/{id:[0-9]+}/player/", podcastsC.GetEmbed)
	// Redirect old podcast URLs
	getRouter.HandleFunc("/uryplayer/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/ontap/", 301)
	})
	getRouter.HandleFunc("/listen/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/about/", 301)
	})
	getRouter.HandleFunc("/uryplayer/podcasts/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/podcasts/", 301)
	})
	getRouter.HandleFunc("/uryplayer/podcasts/{id:[0-9]+}/", func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		id, _ := strconv.Atoi(vars["id"])
		http.Redirect(w, r, fmt.Sprintf("/podcasts/%d/", id), 301)
	})
	getRouter.HandleFunc("/uryplayer/podcasts/{id:[0-9]+}/player/", func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		id, _ := strconv.Atoi(vars["id"])
		http.Redirect(w, r, fmt.Sprintf("/podcasts/%d/player/", id), 301)
	})

	pc := controllers.NewPeopleController(session, c)
	getRouter.HandleFunc("/people/{id:[0-9]+}/", pc.Get)

	teamC := controllers.NewTeamController(session, c)
	getRouter.HandleFunc("/teams/", teamC.GetAll)
	getRouter.HandleFunc("/teams/{alias}/", teamC.Get)

	getinvolvedC := controllers.NewGetInvolvedController(session, c)
	getRouter.HandleFunc("/getinvolved/", getinvolvedC.Get)

	signupC := controllers.NewSignUpController(session, c)
	postRouter.HandleFunc("/signup/", signupC.Post)

	staticC := controllers.NewStaticController(c)
	getRouter.HandleFunc("/about/", staticC.GetAbout)
	getRouter.HandleFunc("/contact/", staticC.GetContact)
	getRouter.HandleFunc("/competitions/", staticC.GetCompetitions)

	// End routes

	s.UseHandler(router)

	return &s, nil

}
<|endoftext|>


<commit_before>package chipmunk

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/sessions"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
)

var store *sessions.CookieStore

var (
	oauthConf = &oauth2.Config{
		ClientID:     "",
		ClientSecret: "",
		RedirectURL:  "http://127.0.0.1:8080/api/v0/oauth_cb/",
		Scopes:       []string{"https://www.googleapis.com/auth/userinfo.email"},
		Endpoint:     google.Endpoint,
	}
	oauthStateString = strconv.Itoa(rand.Int())
)

var Version string = "dev"
var start time.Time
var users []user
var categories []category

type failure struct {
	Success bool   `json:"success"`
	Error   string `json:"error"`
}

func NewFailure(msg string) *failure {
	return &failure{
		Success: false,
		Error:   msg,
	}
}

type Server struct {
	clientID     string
	clientSecret string
	cookieSecret string
	db           *DB
}

func init() {
	log.SetFlags(log.Ltime)
	start = time.Now()
}

func NewServer(sm *http.ServeMux, clientId, clientSecret, cookieSecret, dbhost, dbname, static string) (*Server, error) {
	db, err := NewDB(dbhost, dbname)
	if err != nil {
		return nil, err
	}
	db.db.SetMaxOpenConns(32)

	server := &Server{
		clientID:     clientId,
		clientSecret: clientSecret,
		cookieSecret: cookieSecret,
		db:           db,
	}
	addRoutes(sm, server, static)
	return server, nil
}

func (s *Server) fakeSetup(w http.ResponseWriter, r *http.Request) {
	//u := userInfo{
	//	Email: "derekmcquay@gmail.com",
	//}
	//addUser(u)
}

//func (s *Server) tranx(w http.ResponseWriter, r *http.Request) {
//	//TODO add back in oauth
//	//w.Header().Set("Content-Type", "application/json")
//	//session, err := store.Get(r, "creds")
//	//if err != nil {
//	//	http.Error(w, err.Error(), http.StatusInternalServerError)
//	//	return
//	//}
//	//if loggedIn := session.Values["authenticated"]; loggedIn != true {
//	//	http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
//	//	return
//	//}
//	switch r.Method {
//	default:
//		b, _ := json.Marshal(NewFailure("Allowed method: POST and GET"))
//		http.Error(w, string(b), http.StatusBadRequest)
//		return
//	case "GET":
//		u, err := getUser("derekmcquay@gmail.com") //TODO will grab this from session
//		if err != nil {
//			b, _ := json.Marshal(NewFailure(err.Error()))
//			http.Error(w, string(b), http.StatusInternalServerError)
//			return
//		}
//		json.NewEncoder(w).Encode(users[u].txs)
//		if err != nil {
//			http.Error(w, err.Error(), http.StatusInternalServerError)
//			return
//		}
//	case "POST":
//		u, err := getUser("derekmcquay@gmail.com") //TODO will grab this from session
//		if err != nil {
//			b, _ := json.Marshal(NewFailure(err.Error()))
//			http.Error(w, string(b), http.StatusInternalServerError)
//			return
//		}
//
//		t := tranx{}
//		err = json.NewDecoder(r.Body).Decode(&t)
//		if err != nil {
//			http.Error(w, err.Error(), http.StatusBadRequest)
//			return
//		}
//		defer r.Body.Close()
//
//		users[u].txs = append(users[u].txs,
//			tranx{
//				Cost:  t.Cost,
//				Store: t.Store,
//				Info:  t.Info,
//				Month: t.Month,
//			},
//		)
//	}
//}
//
//func (s *Server) costPerMonth(w http.ResponseWriter, r *http.Request) {
//	//TODO add back in oauth
//	//w.Header().Set("Content-Type", "application/json")
//	//session, err := store.Get(r, "creds")
//	//if err != nil {
//	//	http.Error(w, err.Error(), http.StatusInternalServerError)
//	//	return
//	//}
//	//if loggedIn := session.Values["authenticated"]; loggedIn != true {
//	//	http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
//	//	return
//	//}
//	switch r.Method {
//	default:
//		b, _ := json.Marshal(NewFailure("Allowed method: GET"))
//		http.Error(w, string(b), http.StatusBadRequest)
//		return
//	case "GET":
//		u, err := getUser("derekmcquay@gmail.com") //TODO will grab this from session
//		if err != nil {
//			b, _ := json.Marshal(NewFailure(err.Error()))
//			http.Error(w, string(b), http.StatusInternalServerError)
//			return
//		}
//		monthCost := make(map[time.Month]float32)
//		for _, t := range users[u].txs {
//			c, ok := monthCost[t.Month]
//			if !ok {
//				monthCost[t.Month] = t.Cost
//				continue
//			}
//			monthCost[t.Month] = t.Cost + c
//		}
//		err = json.NewEncoder(w).Encode(monthCost)
//		if err != nil {
//			http.Error(w, err.Error(), http.StatusInternalServerError)
//			return
//		}
//	}
//}
//
//func (s *Server) listUsers(w http.ResponseWriter, r *http.Request) {
//	//TODO add back in oauth
//	//w.Header().Set("Content-Type", "application/json")
//	//session, err := store.Get(r, "creds")
//	//if err != nil {
//	//	http.Error(w, err.Error(), http.StatusInternalServerError)
//	//	return
//	//}
//	//if loggedIn := session.Values["authenticated"]; loggedIn != true {
//	//	http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
//	//	return
//	//}
//	switch r.Method {
//	default:
//		b, _ := json.Marshal(NewFailure("Allowed method: GET"))
//		http.Error(w, string(b), http.StatusBadRequest)
//		return
//	case "GET":
//		err := json.NewEncoder(w).Encode(users)
//		if err != nil {
//			http.Error(w, err.Error(), http.StatusInternalServerError)
//			return
//		}
//	}
//}
//
func (s *Server) login(w http.ResponseWriter, r *http.Request) {
	session, _ := store.Get(r, "creds")
	if loggedIn := session.Values["authenticated"]; loggedIn == true {
		http.Redirect(w, r, "/static/", http.StatusTemporaryRedirect)
		return
	}
	oauthConf.ClientID = s.clientID
	oauthConf.ClientSecret = s.clientSecret
	url := oauthConf.AuthCodeURL(oauthStateString, oauth2.AccessTypeOnline)
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func (s *Server) oauthCallback(w http.ResponseWriter, r *http.Request) {
	state := r.FormValue("state")
	if state != oauthStateString {
		log.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	code := r.FormValue("code")
	token, err := oauthConf.Exchange(oauth2.NoContext, code)
	if err != nil {
		log.Printf("oauthConf.Exchange() failed with '%s'\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	oauthClient := oauthConf.Client(oauth2.NoContext, token)

	email, err := oauthClient.Get("https://www.googleapis.com/oauth2/v3/userinfo")
	if err != nil {
		log.Printf("failed with getting userinfo: '%s'\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}
	defer email.Body.Close()
	data, _ := ioutil.ReadAll(email.Body)
	u := userInfo{}
	err = json.Unmarshal(data, &u)
	if err != nil {
		b, _ := json.Marshal(NewFailure(err.Error()))
		http.Error(w, string(b), http.StatusInternalServerError)
		return
	}

	if authorizedEmail(u.Email) {
		session, err := store.Get(r, "creds")
		if err != nil {
			if !session.IsNew {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
		}
		session.Values["authenticated"] = true
		session.Values["uname"] = u.Email
		if err := session.Save(r, w); err != nil {
			http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		}
		// TODO add psql user here
		//addUser(u)
		http.Redirect(w, r, "/static/", http.StatusTemporaryRedirect)
		return
	}
	b, _ := json.Marshal(NewFailure("Not a authorized user"))
	http.Error(w, string(b), http.StatusForbidden)
	return
}

func (s *Server) auth(w http.ResponseWriter, r *http.Request) {
	output := struct {
		Auth bool `json:"auth"`
	}{
		Auth: false,
	}
	w.Header().Set("Content-Type", "application/json")
	session, err := store.Get(r, "creds")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if loggedIn := session.Values["authenticated"]; loggedIn == true {
		output.Auth = true
		json.NewEncoder(w).Encode(output)
		return
	}
	b, _ := json.Marshal(output)
	http.Error(w, string(b), http.StatusUnauthorized)
}

func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
	session, err := store.Get(r, "creds")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	delete(session.Values, "authenticated")
	delete(session.Values, "uname")
	session.Save(r, w)
	http.Redirect(w, r, "/", http.StatusSeeOther)
}

func (s *Server) serverInfo(w http.ResponseWriter, r *http.Request) {
	output := struct {
		Version string `json:"version"`
		Start   string `json:"start"`
		Uptime  string `json:"uptime"`
	}{
		Version: Version,
		Start:   start.Format("2006-01-02 15:04:05"),
		Uptime:  time.Since(start).String(),
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(output)
}

func (s *Server) plist(w http.ResponseWriter, r *http.Request) {
	session, err := store.Get(r, "creds")
	if err != nil {
		if session.IsNew {
			http.Redirect(w, r, "/", http.StatusSeeOther)
			return
		}
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if loggedIn := session.Values["authenticated"]; loggedIn != true {
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}
	data, err := Asset("static/list.html")
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}
	req := bytes.NewReader(data)
	io.Copy(w, req)
}

func (s *Server) health(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Header().Set("Content-Type", "application/json")
	io.WriteString(w, `{"alive": true}`)
}
<commit_msg>get rid of old list route and function<commit_after>package chipmunk

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/sessions"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
)

var store *sessions.CookieStore

var (
	oauthConf = &oauth2.Config{
		ClientID:     "",
		ClientSecret: "",
		RedirectURL:  "http://127.0.0.1:8080/api/v0/oauth_cb/",
		Scopes:       []string{"https://www.googleapis.com/auth/userinfo.email"},
		Endpoint:     google.Endpoint,
	}
	oauthStateString = strconv.Itoa(rand.Int())
)

var Version string = "dev"
var start time.Time
var users []user
var categories []category

type failure struct {
	Success bool   `json:"success"`
	Error   string `json:"error"`
}

func NewFailure(msg string) *failure {
	return &failure{
		Success: false,
		Error:   msg,
	}
}

type Server struct {
	clientID     string
	clientSecret string
	cookieSecret string
	db           *DB
}

func init() {
	log.SetFlags(log.Ltime)
	start = time.Now()
}

func NewServer(sm *http.ServeMux, clientId, clientSecret, cookieSecret, dbhost, dbname, static string) (*Server, error) {
	db, err := NewDB(dbhost, dbname)
	if err != nil {
		return nil, err
	}
	db.db.SetMaxOpenConns(32)

	server := &Server{
		clientID:     clientId,
		clientSecret: clientSecret,
		cookieSecret: cookieSecret,
		db:           db,
	}
	addRoutes(sm, server, static)
	return server, nil
}

func (s *Server) fakeSetup(w http.ResponseWriter, r *http.Request) {
	//u := userInfo{
	//	Email: "derekmcquay@gmail.com",
	//}
	//addUser(u)
}

//func (s *Server) tranx(w http.ResponseWriter, r *http.Request) {
//	//TODO add back in oauth
//	//w.Header().Set("Content-Type", "application/json")
//	//session, err := store.Get(r, "creds")
//	//if err != nil {
//	//	http.Error(w, err.Error(), http.StatusInternalServerError)
//	//	return
//	//}
//	//if loggedIn := session.Values["authenticated"]; loggedIn != true {
//	//	http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
//	//	return
//	//}
//	switch r.Method {
//	default:
//		b, _ := json.Marshal(NewFailure("Allowed method: POST and GET"))
//		http.Error(w, string(b), http.StatusBadRequest)
//		return
//	case "GET":
//		u, err := getUser("derekmcquay@gmail.com") //TODO will grab this from session
//		if err != nil {
//			b, _ := json.Marshal(NewFailure(err.Error()))
//			http.Error(w, string(b), http.StatusInternalServerError)
//			return
//		}
//		json.NewEncoder(w).Encode(users[u].txs)
//		if err != nil {
//			http.Error(w, err.Error(), http.StatusInternalServerError)
//			return
//		}
//	case "POST":
//		u, err := getUser("derekmcquay@gmail.com") //TODO will grab this from session
//		if err != nil {
//			b, _ := json.Marshal(NewFailure(err.Error()))
//			http.Error(w, string(b), http.StatusInternalServerError)
//			return
//		}
//
//		t := tranx{}
//		err = json.NewDecoder(r.Body).Decode(&t)
//		if err != nil {
//			http.Error(w, err.Error(), http.StatusBadRequest)
//			return
//		}
//		defer r.Body.Close()
//
//		users[u].txs = append(users[u].txs,
//			tranx{
//				Cost:  t.Cost,
//				Store: t.Store,
//				Info:  t.Info,
//				Month: t.Month,
//			},
//		)
//	}
//}
//
//func (s *Server) costPerMonth(w http.ResponseWriter, r *http.Request) {
//	//TODO add back in oauth
//	//w.Header().Set("Content-Type", "application/json")
//	//session, err := store.Get(r, "creds")
//	//if err != nil {
//	//	http.Error(w, err.Error(), http.StatusInternalServerError)
//	//	return
//	//}
//	//if loggedIn := session.Values["authenticated"]; loggedIn != true {
//	//	http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
//	//	return
//	//}
//	switch r.Method {
//	default:
//		b, _ := json.Marshal(NewFailure("Allowed method: GET"))
//		http.Error(w, string(b), http.StatusBadRequest)
//		return
//	case "GET":
//		u, err := getUser("derekmcquay@gmail.com") //TODO will grab this from session
//		if err != nil {
//			b, _ := json.Marshal(NewFailure(err.Error()))
//			http.Error(w, string(b), http.StatusInternalServerError)
//			return
//		}
//		monthCost := make(map[time.Month]float32)
//		for _, t := range users[u].txs {
//			c, ok := monthCost[t.Month]
//			if !ok {
//				monthCost[t.Month] = t.Cost
//				continue
//			}
//			monthCost[t.Month] = t.Cost + c
//		}
//		err = json.NewEncoder(w).Encode(monthCost)
//		if err != nil {
//			http.Error(w, err.Error(), http.StatusInternalServerError)
//			return
//		}
//	}
//}

func (s *Server) login(w http.ResponseWriter, r *http.Request) {
	session, _ := store.Get(r, "creds")
	if loggedIn := session.Values["authenticated"]; loggedIn == true {
		http.Redirect(w, r, "/static/", http.StatusTemporaryRedirect)
		return
	}
	oauthConf.ClientID = s.clientID
	oauthConf.ClientSecret = s.clientSecret
	url := oauthConf.AuthCodeURL(oauthStateString, oauth2.AccessTypeOnline)
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func (s *Server) oauthCallback(w http.ResponseWriter, r *http.Request) {
	state := r.FormValue("state")
	if state != oauthStateString {
		log.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	code := r.FormValue("code")
	token, err := oauthConf.Exchange(oauth2.NoContext, code)
	if err != nil {
		log.Printf("oauthConf.Exchange() failed with '%s'\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	oauthClient := oauthConf.Client(oauth2.NoContext, token)

	email, err := oauthClient.Get("https://www.googleapis.com/oauth2/v3/userinfo")
	if err != nil {
		log.Printf("failed with getting userinfo: '%s'\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}
	defer email.Body.Close()
	data, _ := ioutil.ReadAll(email.Body)
	u := userInfo{}
	err = json.Unmarshal(data, &u)
	if err != nil {
		b, _ := json.Marshal(NewFailure(err.Error()))
		http.Error(w, string(b), http.StatusInternalServerError)
		return
	}

	if authorizedEmail(u.Email) {
		session, err := store.Get(r, "creds")
		if err != nil {
			if !session.IsNew {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
		}
		session.Values["authenticated"] = true
		session.Values["uname"] = u.Email
		if err := session.Save(r, w); err != nil {
			http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		}
		// TODO add psql user here
		//addUser(u)
		http.Redirect(w, r, "/static/", http.StatusTemporaryRedirect)
		return
	}
	b, _ := json.Marshal(NewFailure("Not a authorized user"))
	http.Error(w, string(b), http.StatusForbidden)
	return
}

func (s *Server) auth(w http.ResponseWriter, r *http.Request) {
	output := struct {
		Auth bool `json:"auth"`
	}{
		Auth: false,
	}
	w.Header().Set("Content-Type", "application/json")
	session, err := store.Get(r, "creds")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if loggedIn := session.Values["authenticated"]; loggedIn == true {
		output.Auth = true
		json.NewEncoder(w).Encode(output)
		return
	}
	b, _ := json.Marshal(output)
	http.Error(w, string(b), http.StatusUnauthorized)
}

func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
	session, err := store.Get(r, "creds")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	delete(session.Values, "authenticated")
	delete(session.Values, "uname")
	session.Save(r, w)
	http.Redirect(w, r, "/", http.StatusSeeOther)
}

func (s *Server) serverInfo(w http.ResponseWriter, r *http.Request) {
	output := struct {
		Version string `json:"version"`
		Start   string `json:"start"`
		Uptime  string `json:"uptime"`
	}{
		Version: Version,
		Start:   start.Format("2006-01-02 15:04:05"),
		Uptime:  time.Since(start).String(),
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(output)
}

func (s *Server) plist(w http.ResponseWriter, r *http.Request) {
	session, err := store.Get(r, "creds")
	if err != nil {
		if session.IsNew {
			http.Redirect(w, r, "/", http.StatusSeeOther)
			return
		}
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if loggedIn := session.Values["authenticated"]; loggedIn != true {
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}
	data, err := Asset("static/list.html")
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}
	req := bytes.NewReader(data)
	io.Copy(w, req)
}

func (s *Server) health(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Header().Set("Content-Type", "application/json")
	io.WriteString(w, `{"alive": true}`)
}
<|endoftext|>


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

package main

import (
	"net/http"

	"github.com/codegangsta/negroni"
	"github.com/goincremental/negroni-sessions"
	"github.com/goincremental/negroni-sessions/cookiestore"
	"github.com/julienschmidt/httprouter"
	"github.com/repejota/logger"
	"github.com/tvtio/front/routes"
)

// Start starts the HTTP Server of the application
// * Define the routes
// * Setup middleware engine
//
func Start() error {
	l := logger.New("default")

	// Setup middleware
	middle := negroni.Classic()
	store := cookiestore.New([]byte("keyboard cat"))
	middle.Use(sessions.Sessions("tvtio", store))

	// Setup router
	router := httprouter.New()

	router.GET("/", routes.Index)

	router.GET("/search", routes.Search)

	router.GET("/movie/:id", routes.Movie)

	router.GET("/tv/:id", routes.TV)
	router.GET("/tv/:id/season/:snumber", routes.Season)
	router.GET("/tv/:id/season/:snumber/episode/:enumber", routes.Episode)

	router.GET("/person/:id", routes.Person)

	router.GET("/about", routes.About)
	router.GET("/policy", routes.Policy)
	router.GET("/terms", routes.Terms)
	router.GET("/support", routes.Support)

	router.ServeFiles("/css/*filepath", http.Dir("/go/src/github.com/tvtio/front/templates/css"))
	router.ServeFiles("/js/*filepath", http.Dir("/go/src/github.com/tvtio/front/templates/js"))
	router.ServeFiles("/img/*filepath", http.Dir("/go/src/github.com/tvtio/front/templates/img"))

	// Apply middleware to the router
	middle.UseHandler(router)

	l.Infof("Listening at :8080")

	// Start server
	return http.ListenAndServe(":8080", middle)
}
<commit_msg>env var for templates<commit_after>// Copyright 2016 The front Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/codegangsta/negroni"
	"github.com/goincremental/negroni-sessions"
	"github.com/goincremental/negroni-sessions/cookiestore"
	"github.com/julienschmidt/httprouter"
	"github.com/repejota/logger"
	"github.com/tvtio/front/routes"
)

// Start starts the HTTP Server of the application
// * Define the routes
// * Setup middleware engine
//
func Start() error {
	l := logger.New("default")

	// Setup middleware
	middle := negroni.Classic()
	store := cookiestore.New([]byte("keyboard cat"))
	middle.Use(sessions.Sessions("tvtio", store))

	// Setup router
	router := httprouter.New()

	router.GET("/", routes.Index)

	router.GET("/search", routes.Search)

	router.GET("/movie/:id", routes.Movie)

	router.GET("/tv/:id", routes.TV)
	router.GET("/tv/:id/season/:snumber", routes.Season)
	router.GET("/tv/:id/season/:snumber/episode/:enumber", routes.Episode)

	router.GET("/person/:id", routes.Person)

	router.GET("/about", routes.About)
	router.GET("/policy", routes.Policy)
	router.GET("/terms", routes.Terms)
	router.GET("/support", routes.Support)

	router.ServeFiles("/css/*filepath", http.Dir(os.Getenv("TEMPLATEPATH")+"/css"))
	router.ServeFiles("/js/*filepath", http.Dir(os.Getenv("TEMPLATEPATH")+"/js"))
	router.ServeFiles("/img/*filepath", http.Dir(os.Getenv("TEMPLATEPATH")+"/img"))

	fmt.Println(os.Getenv("TEMPLATEPATH") + "/css")

	// Apply middleware to the router
	middle.UseHandler(router)

	l.Infof("Listening at :8080")

	// Start server
	return http.ListenAndServe(":8080", middle)
}
<|endoftext|>


<commit_before>package cloudformation

// IAMPolicyDocument represents an IAM policy document
type IAMPolicyDocument struct {
	Version   string `json:",omitempty"`
	Statement []IAMPolicyStatement
}

// IAMPrincipal represents a principal in an IAM policy
type IAMPrincipal struct {
	Service *StringListExpr `json:",omitempty"`
}

// IAMPolicyStatement represents an IAM policy statement
type IAMPolicyStatement struct {
	Sid            string          `json:",omitempty"`
	Effect         string          `json:",omitempty"`
	Principal      *IAMPrincipal   `json:",omitempty"`
	Action         *StringListExpr `json:",omitempty"`
	Resource       *StringListExpr `json:",omitempty"`
	ConditionBlock interface{}     `json:",omitempty"`
}
<commit_msg>update IAM data types<commit_after>package cloudformation

// IAMPolicyDocument represents an IAM policy document
type IAMPolicyDocument struct {
	Version   string `json:",omitempty"`
	Statement []IAMPolicyStatement
}

// IAMPrincipal represents a principal in an IAM policy
type IAMPrincipal struct {
	Service *StringListExpr `json:",omitempty"`
	AWS     *StringListExpr `json:",omitempty"`
}

// IAMPolicyStatement represents an IAM policy statement
type IAMPolicyStatement struct {
	Sid       string          `json:",omitempty"`
	Effect    string          `json:",omitempty"`
	Principal *IAMPrincipal   `json:",omitempty"`
	Action    *StringListExpr `json:",omitempty"`
	Resource  *StringListExpr `json:",omitempty"`
	Condition interface{}     `json:",omitempty"`
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"reflect"
	"testing"
)

func testIconsFindHelper(terms []string) icons {
	return newIcons().find(terms)
}

func TestIcons_iconsYamlPath_TestEnv(t *testing.T) {
	actual := iconsYamlPath()
	expected := "workflow/icons.yml"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_iconsYamlPath_ProductionEnv(t *testing.T) {
	resetEnv := setTestEnvHelper("FAW_ICONS_YAML_PATH", "")
	defer resetEnv()

	actual := iconsYamlPath()
	expected := "icons.yml"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_iconsReadYaml(t *testing.T) {
	path := "workflow/icons.yml"
	actual, _ := iconsReadYaml(path)

	expected, _ := ioutil.ReadFile(path)
	if !reflect.DeepEqual(actual, expected) {
		t.Error("failed to read file")
	}
}

func TestIcons_iconsReadYaml_Error(t *testing.T) {
	path := ""
	_, err := iconsReadYaml(path)

	if err == nil {
		t.Error("expected error, but nil")
	}
}

func TestIcons_iconsUnmarshalYaml(t *testing.T) {
	b := []byte(`
icons:
- name: Accessible Icon
  id: accessible-icon
  unicode: f368
  created: 5.0.0
  filter:
  - accessibility
  - wheelchair
  - handicap
  - person
  - wheelchair-alt
  categories: unknown
`)
	actual, _ := iconsUnmarshalYaml(b)

	icon := icon{
		Name:    "Accessible Icon",
		ID:      "accessible-icon",
		Unicode: "f368",
		Created: "5.0.0",
		Filter:  []string{"accessibility", "wheelchair", "handicap", "person", "wheelchair-alt"},
	}
	expected := iconsYaml{icons{icon}}
	if !reflect.DeepEqual(actual, expected) {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_AllIcons(t *testing.T) {
	fi := testIconsFindHelper([]string{""})

	actual := len(fi)
	expected := 1238
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_ZeroIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{"foo-bar-baz"})

	actual := len(fi)
	expected := 0
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_OneIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{"github-square"})

	actual := len(fi)
	expected := 1
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_TwoIcons(t *testing.T) {
	fi := testIconsFindHelper([]string{"github-"})

	actual := len(fi)
	expected := 2
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_FirstIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{""})

	actual := fi[0].ID
	expected := "500px"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_LastIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{""})

	actual := fi[len(fi)-1].ID
	expected := "zhihu"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_TaxiIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{"taxi"})

	actual := fi[0].Name
	expected := "Taxi"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].ID
	expected = "taxi"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Unicode
	expected = "f1ba"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Created
	expected = "4.1"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Filter[0]
	expected = "cab"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	// actual = fi[0].Categories[0]
	// expected = "Web Application Icons"
	// if actual != expected {
	// 	t.Errorf("expected %v to eq %v", actual, expected)
	// }
}

// func TestIcons_find_Aliases(t *testing.T) {
// 	fi := testIconsFindHelper([]string{"navicon"})

// 	actual := fi[0].ID
// 	expected := "bars"
// 	if actual != expected {
// 		t.Errorf("expected %v to eq %v", actual, expected)
// 	}

// 	if len(fi) != 1 {
// 		t.Errorf("expected %v to eq %v", len(fi), 1)
// 	}
// }

func TestIcons_findByUnicode(t *testing.T) {
	fi := newIcons().findByUnicode("f067")

	actual := fi[0].ID
	expected := "plus"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	if len(fi) != 1 {
		t.Errorf("expected %v to eq %v", len(fi), 1)
	}
}
<commit_msg>(Font Awesome 5.5.0) Fix test<commit_after>package main

import (
	"io/ioutil"
	"reflect"
	"testing"
)

func testIconsFindHelper(terms []string) icons {
	return newIcons().find(terms)
}

func TestIcons_iconsYamlPath_TestEnv(t *testing.T) {
	actual := iconsYamlPath()
	expected := "workflow/icons.yml"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_iconsYamlPath_ProductionEnv(t *testing.T) {
	resetEnv := setTestEnvHelper("FAW_ICONS_YAML_PATH", "")
	defer resetEnv()

	actual := iconsYamlPath()
	expected := "icons.yml"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_iconsReadYaml(t *testing.T) {
	path := "workflow/icons.yml"
	actual, _ := iconsReadYaml(path)

	expected, _ := ioutil.ReadFile(path)
	if !reflect.DeepEqual(actual, expected) {
		t.Error("failed to read file")
	}
}

func TestIcons_iconsReadYaml_Error(t *testing.T) {
	path := ""
	_, err := iconsReadYaml(path)

	if err == nil {
		t.Error("expected error, but nil")
	}
}

func TestIcons_iconsUnmarshalYaml(t *testing.T) {
	b := []byte(`
icons:
- name: Accessible Icon
  id: accessible-icon
  unicode: f368
  created: 5.0.0
  filter:
  - accessibility
  - wheelchair
  - handicap
  - person
  - wheelchair-alt
  categories: unknown
`)
	actual, _ := iconsUnmarshalYaml(b)

	icon := icon{
		Name:    "Accessible Icon",
		ID:      "accessible-icon",
		Unicode: "f368",
		Created: "5.0.0",
		Filter:  []string{"accessibility", "wheelchair", "handicap", "person", "wheelchair-alt"},
	}
	expected := iconsYaml{icons{icon}}
	if !reflect.DeepEqual(actual, expected) {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_AllIcons(t *testing.T) {
	fi := testIconsFindHelper([]string{""})

	actual := len(fi)
	expected := 1259
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_ZeroIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{"foo-bar-baz"})

	actual := len(fi)
	expected := 0
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_OneIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{"github-square"})

	actual := len(fi)
	expected := 1
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_TwoIcons(t *testing.T) {
	fi := testIconsFindHelper([]string{"github-"})

	actual := len(fi)
	expected := 2
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_FirstIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{""})

	actual := fi[0].ID
	expected := "500px"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_LastIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{""})

	actual := fi[len(fi)-1].ID
	expected := "zhihu"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}
}

func TestIcons_find_TaxiIcon(t *testing.T) {
	fi := testIconsFindHelper([]string{"taxi"})

	actual := fi[0].Name
	expected := "Taxi"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].ID
	expected = "taxi"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Unicode
	expected = "f1ba"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Created
	expected = "4.1"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	actual = fi[0].Filter[0]
	expected = "cab"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	// actual = fi[0].Categories[0]
	// expected = "Web Application Icons"
	// if actual != expected {
	// 	t.Errorf("expected %v to eq %v", actual, expected)
	// }
}

// func TestIcons_find_Aliases(t *testing.T) {
// 	fi := testIconsFindHelper([]string{"navicon"})

// 	actual := fi[0].ID
// 	expected := "bars"
// 	if actual != expected {
// 		t.Errorf("expected %v to eq %v", actual, expected)
// 	}

// 	if len(fi) != 1 {
// 		t.Errorf("expected %v to eq %v", len(fi), 1)
// 	}
// }

func TestIcons_findByUnicode(t *testing.T) {
	fi := newIcons().findByUnicode("f067")

	actual := fi[0].ID
	expected := "plus"
	if actual != expected {
		t.Errorf("expected %v to eq %v", actual, expected)
	}

	if len(fi) != 1 {
		t.Errorf("expected %v to eq %v", len(fi), 1)
	}
}
<|endoftext|>


<commit_before>package geoy

type LatLoner interface {
	LatLon() []float64
}

type LatLon struct {
	Lat float64 `json:"lat"`
	Lon float64 `json:"lon"`
}

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

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

func NewPoint(x, y float64) *Point {
	p := Point{Type: "point", Coordinates: []float64{x, y}}
	return &p
}

// LatLon returns an array of [lat, lon]
func (p Point) LatLon() []float64 {
	return []float64{p.Coordinates[1], p.Coordinates[0]}
}

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

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

// PointFromLatLon converts a latLon to a xy Point
func PointFromLatLon(latlon LatLoner) *Point {
	l := latlon.LatLon()
	x, y := l[1], l[0]
	return NewPoint(x, y)
}

// LatLonFromPoint converts a Point to a LatLon
func LatLonFromPoint(p Point) *LatLon {
	return &LatLon{Lat: p.Coordinates[1], Lon: 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
}

func (l InstagramLocation) LatLon() []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
<commit_msg>Added wkt encoding and wkb decoding to Point type for interfacing with PostGIS.<commit_after>package geoy

type LatLoner interface {
	LatLon() []float64
}

type LatLon struct {
	Lat float64 `json:"lat"`
	Lon float64 `json:"lon"`
}

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

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

func NewPoint(x, y float64) *Point {
	p := Point{Type: "point", Coordinates: []float64{x, y}}
	return &p
}

// LatLon returns an array of [lat, lon]
func (p Point) LatLon() []float64 {
	return []float64{p.Coordinates[1], p.Coordinates[0]}
}

func (p Point) MarshalDB() (interface{}, error) {
	return p.WKT(), nil
}

func (p *Point) UnmarshalDB(v interface{}) error {
	b, err := hex.DecodeString(v.(string))
	if err != nil {
		return err
	}
	buf := bytes.NewReader(b)
	type pointEWKB struct {
		Endiness byte
		Type     uint32
		SRID     uint32
		X        float64
		Y        float64
	}
	var ewkb pointEWKB
	err = binary.Read(buf, binary.LittleEndian, &ewkb)
	if err != nil {
		fmt.Printf("%v", err)
		return err
	}
	p.Type = "point"
	p.Coordinates = []float64{ewkb.X, ewkb.Y}
	return nil
}

func (p Point) WKT() string {
	return fmt.Sprintf("POINT(%0.6f %0.6f)", p.Coordinates[0], p.Coordinates[1])
}

func (p Point) String() string {
	return p.WKT()
}

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

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

// PointFromLatLon converts a latLon to a xy Point
func PointFromLatLon(latlon LatLoner) *Point {
	l := latlon.LatLon()
	x, y := l[1], l[0]
	return NewPoint(x, y)
}

// LatLonFromPoint converts a Point to a LatLon
func LatLonFromPoint(p Point) *LatLon {
	return &LatLon{Lat: p.Coordinates[1], Lon: 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
}

func (l InstagramLocation) LatLon() []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
<|endoftext|>


<commit_before>package in

import (
	"archive/zip"
	"bufio"
	"io"
	"os"

	"archive/tar"
	"compress/gzip"
	"github.com/h2non/filetype"
	"path/filepath"
)

const (
	mimeTypeZip  = "application/zip"
	mimeTypeTar  = "application/x-tar"
	mimeTypeGzip = "application/gzip"
)

func isSupportedMimeType(mimeType string) bool {
	return mimeType == mimeTypeZip ||
		mimeType == mimeTypeTar ||
		mimeType == mimeTypeGzip
}

func getMimeType(path string) (string, error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()

	bs, err := bufio.NewReader(f).Peek(512)
	if err != nil && err != io.EOF {
		return "", err
	}

	kind, err := filetype.Match(bs)
	if err != nil {
		return "", err
	}

	return kind.MIME.Value, nil
}

func unpack(mimeType, sourcePath string) error {
	for mimeType == mimeTypeGzip {
		var err error
		sourcePath, err = unpackGzip(sourcePath)
		if err != nil {
			return err
		}

		mimeType, err = getMimeType(sourcePath)
		if err != nil {
			return err
		}
	}

	destinationDir := filepath.Dir(sourcePath)

	switch mimeType {
	case mimeTypeZip:
		return unpackZip(sourcePath, destinationDir)
	case mimeTypeTar:
		return unpackTar(sourcePath, destinationDir)
	}

	return nil
}

func unpackZip(sourcePath, destinationDir string) error {
	r, err := zip.OpenReader(sourcePath)
	if err != nil {
		return err
	}
	defer r.Close()

	for _, f := range r.File {
		rc, err := f.Open()
		if err != nil {
			return err
		}
		defer rc.Close()

		fpath := filepath.Join(destinationDir, f.Name)

		if f.FileInfo().IsDir() {
			if err := os.MkdirAll(fpath, os.ModePerm); err != nil {
				return err
			}
		} else {
			if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
				return err
			}

			outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
			if err != nil {
				return err
			}

			_, err = io.Copy(outFile, rc)
			outFile.Close()

			if err != nil {
				return err
			}
		}
	}

	return nil
}

func unpackGzip(sourcePath string) (string, error) {
	reader, err := os.Open(sourcePath)
	if err != nil {
		return "", err
	}
	defer reader.Close()

	archive, err := gzip.NewReader(reader)
	if err != nil {
		return "", err
	}
	defer archive.Close()

	var destinationPath string
	if archive.Name != "" {
		destinationPath = filepath.Join(filepath.Dir(sourcePath), archive.Name)
	} else {
		destinationPath = sourcePath + ".uncompressed"
	}

	writer, err := os.Create(destinationPath)
	if err != nil {
		return "", err
	}
	defer writer.Close()

	_, err = io.Copy(writer, archive)
	return destinationPath, err
}

func unpackTar(sourcePath, destinationDir string) error {
	reader, err := os.Open(sourcePath)
	if err != nil {
		return err
	}
	defer reader.Close()

	tarReader := tar.NewReader(reader)

	for {
		header, err := tarReader.Next()
		if err == io.EOF {
			break
		} else if err != nil {
			return err
		}

		path := filepath.Join(destinationDir, header.Name)
		info := header.FileInfo()
		if info.IsDir() {
			if err = os.MkdirAll(path, info.Mode()); err != nil {
				return err
			}

			continue
		}

		file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
		if err != nil {
			return err
		}

		_, err = io.Copy(file, tarReader)
		file.Close()

		if err != nil {
			return err
		}

	}

	return nil
}
<commit_msg>using tar/unzip util to handle archive instead of golang lib<commit_after>package in

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"

	"github.com/h2non/filetype"
	"path/filepath"
)

const (
	mimeTypeZip  = "application/zip"
	mimeTypeTar  = "application/x-tar"
	mimeTypeGzip = "application/gzip"
)

func isSupportedMimeType(mimeType string) bool {
	return mimeType == mimeTypeZip ||
		mimeType == mimeTypeTar ||
		mimeType == mimeTypeGzip
}

func getMimeType(path string) (string, error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()

	bs, err := bufio.NewReader(f).Peek(512)
	if err != nil && err != io.EOF {
		return "", err
	}

	kind, err := filetype.Match(bs)
	if err != nil {
		return "", err
	}

	return kind.MIME.Value, nil
}

func unpack(mimeType, sourcePath string) error {
	for mimeType == mimeTypeGzip {
		var err error
		sourcePath, err = unpackGzip(sourcePath)
		if err != nil {
			return err
		}

		mimeType, err = getMimeType(sourcePath)
		if err != nil {
			return err
		}
	}

	destinationDir := filepath.Dir(sourcePath)

	switch mimeType {
	case mimeTypeZip:
		return unpackZip(sourcePath, destinationDir)
	case mimeTypeTar:
		return unpackTar(sourcePath, destinationDir)
	}

	return nil
}

func unpackZip(sourcePath, destinationDir string) error {
	cmd := exec.Command("unzip", "-P", "", "-d", destinationDir, sourcePath)
	defer os.Remove(sourcePath)

	return cmd.Run()
}

func unpackGzip(sourcePath string) (string, error) {
	cmd := exec.Command("gunzip", sourcePath)
	err := cmd.Run()
	if err != nil {
		return "", err
	}

	destinationDir := filepath.Dir(sourcePath)
	fileInfos, err := ioutil.ReadDir(destinationDir)
	if err != nil {
		return "", fmt.Errorf("failed to read dir: %s", err)
	}
	if len(fileInfos) != 1 {
		return "", fmt.Errorf("%d files found after gunzip; expected 1", len(fileInfos))
	}

	return filepath.Join(destinationDir, fileInfos[0].Name()), err
}

func unpackTar(sourcePath, destinationDir string) error {
	cmd := exec.Command("tar", "xf", sourcePath, "-C", destinationDir)
	defer os.Remove(sourcePath)

	return cmd.Run()
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/streadway/amqp"
	"koding/tools/amqputil"
	"strconv"
)

var scale int = 1e4

func userNum(i int) string {
	return "user" + strconv.Itoa(i)
}

func main() {
	startPublishing()
}

func startPublishing() {
	conn := amqputil.CreateConnection("ffproduce")
	channel := amqputil.CreateChannel(conn)

	for i := 0; i < scale; i++ {

		key := userNum(i)

		if err := channel.Publish(
			key,   // exchange
			key,   // routing key
			false, // mandatory
			false, // immediate
			amqp.Publishing{
				Headers:         amqp.Table{},
				ContentType:     "text/plain",
				ContentEncoding: "",
				Body:            []byte("hello to " + key),
				DeliveryMode:    amqp.Transient, // 1=non-persistent, 2=persistent
				Priority:        0,              // 0-9
				// a bunch of application/implementation-specific fields
			},
		); err != nil {
			panic(err)
		}
	}
}
<commit_msg>followfeed: stress test; use a goroutine<commit_after>package main

import (
	"github.com/streadway/amqp"
	"koding/tools/amqputil"
	"strconv"
)

var scale int = 1e4

func userNum(i int) string {
	return "user" + strconv.Itoa(i)
}

func main() {
	startPublishing()
}

func startPublishing() {
	conn := amqputil.CreateConnection("ffproduce")
	channel := amqputil.CreateChannel(conn)

	for i := 0; i < scale; i++ {
		go func() {
			key := userNum(i)

			if err := channel.Publish(
				key,   // exchange
				key,   // routing key
				false, // mandatory
				false, // immediate
				amqp.Publishing{
					Headers:         amqp.Table{},
					ContentType:     "text/plain",
					ContentEncoding: "",
					Body:            []byte("hello to " + key),
					DeliveryMode:    amqp.Transient, // 1=non-persistent, 2=persistent
					Priority:        0,              // 0-9
					// a bunch of application/implementation-specific fields
				},
			); err != nil {
				panic(err)
			}
		}()
	}
}
<|endoftext|>


<commit_before>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package binlog

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"path"
	"regexp"
	"strconv"
	"time"

	log "github.com/golang/glog"
	"github.com/youtube/vitess/go/sync2"
	"github.com/youtube/vitess/go/vt/binlog/proto"
	"github.com/youtube/vitess/go/vt/mysqlctl"
	myproto "github.com/youtube/vitess/go/vt/mysqlctl/proto"
)

var (
	// posRE is for extracting server id, end_log_pos and group_id.
	posRE = regexp.MustCompile(`^#.*server id ([0-9]+).*end_log_pos ([0-9]+).*group_id ([0-9]+)`)

	// rotateRE is for extracting log rotation info.
	rotateRE = regexp.MustCompile(`^#.*Rotate to ([^ \t]+).*pos: ([0-9]+)`)

	// delimRE is for extracting the delimeter.
	delimRE = regexp.MustCompile(`^DELIMITER[ \t]+(.*;)`)

	// eolfPrefix is for detecting the end of log file marker.
	eolfPrefix = []byte(`# End of log file`)

	// ignorePrefixes defines the prefixes that can be ignored.
	ignorePrefixes = [][]byte{
		[]byte("#"),
		[]byte("use"),
		[]byte("USE"),
		[]byte("/*"),
		[]byte("SET @@session"),
		[]byte("BINLOG"),
	}

	// statementPrefixes are normal sql statement prefixes.
	statementPrefixes = map[string]int{
		"begin":    proto.BL_BEGIN,
		"commit":   proto.BL_COMMIT,
		"rollback": proto.BL_ROLLBACK,
		"insert":   proto.BL_DML,
		"update":   proto.BL_DML,
		"delete":   proto.BL_DML,
		"create":   proto.BL_DDL,
		"alter":    proto.BL_DDL,
		"drop":     proto.BL_DDL,
		"truncate": proto.BL_DDL,
		"rename":   proto.BL_DDL,
		"set":      proto.BL_SET,
	}

	// Misc vars.
	HASH_COMMENT  = []byte("#")
	SLASH_COMMENT = []byte("/*")
	DELIM_STMT    = []byte("DELIMITER")
	DEFAULT_DELIM = []byte(";")
)

// binlogFileStreamer only supports Google MySQL. Support for other flavors will
// come with the switch to a connection-based streamer.
const blsMysqlFlavor = "GoogleMysql"

// binlogFileStreamer streams binlog events from a set of local files.
type binlogFileStreamer struct {
	// dbname, dir, and mysqld are set at creation.
	dbname string
	dir    string
	mysqld *mysqlctl.Mysqld

	svm sync2.ServiceManager

	// file, blPos & delim are updated during streaming.
	file  fileInfo
	blPos binlogPosition
	delim []byte
}

// newBinlogFileStreamer creates a BinlogStreamer. dbname specifes
// the db to stream events for, and binlogPrefix is as defined
// by the mycnf variable.
func newBinlogFileStreamer(dbname string, mysqld *mysqlctl.Mysqld) BinlogStreamer {
	return &binlogFileStreamer{
		dbname: dbname,
		dir:    path.Dir(mysqld.Cnf().BinLogPath),
		mysqld: mysqld,
	}
}

// streamFilePos starts streaming events from a given file and position.
func (bls *binlogFileStreamer) streamFilePos(file string, pos int64, sendTransaction sendTransactionFunc) (err error) {
	if err = bls.file.Init(path.Join(bls.dir, file), pos); err != nil {
		return err
	}
	defer bls.file.Close()

	// Launch using service manager so we can stop this
	// as needed.
	bls.svm.Go(func(_ *sync2.ServiceManager) {
		for {
			if err = bls.run(sendTransaction); err != nil {
				return
			}
			if err = bls.file.WaitForChange(&bls.svm); err != nil {
				return
			}
		}
	})

	// Wait for service to exit, and handle errors if any.
	bls.svm.Wait()
	if err == io.EOF {
		log.Infof("Stream ended @ %#v", bls.file)
		return nil
	}
	log.Errorf("Stream error @ %#v, error: %v", bls.file, err)
	return fmt.Errorf("stream error @ %#v, error: %v", bls.file, err)
}

// Stream implements BinlogStreamer.Stream().
func (bls *binlogFileStreamer) Stream(gtid myproto.GTID, sendTransaction sendTransactionFunc) error {
	// Query mysqld to convert GTID to file & pos.
	rp, err := bls.mysqld.BinlogInfo(gtid)
	if err != nil {
		log.Errorf("Unable to serve client request: error computing start position: %v", err)
		return fmt.Errorf("error computing start position: %v", err)
	}
	return bls.streamFilePos(rp.MasterLogFile, int64(rp.MasterLogPosition), sendTransaction)
}

// Stop implements BinlogStreamer.Stop().
func (bls *binlogFileStreamer) Stop() {
	bls.svm.Stop()
}

// run launches mysqlbinlog and starts the stream. It takes care of
// cleaning up the process when streaming returns.
func (bls *binlogFileStreamer) run(sendTransaction sendTransactionFunc) (err error) {
	mbl := &MysqlBinlog{}
	reader, err := mbl.Launch(bls.dbname, bls.file.name, bls.file.pos)
	if err != nil {
		return fmt.Errorf("launch error: %v", err)
	}
	defer reader.Close()
	err = bls.parseEvents(sendTransaction, reader)
	// Always kill because we don't read from reader all the way to EOF.
	// If we wait, we may deadlock.
	mbl.Kill()
	return err
}

// parseEvents parses events and transmits them as transactions for the current mysqlbinlog stream.
func (bls *binlogFileStreamer) parseEvents(sendTransaction sendTransactionFunc, reader io.Reader) (err error) {
	bls.delim = DEFAULT_DELIM
	bufReader := bufio.NewReader(reader)
	var statements []proto.Statement
	var timestamp int64
	for {
		sql, err := bls.nextStatement(bufReader)
		if sql == nil {
			return err
		}
		prefix := string(bytes.ToLower(bytes.SplitN(sql, SPACE, 2)[0]))
		switch category := statementPrefixes[prefix]; category {
		// We trust that mysqlbinlog doesn't send proto.BL_DMLs withot a proto.BL_BEGIN
		case proto.BL_BEGIN:
			statements = nil
			timestamp = 0
		case proto.BL_ROLLBACK:
			bls.file.Save()
			statements = nil
			timestamp = 0
		case proto.BL_DDL:
			statements = append(statements, proto.Statement{Category: category, Sql: sql})
			fallthrough
		case proto.BL_COMMIT:
			trans := &proto.BinlogTransaction{
				Statements: statements,
				Timestamp:  timestamp,
				GTIDField:  myproto.GTIDField{bls.blPos.GTID},
			}
			if err = sendTransaction(trans); err != nil {
				if err == io.EOF {
					return err
				}
				return fmt.Errorf("send reply error: %v", err)
			}
			bls.file.Save()
			statements = nil
			timestamp = 0
		case proto.BL_SET:
			if statements == nil && bytes.HasPrefix(sql, BINLOG_SET_TIMESTAMP) {
				// get the timestamp
				timestamp, _ = strconv.ParseInt(string(sql[BINLOG_SET_TIMESTAMP_LEN:]), 10, 64)
			}
			statements = append(statements, proto.Statement{Category: category, Sql: sql})
		// proto.BL_DML or proto.BL_UNRECOGNIZED
		default:
			statements = append(statements, proto.Statement{Category: category, Sql: sql})
		}
	}
}

// nextStatement returns the next statement encountered in the binlog stream. If there are
// positional comments, it updates the binlogFileStreamer state. It also ignores events that
// are not material. If it returns nil, it's the end of stream. If err is also nil, then
// it was due to a normal termination.
func (bls *binlogFileStreamer) nextStatement(bufReader *bufio.Reader) (stmt []byte, err error) {
eventLoop:
	for {
		// Stop processing if we're shutting down
		if bls.svm.State() != sync2.SERVICE_RUNNING {
			return nil, io.EOF
		}
		event, err := bls.readEvent(bufReader)
		if err != nil {
			if err == io.EOF {
				return nil, nil
			}
			return nil, err
		}
		values := posRE.FindSubmatch(event)
		if values != nil {
			bls.blPos.ServerId = mustParseInt64(values[1])
			bls.file.Set(mustParseInt64(values[2]))
			bls.blPos.GTID = myproto.MustParseGTID(blsMysqlFlavor, string(values[3]))
			continue
		}
		values = rotateRE.FindSubmatch(event)
		if values != nil {
			err = bls.file.Rotate(path.Join(bls.dir, string(values[1])), mustParseInt64(values[2]))
			if err != nil {
				return nil, err
			}
			return nil, nil
		}
		if bytes.HasPrefix(event, eolfPrefix) {
			return nil, nil
		}
		values = delimRE.FindSubmatch(event)
		if values != nil {
			bls.delim = values[1]
			continue
		}
		for _, ignorePrefix := range ignorePrefixes {
			if bytes.HasPrefix(event, ignorePrefix) {
				continue eventLoop
			}
		}
		return event, nil
	}
}

// readEvent reads a single binlog event. It can be a single comment line,
// or multiple lines terminated by the delimiter.
func (bls *binlogFileStreamer) readEvent(bufReader *bufio.Reader) (event []byte, err error) {
	for {
		fragment, err := bufReader.ReadSlice('\n')
		event = append(event, fragment...)
		if err == bufio.ErrBufferFull {
			continue
		}
		if err != nil {
			if err == io.EOF {
				return event, err
			}
			return event, fmt.Errorf("read error: %v", err)
		}
		// Use a different var than event, because you have to keep
		// the trailing \n if we continue
		trimmed := bytes.TrimSpace(event)
		if bytes.HasPrefix(trimmed, HASH_COMMENT) ||
			bytes.HasPrefix(trimmed, SLASH_COMMENT) ||
			bytes.HasPrefix(trimmed, DELIM_STMT) {
			return trimmed, nil
		}
		if bytes.HasSuffix(trimmed, bls.delim) {
			return bytes.TrimSpace(trimmed[:len(trimmed)-len(bls.delim)]), nil
		}
	}
}

// fileInfo is used to track the current binlog file and position.
type fileInfo struct {
	name string
	// pos is the position to be used if mysqlbinlog should be restarted.
	pos int64
	// lastPos is the last position seen by mysqlbinlog. This is different
	// from pos because the corresponding event may not get successfully read.
	// If we restart mysqlbinlog without successfully reading a full transaction,
	// we have to start from the beginning, which will be pos. lastPos is saved
	// into pos when we see a commit or rollback. In case of a rotate, pos is
	// set to the new value and lastPos is reset.
	lastPos int64
	handle  *os.File
}

func (f *fileInfo) Init(name string, pos int64) error {
	err := f.Rotate(name, pos)
	if err != nil {
		return err
	}
	// Make sure the current file hasn't rotated.
	next := nextFileName(name)
	fi, _ := os.Stat(next)
	if fi == nil {
		// Assume next file doesn't exist
		return nil
	}

	// Next file exists. Check if current file size matches position
	fi, err = f.handle.Stat()
	if err != nil {
		return err
	}
	if fi.Size() <= pos {
		// The file has rotated
		return f.Rotate(next, 4)
	}
	return nil
}

func (f *fileInfo) Rotate(name string, pos int64) (err error) {
	if f.handle != nil {
		f.handle.Close()
	}
	f.name = name
	f.pos, f.lastPos = pos, 0
	f.handle, err = os.Open(name)
	if err != nil {
		// Sometimes, the new file is not ready yet.
		// Retry once after a delay.
		time.Sleep(1 * time.Second)
		f.handle, err = os.Open(name)
		if err != nil {
			return fmt.Errorf("open error: %v", err)
		}
	}
	return nil
}

func (f *fileInfo) Set(pos int64) {
	f.lastPos = pos
}

func (f *fileInfo) Save() {
	if f.lastPos != 0 {
		f.pos, f.lastPos = f.lastPos, 0
	}
}

func (f *fileInfo) WaitForChange(svm *sync2.ServiceManager) error {
	for {
		// Stop waiting if we're shutting down
		if svm.State() != sync2.SERVICE_RUNNING {
			return io.EOF
		}
		time.Sleep(100 * time.Millisecond)
		fi, err := f.handle.Stat()
		if err != nil {
			return fmt.Errorf("stat error: %v", err)
		}
		if fi.Size() != f.lastPos {
			return nil
		}
	}
}

func (f *fileInfo) Close() (err error) {
	if f.handle == nil {
		return nil
	}
	err = f.handle.Close()
	f.handle = nil
	if err != nil {
		return fmt.Errorf("close error: %v", err)
	}
	return nil
}

func nextFileName(name string) string {
	newname := []byte(name)
	index := len(newname) - 1
	for newname[index] == '9' && index > 0 {
		newname[index] = '0'
		index--
	}
	newname[index] += 1
	return string(newname)
}

// mustParseInt64 can be used if you don't expect to fail.
func mustParseInt64(b []byte) int64 {
	val, err := strconv.ParseInt(string(b), 10, 64)
	if err != nil {
		panic(err)
	}
	return val
}
<commit_msg>binlog_streamer: wait indefinitely on logfile rotation<commit_after>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package binlog

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"path"
	"regexp"
	"strconv"
	"time"

	log "github.com/golang/glog"
	"github.com/youtube/vitess/go/sync2"
	"github.com/youtube/vitess/go/vt/binlog/proto"
	"github.com/youtube/vitess/go/vt/mysqlctl"
	myproto "github.com/youtube/vitess/go/vt/mysqlctl/proto"
)

var (
	// posRE is for extracting server id, end_log_pos and group_id.
	posRE = regexp.MustCompile(`^#.*server id ([0-9]+).*end_log_pos ([0-9]+).*group_id ([0-9]+)`)

	// rotateRE is for extracting log rotation info.
	rotateRE = regexp.MustCompile(`^#.*Rotate to ([^ \t]+).*pos: ([0-9]+)`)

	// delimRE is for extracting the delimeter.
	delimRE = regexp.MustCompile(`^DELIMITER[ \t]+(.*;)`)

	// eolfPrefix is for detecting the end of log file marker.
	eolfPrefix = []byte(`# End of log file`)

	// ignorePrefixes defines the prefixes that can be ignored.
	ignorePrefixes = [][]byte{
		[]byte("#"),
		[]byte("use"),
		[]byte("USE"),
		[]byte("/*"),
		[]byte("SET @@session"),
		[]byte("BINLOG"),
	}

	// statementPrefixes are normal sql statement prefixes.
	statementPrefixes = map[string]int{
		"begin":    proto.BL_BEGIN,
		"commit":   proto.BL_COMMIT,
		"rollback": proto.BL_ROLLBACK,
		"insert":   proto.BL_DML,
		"update":   proto.BL_DML,
		"delete":   proto.BL_DML,
		"create":   proto.BL_DDL,
		"alter":    proto.BL_DDL,
		"drop":     proto.BL_DDL,
		"truncate": proto.BL_DDL,
		"rename":   proto.BL_DDL,
		"set":      proto.BL_SET,
	}

	// Misc vars.
	HASH_COMMENT  = []byte("#")
	SLASH_COMMENT = []byte("/*")
	DELIM_STMT    = []byte("DELIMITER")
	DEFAULT_DELIM = []byte(";")
)

// binlogFileStreamer only supports Google MySQL. Support for other flavors will
// come with the switch to a connection-based streamer.
const blsMysqlFlavor = "GoogleMysql"

// binlogFileStreamer streams binlog events from a set of local files.
type binlogFileStreamer struct {
	// dbname, dir, and mysqld are set at creation.
	dbname string
	dir    string
	mysqld *mysqlctl.Mysqld

	svm sync2.ServiceManager

	// file, blPos & delim are updated during streaming.
	file  fileInfo
	blPos binlogPosition
	delim []byte
}

// newBinlogFileStreamer creates a BinlogStreamer. dbname specifes
// the db to stream events for, and binlogPrefix is as defined
// by the mycnf variable.
func newBinlogFileStreamer(dbname string, mysqld *mysqlctl.Mysqld) BinlogStreamer {
	return &binlogFileStreamer{
		dbname: dbname,
		dir:    path.Dir(mysqld.Cnf().BinLogPath),
		mysqld: mysqld,
	}
}

// streamFilePos starts streaming events from a given file and position.
func (bls *binlogFileStreamer) streamFilePos(file string, pos int64, sendTransaction sendTransactionFunc) (err error) {
	if err = bls.file.Init(path.Join(bls.dir, file), pos); err != nil {
		return err
	}
	defer bls.file.Close()

	// Launch using service manager so we can stop this
	// as needed.
	bls.svm.Go(func(_ *sync2.ServiceManager) {
		for {
			if err = bls.run(sendTransaction); err != nil {
				return
			}
			if err = bls.file.WaitForChange(&bls.svm); err != nil {
				return
			}
		}
	})

	// Wait for service to exit, and handle errors if any.
	bls.svm.Wait()
	if err == io.EOF {
		log.Infof("Stream ended @ %#v", bls.file)
		return nil
	}
	log.Errorf("Stream error @ %#v, error: %v", bls.file, err)
	return fmt.Errorf("stream error @ %#v, error: %v", bls.file, err)
}

// Stream implements BinlogStreamer.Stream().
func (bls *binlogFileStreamer) Stream(gtid myproto.GTID, sendTransaction sendTransactionFunc) error {
	// Query mysqld to convert GTID to file & pos.
	rp, err := bls.mysqld.BinlogInfo(gtid)
	if err != nil {
		log.Errorf("Unable to serve client request: error computing start position: %v", err)
		return fmt.Errorf("error computing start position: %v", err)
	}
	return bls.streamFilePos(rp.MasterLogFile, int64(rp.MasterLogPosition), sendTransaction)
}

// Stop implements BinlogStreamer.Stop().
func (bls *binlogFileStreamer) Stop() {
	bls.svm.Stop()
}

// run launches mysqlbinlog and starts the stream. It takes care of
// cleaning up the process when streaming returns.
func (bls *binlogFileStreamer) run(sendTransaction sendTransactionFunc) (err error) {
	mbl := &MysqlBinlog{}
	reader, err := mbl.Launch(bls.dbname, bls.file.name, bls.file.pos)
	if err != nil {
		return fmt.Errorf("launch error: %v", err)
	}
	defer reader.Close()
	err = bls.parseEvents(sendTransaction, reader)
	// Always kill because we don't read from reader all the way to EOF.
	// If we wait, we may deadlock.
	mbl.Kill()
	return err
}

// parseEvents parses events and transmits them as transactions for the current mysqlbinlog stream.
func (bls *binlogFileStreamer) parseEvents(sendTransaction sendTransactionFunc, reader io.Reader) (err error) {
	bls.delim = DEFAULT_DELIM
	bufReader := bufio.NewReader(reader)
	var statements []proto.Statement
	var timestamp int64
	for {
		sql, err := bls.nextStatement(bufReader)
		if sql == nil {
			return err
		}
		prefix := string(bytes.ToLower(bytes.SplitN(sql, SPACE, 2)[0]))
		switch category := statementPrefixes[prefix]; category {
		// We trust that mysqlbinlog doesn't send proto.BL_DMLs withot a proto.BL_BEGIN
		case proto.BL_BEGIN:
			statements = nil
			timestamp = 0
		case proto.BL_ROLLBACK:
			bls.file.Save()
			statements = nil
			timestamp = 0
		case proto.BL_DDL:
			statements = append(statements, proto.Statement{Category: category, Sql: sql})
			fallthrough
		case proto.BL_COMMIT:
			trans := &proto.BinlogTransaction{
				Statements: statements,
				Timestamp:  timestamp,
				GTIDField:  myproto.GTIDField{bls.blPos.GTID},
			}
			if err = sendTransaction(trans); err != nil {
				if err == io.EOF {
					return err
				}
				return fmt.Errorf("send reply error: %v", err)
			}
			bls.file.Save()
			statements = nil
			timestamp = 0
		case proto.BL_SET:
			if statements == nil && bytes.HasPrefix(sql, BINLOG_SET_TIMESTAMP) {
				// get the timestamp
				timestamp, _ = strconv.ParseInt(string(sql[BINLOG_SET_TIMESTAMP_LEN:]), 10, 64)
			}
			statements = append(statements, proto.Statement{Category: category, Sql: sql})
		// proto.BL_DML or proto.BL_UNRECOGNIZED
		default:
			statements = append(statements, proto.Statement{Category: category, Sql: sql})
		}
	}
}

// nextStatement returns the next statement encountered in the binlog stream. If there are
// positional comments, it updates the binlogFileStreamer state. It also ignores events that
// are not material. If it returns nil, it's the end of stream. If err is also nil, then
// it was due to a normal termination.
func (bls *binlogFileStreamer) nextStatement(bufReader *bufio.Reader) (stmt []byte, err error) {
eventLoop:
	for {
		// Stop processing if we're shutting down
		if bls.svm.State() != sync2.SERVICE_RUNNING {
			return nil, io.EOF
		}
		event, err := bls.readEvent(bufReader)
		if err != nil {
			if err == io.EOF {
				return nil, nil
			}
			return nil, err
		}
		values := posRE.FindSubmatch(event)
		if values != nil {
			bls.blPos.ServerId = mustParseInt64(values[1])
			bls.file.Set(mustParseInt64(values[2]))
			bls.blPos.GTID = myproto.MustParseGTID(blsMysqlFlavor, string(values[3]))
			continue
		}
		values = rotateRE.FindSubmatch(event)
		if values != nil {
			err = bls.file.Rotate(path.Join(bls.dir, string(values[1])), mustParseInt64(values[2]))
			if err != nil {
				return nil, err
			}
			return nil, nil
		}
		if bytes.HasPrefix(event, eolfPrefix) {
			return nil, nil
		}
		values = delimRE.FindSubmatch(event)
		if values != nil {
			bls.delim = values[1]
			continue
		}
		for _, ignorePrefix := range ignorePrefixes {
			if bytes.HasPrefix(event, ignorePrefix) {
				continue eventLoop
			}
		}
		return event, nil
	}
}

// readEvent reads a single binlog event. It can be a single comment line,
// or multiple lines terminated by the delimiter.
func (bls *binlogFileStreamer) readEvent(bufReader *bufio.Reader) (event []byte, err error) {
	for {
		fragment, err := bufReader.ReadSlice('\n')
		event = append(event, fragment...)
		if err == bufio.ErrBufferFull {
			continue
		}
		if err != nil {
			if err == io.EOF {
				return event, err
			}
			return event, fmt.Errorf("read error: %v", err)
		}
		// Use a different var than event, because you have to keep
		// the trailing \n if we continue
		trimmed := bytes.TrimSpace(event)
		if bytes.HasPrefix(trimmed, HASH_COMMENT) ||
			bytes.HasPrefix(trimmed, SLASH_COMMENT) ||
			bytes.HasPrefix(trimmed, DELIM_STMT) {
			return trimmed, nil
		}
		if bytes.HasSuffix(trimmed, bls.delim) {
			return bytes.TrimSpace(trimmed[:len(trimmed)-len(bls.delim)]), nil
		}
	}
}

// fileInfo is used to track the current binlog file and position.
type fileInfo struct {
	name string
	// pos is the position to be used if mysqlbinlog should be restarted.
	pos int64
	// lastPos is the last position seen by mysqlbinlog. This is different
	// from pos because the corresponding event may not get successfully read.
	// If we restart mysqlbinlog without successfully reading a full transaction,
	// we have to start from the beginning, which will be pos. lastPos is saved
	// into pos when we see a commit or rollback. In case of a rotate, pos is
	// set to the new value and lastPos is reset.
	lastPos int64
	handle  *os.File
}

func (f *fileInfo) Init(name string, pos int64) error {
	err := f.Rotate(name, pos)
	if err != nil {
		return err
	}
	// Make sure the current file hasn't rotated.
	next := nextFileName(name)
	fi, _ := os.Stat(next)
	if fi == nil {
		// Assume next file doesn't exist
		return nil
	}

	// Next file exists. Check if current file size matches position
	fi, err = f.handle.Stat()
	if err != nil {
		return err
	}
	if fi.Size() <= pos {
		// The file has rotated
		return f.Rotate(next, 4)
	}
	return nil
}

func (f *fileInfo) Rotate(name string, pos int64) (err error) {
	if f.handle != nil {
		f.handle.Close()
	}
	f.name = name
	f.pos, f.lastPos = pos, 0
	f.handle, err = os.Open(name)
	// If file doesn't exist, wait indefinitely for it to be created.
	for os.IsNotExist(err) {
		// Log the event so we know it's happening.
		log.Infof("Waiting for file %s to be created, retrying in 1 second.", name)
		time.Sleep(1 * time.Second)
		f.handle, err = os.Open(name)
	}
	return err
}

func (f *fileInfo) Set(pos int64) {
	f.lastPos = pos
}

func (f *fileInfo) Save() {
	if f.lastPos != 0 {
		f.pos, f.lastPos = f.lastPos, 0
	}
}

func (f *fileInfo) WaitForChange(svm *sync2.ServiceManager) error {
	for {
		// Stop waiting if we're shutting down
		if svm.State() != sync2.SERVICE_RUNNING {
			return io.EOF
		}
		time.Sleep(100 * time.Millisecond)
		fi, err := f.handle.Stat()
		if err != nil {
			return fmt.Errorf("stat error: %v", err)
		}
		if fi.Size() != f.lastPos {
			return nil
		}
	}
}

func (f *fileInfo) Close() (err error) {
	if f.handle == nil {
		return nil
	}
	err = f.handle.Close()
	f.handle = nil
	if err != nil {
		return fmt.Errorf("close error: %v", err)
	}
	return nil
}

func nextFileName(name string) string {
	newname := []byte(name)
	index := len(newname) - 1
	for newname[index] == '9' && index > 0 {
		newname[index] = '0'
		index--
	}
	newname[index] += 1
	return string(newname)
}

// mustParseInt64 can be used if you don't expect to fail.
func mustParseInt64(b []byte) int64 {
	val, err := strconv.ParseInt(string(b), 10, 64)
	if err != nil {
		panic(err)
	}
	return val
}
<|endoftext|>