<commit_before>package main

import (
  "encoding/json"
  "errors"
  "io/ioutil"
  "log"
  "net/http"
  "os"
  "strings"

  "github.com/urfave/negroni"
  "github.com/unrolled/render"
  "github.com/juz501/go-logger-middleware"
)

func main() {
  logfilename := os.Getenv("GO_LOGFILE")
  if logfilename == "" {
    logfilename = "/var/log/gologs/goapp.log"
  }
  errorLog, err := os.OpenFile(logfilename, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666)
  if err != nil {
    log.Fatal("error writing to log: " + logfilename)
  }
  defer errorLog.Close()

  rend := render.New(render.Options{IsDevelopment: true})
  mux := http.NewServeMux()

  n := negroni.New()
  r := negroni.NewRecovery()
  l := juz501.NewLoggerWithStream( errorLog )
  r.Logger = l
  handleRender(mux, rend, l)
  r.PrintStack = false
  n.Use(r)
  n.Use(l)
  n.UseHandler(mux)
  s := negroni.NewStatic(http.Dir("public"))

  n.Use(s)
  port := ":" + os.Getenv("PORT")
  if port == ":" {
    port = ":80"
  }
  addr := os.Getenv("SERVER_ADDR")
  l.Println("Starting Goapp Service")
  l.Println("----------------------")
  http.ListenAndServe( addr + port, n )
}

func handleRender(mux *http.ServeMux, rend *render.Render, logger juz501.ALogger) {
  mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
    _, _, _, baseURI, templateName, err := getBasePathAndTemplate(req, logger)
    if err != nil {
      return
    }
    data, err := loadData(templateName + ".json", baseURI, logger)

    if err != nil {
      rend.HTML(w, http.StatusServiceUnavailable, "default/dataUnavailable", "")
    } else if false == Exists(templateName, ".tmpl", "templates") {
      rend.HTML(w, http.StatusServiceUnavailable, "default/templateUnavailable", "")
    } else {
      rend.HTML(w, http.StatusOK, templateName, data)
    }
  })
}

func getBasePathAndTemplate(req *http.Request, logger juz501.ALogger) (string, string, string, string, string, error) {
  proto, host, path := getRequestVars(req, logger)
  baseURI := proto + "://" + host + "/"
  templateName, err := getTemplate(req, baseURI, logger)
  return proto, host, path, baseURI, templateName, err
}

func getRequestVars(req *http.Request, logger juz501.ALogger) (string, string, string) {
  forwardedProto := req.Header.Get( "X-Forwarded-Proto" )
  forwardedPath := req.Header.Get( "X-Forwarded-Path" )
  forwardedHost := req.Header.Get( "X-Forwarded-Host" )
  host := req.Host
  if forwardedHost != "" {
    host = forwardedHost
  }
	return forwardedProto, host, forwardedPath
}

func getTemplate(req *http.Request, baseURI string, logger juz501.ALogger) (string, error) {
  var err error
	templateName := strings.TrimSuffix(strings.TrimPrefix(req.RequestURI, "/"), "/")
	if templateName == "" {
		templateName = "index"
	} else if Exists( templateName, "", "public" ) == true {
    err = errors.New("Not a template")
  }
  return templateName, err
}

func Exists(name string, extension string, folder string) bool {
  filename := folder + "/" + name + extension
  _, err := os.Stat( filename )
  if os.IsNotExist(err) {
    return false
  }
  return true
}

func loadData(filename string, basepath string, logger juz501.ALogger) (interface{}, error) {
  var raw []byte
  raw, err := ioutil.ReadFile("data/" + filename)
  if err != nil {
    return raw, err
  }
  var data map[string]interface{}
  err = json.Unmarshal(raw, &data)
  data["BasePath"] = basepath

  return data, err
}
<commit_msg>work out hosts and paths<commit_after>package main

import (
  "encoding/json"
  "errors"
  "io/ioutil"
  "log"
  "net/http"
  "os"
  "strings"

  "github.com/urfave/negroni"
  "github.com/unrolled/render"
  "github.com/juz501/go-logger-middleware"
)

func main() {
  logfilename := os.Getenv("GO_LOGFILE")
  if logfilename == "" {
    logfilename = "/var/log/gologs/goapp.log"
  }
  errorLog, err := os.OpenFile(logfilename, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666)
  if err != nil {
    log.Fatal("error writing to log: " + logfilename)
  }
  defer errorLog.Close()

  rend := render.New(render.Options{IsDevelopment: true})
  mux := http.NewServeMux()

  n := negroni.New()
  r := negroni.NewRecovery()
  l := juz501.NewLoggerWithStream( errorLog )
  r.Logger = l
  handleRender(mux, rend, l)
  r.PrintStack = false
  n.Use(r)
  n.Use(l)
  n.UseHandler(mux)
  s := negroni.NewStatic(http.Dir("public"))

  n.Use(s)
  port := ":" + os.Getenv("PORT")
  if port == ":" {
    port = ":80"
  }
  addr := os.Getenv("SERVER_ADDR")
  l.Println("Starting Goapp Service")
  l.Println("----------------------")
  http.ListenAndServe( addr + port, n )
}

func handleRender(mux *http.ServeMux, rend *render.Render, logger juz501.ALogger) {
  mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
    _, _, _, baseURI, templateName, err := getBasePathAndTemplate(req, logger)
    if err != nil {
      return
    }
    data, err := loadData(templateName + ".json", baseURI, logger)

    if err != nil {
      rend.HTML(w, http.StatusServiceUnavailable, "default/dataUnavailable", "")
    } else if false == Exists(templateName, ".tmpl", "templates") {
      rend.HTML(w, http.StatusServiceUnavailable, "default/templateUnavailable", "")
    } else {
      rend.HTML(w, http.StatusOK, templateName, data)
    }
  })
}

func getBasePathAndTemplate(req *http.Request, logger juz501.ALogger) (string, string, string, string, string, error) {
  proto, host, path := getRequestVars(req, logger)
  baseURI := proto + "://" + host + "/"
  templateName, err := getTemplate(req, baseURI, logger)
  return proto, host, path, baseURI, templateName, err
}

func getRequestVars(req *http.Request, logger juz501.ALogger) (string, string, string) {
  forwardedProto := req.Header.Get( "X-Forwarded-Proto" )
  proto := req.URL.Scheme
  if forwardedProto != "" {
    proto = forwardedProto
  }

  forwardedHost := req.Header.Get( "X-Forwarded-Host" )
  host := req.Host
  if forwardedHost != "" {
    host = forwardedHost
  }
  forwardedPath := req.Header.Get( "X-Forwarded-Path" )
  path := req.URL.Path
  if forwardedPath != "" {
    path = forwardedPath
  }
	return proto, host, path
}

func getTemplate(req *http.Request, baseURI string, logger juz501.ALogger) (string, error) {
  var err error
	templateName := strings.TrimSuffix(strings.TrimPrefix(req.RequestURI, "/"), "/")
	if templateName == "" {
		templateName = "index"
	} else if Exists( templateName, "", "public" ) == true {
    err = errors.New("Not a template")
  }
  return templateName, err
}

func Exists(name string, extension string, folder string) bool {
  filename := folder + "/" + name + extension
  _, err := os.Stat( filename )
  if os.IsNotExist(err) {
    return false
  }
  return true
}

func loadData(filename string, basepath string, logger juz501.ALogger) (interface{}, error) {
  var raw []byte
  raw, err := ioutil.ReadFile("data/" + filename)
  if err != nil {
    return raw, err
  }
  var data map[string]interface{}
  err = json.Unmarshal(raw, &data)
  data["BasePath"] = basepath

  return data, err
}
<|endoftext|>


<commit_before>package main

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

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

const (
	forbiddenMsg = `{"message":"Forbidden"}`
	notFoundMsg  = `{"message":"Not Found"}`
	errMsg       = `{"message":"%s"}`
)

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

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

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

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

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

// App links a Router, ContentStore, and MetaStore to provide the LFS server.
type App struct {
	router       *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()

	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, notFoundMsg)
		}
		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, notFoundMsg)
		}
		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, notFoundMsg)
		}
		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)
}

// 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, notFoundMsg)
		}
		return
	}

	if err := a.contentStore.Put(meta, r.Body); err != nil {
		writeStatus(w, r, 500, errMsg, err)
		return
	}

	logRequest(r, 200)
}

// Represent takes a RequestVars and Meta and turns it into a Representation suitable
// for json encoding
func (a *App) Represent(rv *RequestVars, meta *Meta, 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
}

func writeStatus(w http.ResponseWriter, r *http.Request, status int, format string, a ...interface{}) {
	w.WriteHeader(status)
	fmt.Fprintf(w, format, a)
	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, forbiddenMsg)
}
<commit_msg>Simplify writing status messages<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
}

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

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

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

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

// App links a Router, ContentStore, and MetaStore to provide the LFS server.
type App struct {
	router       *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()

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

// 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 *Meta, 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
}

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>// Package turnpike provides a Websocket Application Messaging Protocol (WAMP) server and client
package turnpike

import (
	"code.google.com/p/go.net/websocket"
	"encoding/json"
	"github.com/nu7hatch/gouuid"
	"io"
	"net"
	"sync"
	"time"
)

var (
	serverBacklog = 20
)

const (
	CLIENT_CONN_TIMEOUT = 6
	CLIENT_MAX_FAILURES = 3
)

type RPCError interface {
	error
	URI() string
	Description() string
	Details() interface{}
}

type listenerMap map[string]bool

func (lm listenerMap) Add(id string) {
	lm[id] = true
}
func (lm listenerMap) Contains(id string) bool {
	return lm[id]
}
func (lm listenerMap) Remove(id string) {
	delete(lm, id)
}

// this may be broken in v2 if multiple-return is implemented
type RPCHandler func(string, string, ...interface{}) (interface{}, error)

type Server struct {
	clients map[string]chan string
	// this is a map because it cheaply prevents a client from subscribing multiple times
	// the nice side-effect here is it's easy to unsubscribe
	//               topicID  clients
	subscriptions map[string]listenerMap
	//           client    prefixes
	prefixes map[string]PrefixMap
	rpcHooks map[string]RPCHandler
	subLock  *sync.Mutex
}

func NewServer() *Server {
	return &Server{
		clients:       make(map[string]chan string),
		subscriptions: make(map[string]listenerMap),
		prefixes:      make(map[string]PrefixMap),
		rpcHooks:      make(map[string]RPCHandler),
		subLock:       new(sync.Mutex)}
}

func (t *Server) handlePrefix(id string, msg PrefixMsg) {
	log.Trace("turnpike: handling prefix message")
	if _, ok := t.prefixes[id]; !ok {
		t.prefixes[id] = make(PrefixMap)
	}
	if err := t.prefixes[id].RegisterPrefix(msg.Prefix, msg.URI); err != nil {
		log.Error("turnpike: error registering prefix: %s", err)
	}
	log.Debug("turnpike: client %s registered prefix '%s' for URI: %s", id, msg.Prefix, msg.URI)
}

func (t *Server) handleCall(id string, msg CallMsg) {
	log.Trace("turnpike: handling call message")
	var out string
	var err error

	if f, ok := t.rpcHooks[msg.ProcURI]; ok && f != nil {
		var res interface{}
		res, err = f(id, msg.ProcURI, msg.CallArgs...)
		if err != nil {
			var errorURI, desc string
			var details interface{}
			if er, ok := err.(RPCError); ok {
				errorURI = er.URI()
				desc = er.Description()
				details = er.Details()
			} else {
				errorURI = msg.ProcURI + "#generic-error"
				desc = err.Error()
			}

			if details != nil {
				out, err = CreateCallError(msg.CallID, errorURI, desc, details)
			} else {
				out, err = CreateCallError(msg.CallID, errorURI, desc)
			}
		} else {
			out, err = CreateCallResult(msg.CallID, res)
		}
	} else {
		log.Warn("turnpike: RPC call not registered: %s", msg.ProcURI)
		out, err = CreateCallError(msg.CallID, "error:notimplemented", "RPC call '%s' not implemented", msg.ProcURI)
	}

	if err != nil {
		// whatever, let the client hang...
		log.Fatal("turnpike: error creating callError message: %s", err)
		return
	}
	if client, ok := t.clients[id]; ok {
		client <- out
	}
}

func (t *Server) handleSubscribe(id string, msg SubscribeMsg) {
	log.Trace("turnpike: handling subscribe message")
	t.subLock.Lock()
	topic := CheckCurie(t.prefixes[id], msg.TopicURI)
	if _, ok := t.subscriptions[topic]; !ok {
		t.subscriptions[topic] = make(map[string]bool)
	}
	t.subscriptions[topic].Add(id)
	t.subLock.Unlock()
	log.Debug("turnpike: client %s subscribed to topic: %s", id, topic)
}

func (t *Server) handleUnsubscribe(id string, msg UnsubscribeMsg) {
	log.Trace("turnpike: handling unsubscribe message")
	t.subLock.Lock()
	topic := CheckCurie(t.prefixes[id], msg.TopicURI)
	if lm, ok := t.subscriptions[topic]; ok {
		lm.Remove(id)
	}
	t.subLock.Unlock()
	log.Debug("turnpike: client %s unsubscribed from topic: %s", id, topic)
}

func (t *Server) handlePublish(id string, msg PublishMsg) {
	log.Trace("turnpike: handling publish message")
	topic := CheckCurie(t.prefixes[id], msg.TopicURI)
	lm, ok := t.subscriptions[topic]
	if !ok {
		return
	}

	out, err := CreateEvent(topic, msg.Event)
	if err != nil {
		log.Error("turnpike: error creating event message: %s", err)
		return
	}

	var sendTo []string
	if len(msg.ExcludeList) > 0 || len(msg.EligibleList) > 0 {
		// this is super ugly, but I couldn't think of a better way...
		for tid := range lm {
			include := true
			for _, _tid := range msg.ExcludeList {
				if tid == _tid {
					include = false
					break
				}
			}
			if include {
				sendTo = append(sendTo, tid)
			}
		}

		for _, tid := range msg.EligibleList {
			include := true
			for _, _tid := range sendTo {
				if _tid == tid {
					include = false
					break
				}
			}
			if include {
				sendTo = append(sendTo, tid)
			}
		}
	} else {
		for tid := range lm {
			if tid == id && msg.ExcludeMe {
				continue
			}
			sendTo = append(sendTo, tid)
		}
	}

	for _, tid := range sendTo {
		// we're not locking anything, so we need
		// to make sure the client didn't disconnecct in the
		// last few nanoseconds...
		if client, ok := t.clients[tid]; ok {
			if len(client) == cap(client) {
				<-client
			}
			client <- string(out)
		}
	}
}

func (t *Server) HandleWebsocket(conn *websocket.Conn) {
	defer conn.Close()

	log.Debug("turnpike: received websocket connection")

	tid, err := uuid.NewV4()
	if err != nil {
		log.Error("turnpike: could not create unique id, refusing client connection")
		return
	}
	id := tid.String()
	log.Info("turnpike: client connected: %s", id)

	arr, err := CreateWelcome(id, TURNPIKE_SERVER_IDENT)
	if err != nil {
		log.Error("turnpike: error encoding welcome message")
		return
	}
	log.Debug("turnpike: sending welcome message: %s", arr)
	err = websocket.Message.Send(conn, string(arr))
	if err != nil {
		log.Error("turnpike: error sending welcome message, aborting connection: %s", err)
		return
	}

	c := make(chan string, serverBacklog)
	t.clients[id] = c

	failures := 0
	go func() {
		for msg := range c {
			log.Trace("turnpike: sending message: %s", msg)
			conn.SetWriteDeadline(time.Now().Add(CLIENT_CONN_TIMEOUT * time.Second))
			err := websocket.Message.Send(conn, msg)
			if err != nil {
				if nErr, ok := err.(net.Error); ok && (nErr.Timeout() || nErr.Temporary()) {
					log.Warn("Network error: %s", nErr)
					failures++
					if failures > CLIENT_MAX_FAILURES {
						break
					}
				} else {
					log.Error("turnpike: error sending message: %s", err)
					break
				}
			}
		}
		log.Info("Client %s disconnected", id)
		conn.Close()
	}()

	for {
		var rec string
		err := websocket.Message.Receive(conn, &rec)
		if err != nil {
			if err != io.EOF {
				log.Error("turnpike: error receiving message, aborting connection: %s", err)
			}
			break
		}
		log.Trace("turnpike: message received: %s", rec)

		data := []byte(rec)

		switch typ := ParseType(rec); typ {
		case PREFIX:
			var msg PrefixMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				log.Error("turnpike: error unmarshalling prefix message: %s", err)
				continue
			}
			t.handlePrefix(id, msg)
		case CALL:
			var msg CallMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				log.Error("turnpike: error unmarshalling call message: %s", err)
				continue
			}
			t.handleCall(id, msg)
		case SUBSCRIBE:
			var msg SubscribeMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				log.Error("turnpike: error unmarshalling subscribe message: %s", err)
				continue
			}
			t.handleSubscribe(id, msg)
		case UNSUBSCRIBE:
			var msg UnsubscribeMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				log.Error("turnpike: error unmarshalling unsubscribe message: %s", err)
				continue
			}
			t.handleUnsubscribe(id, msg)
		case PUBLISH:
			var msg PublishMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				log.Error("turnpike: error unmarshalling publish message: %s", err)
				continue
			}
			t.handlePublish(id, msg)
		case WELCOME, CALLRESULT, CALLERROR, EVENT:
			log.Error("turnpike: server -> client message received, ignored: %s", TypeString(typ))
		default:
			log.Error("turnpike: invalid message format, message dropped: %s", data)
		}
	}

	delete(t.clients, id)
	close(c)
}

func (t *Server) RegisterRPC(uri string, f RPCHandler) {
	if f != nil {
		t.rpcHooks[uri] = f
	}
}

func (t *Server) UnregisterRPC(uri string) {
	delete(t.rpcHooks, uri)
}

func (t *Server) SendEvent(topic string, event interface{}) {
	t.handlePublish(topic, PublishMsg{
		TopicURI: topic,
		Event:    event,
	})
}
<commit_msg>Possible fix for autobahn.js changes<commit_after>// Package turnpike provides a Websocket Application Messaging Protocol (WAMP) server and client
package turnpike

import (
	"code.google.com/p/go.net/websocket"
	"encoding/json"
	"github.com/nu7hatch/gouuid"
	"io"
	"net"
	"net/http"
	"sync"
	"time"
)

var (
	serverBacklog = 20
)

const (
	CLIENT_CONN_TIMEOUT = 6
	CLIENT_MAX_FAILURES = 3
)

type RPCError interface {
	error
	URI() string
	Description() string
	Details() interface{}
}

type listenerMap map[string]bool

func (lm listenerMap) Add(id string) {
	lm[id] = true
}
func (lm listenerMap) Contains(id string) bool {
	return lm[id]
}
func (lm listenerMap) Remove(id string) {
	delete(lm, id)
}

// this may be broken in v2 if multiple-return is implemented
type RPCHandler func(string, string, ...interface{}) (interface{}, error)

type Server struct {
	clients map[string]chan string
	// this is a map because it cheaply prevents a client from subscribing multiple times
	// the nice side-effect here is it's easy to unsubscribe
	//               topicID  clients
	subscriptions map[string]listenerMap
	//           client    prefixes
	prefixes map[string]PrefixMap
	rpcHooks map[string]RPCHandler
	subLock  *sync.Mutex
	websocket.Server
}

func checkWAMPHandshake(config *websocket.Config, req *http.Request) error {
	for _, protocol := range config.Protocol {
		if protocol == "wamp" {
			config.Protocol = []string{protocol}
			return nil
		}
	}
	return websocket.ErrBadWebSocketProtocol
}

func NewServer() *Server {
	s := &Server{
		clients:       make(map[string]chan string),
		subscriptions: make(map[string]listenerMap),
		prefixes:      make(map[string]PrefixMap),
		rpcHooks:      make(map[string]RPCHandler),
		subLock:       new(sync.Mutex),
	}
	s.Server = websocket.Server{
		Handshake: checkWAMPHandshake,
		Handler:   websocket.Handler(s.HandleWebsocket),
	}
	return s
}

func (t *Server) handlePrefix(id string, msg PrefixMsg) {
	log.Trace("turnpike: handling prefix message")
	if _, ok := t.prefixes[id]; !ok {
		t.prefixes[id] = make(PrefixMap)
	}
	if err := t.prefixes[id].RegisterPrefix(msg.Prefix, msg.URI); err != nil {
		log.Error("turnpike: error registering prefix: %s", err)
	}
	log.Debug("turnpike: client %s registered prefix '%s' for URI: %s", id, msg.Prefix, msg.URI)
}

func (t *Server) handleCall(id string, msg CallMsg) {
	log.Trace("turnpike: handling call message")
	var out string
	var err error

	if f, ok := t.rpcHooks[msg.ProcURI]; ok && f != nil {
		var res interface{}
		res, err = f(id, msg.ProcURI, msg.CallArgs...)
		if err != nil {
			var errorURI, desc string
			var details interface{}
			if er, ok := err.(RPCError); ok {
				errorURI = er.URI()
				desc = er.Description()
				details = er.Details()
			} else {
				errorURI = msg.ProcURI + "#generic-error"
				desc = err.Error()
			}

			if details != nil {
				out, err = CreateCallError(msg.CallID, errorURI, desc, details)
			} else {
				out, err = CreateCallError(msg.CallID, errorURI, desc)
			}
		} else {
			out, err = CreateCallResult(msg.CallID, res)
		}
	} else {
		log.Warn("turnpike: RPC call not registered: %s", msg.ProcURI)
		out, err = CreateCallError(msg.CallID, "error:notimplemented", "RPC call '%s' not implemented", msg.ProcURI)
	}

	if err != nil {
		// whatever, let the client hang...
		log.Fatal("turnpike: error creating callError message: %s", err)
		return
	}
	if client, ok := t.clients[id]; ok {
		client <- out
	}
}

func (t *Server) handleSubscribe(id string, msg SubscribeMsg) {
	log.Trace("turnpike: handling subscribe message")
	t.subLock.Lock()
	topic := CheckCurie(t.prefixes[id], msg.TopicURI)
	if _, ok := t.subscriptions[topic]; !ok {
		t.subscriptions[topic] = make(map[string]bool)
	}
	t.subscriptions[topic].Add(id)
	t.subLock.Unlock()
	log.Debug("turnpike: client %s subscribed to topic: %s", id, topic)
}

func (t *Server) handleUnsubscribe(id string, msg UnsubscribeMsg) {
	log.Trace("turnpike: handling unsubscribe message")
	t.subLock.Lock()
	topic := CheckCurie(t.prefixes[id], msg.TopicURI)
	if lm, ok := t.subscriptions[topic]; ok {
		lm.Remove(id)
	}
	t.subLock.Unlock()
	log.Debug("turnpike: client %s unsubscribed from topic: %s", id, topic)
}

func (t *Server) handlePublish(id string, msg PublishMsg) {
	log.Trace("turnpike: handling publish message")
	topic := CheckCurie(t.prefixes[id], msg.TopicURI)
	lm, ok := t.subscriptions[topic]
	if !ok {
		return
	}

	out, err := CreateEvent(topic, msg.Event)
	if err != nil {
		log.Error("turnpike: error creating event message: %s", err)
		return
	}

	var sendTo []string
	if len(msg.ExcludeList) > 0 || len(msg.EligibleList) > 0 {
		// this is super ugly, but I couldn't think of a better way...
		for tid := range lm {
			include := true
			for _, _tid := range msg.ExcludeList {
				if tid == _tid {
					include = false
					break
				}
			}
			if include {
				sendTo = append(sendTo, tid)
			}
		}

		for _, tid := range msg.EligibleList {
			include := true
			for _, _tid := range sendTo {
				if _tid == tid {
					include = false
					break
				}
			}
			if include {
				sendTo = append(sendTo, tid)
			}
		}
	} else {
		for tid := range lm {
			if tid == id && msg.ExcludeMe {
				continue
			}
			sendTo = append(sendTo, tid)
		}
	}

	for _, tid := range sendTo {
		// we're not locking anything, so we need
		// to make sure the client didn't disconnecct in the
		// last few nanoseconds...
		if client, ok := t.clients[tid]; ok {
			if len(client) == cap(client) {
				<-client
			}
			client <- string(out)
		}
	}
}

func (t *Server) HandleWebsocket(conn *websocket.Conn) {
	defer conn.Close()

	log.Debug("turnpike: received websocket connection")

	tid, err := uuid.NewV4()
	if err != nil {
		log.Error("turnpike: could not create unique id, refusing client connection")
		return
	}
	id := tid.String()
	log.Info("turnpike: client connected: %s", id)

	arr, err := CreateWelcome(id, TURNPIKE_SERVER_IDENT)
	if err != nil {
		log.Error("turnpike: error encoding welcome message")
		return
	}
	log.Debug("turnpike: sending welcome message: %s", arr)
	err = websocket.Message.Send(conn, string(arr))
	if err != nil {
		log.Error("turnpike: error sending welcome message, aborting connection: %s", err)
		return
	}

	c := make(chan string, serverBacklog)
	t.clients[id] = c

	failures := 0
	go func() {
		for msg := range c {
			log.Trace("turnpike: sending message: %s", msg)
			conn.SetWriteDeadline(time.Now().Add(CLIENT_CONN_TIMEOUT * time.Second))
			err := websocket.Message.Send(conn, msg)
			if err != nil {
				if nErr, ok := err.(net.Error); ok && (nErr.Timeout() || nErr.Temporary()) {
					log.Warn("Network error: %s", nErr)
					failures++
					if failures > CLIENT_MAX_FAILURES {
						break
					}
				} else {
					log.Error("turnpike: error sending message: %s", err)
					break
				}
			}
		}
		log.Info("Client %s disconnected", id)
		conn.Close()
	}()

	for {
		var rec string
		err := websocket.Message.Receive(conn, &rec)
		if err != nil {
			if err != io.EOF {
				log.Error("turnpike: error receiving message, aborting connection: %s", err)
			}
			break
		}
		log.Trace("turnpike: message received: %s", rec)

		data := []byte(rec)

		switch typ := ParseType(rec); typ {
		case PREFIX:
			var msg PrefixMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				log.Error("turnpike: error unmarshalling prefix message: %s", err)
				continue
			}
			t.handlePrefix(id, msg)
		case CALL:
			var msg CallMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				log.Error("turnpike: error unmarshalling call message: %s", err)
				continue
			}
			t.handleCall(id, msg)
		case SUBSCRIBE:
			var msg SubscribeMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				log.Error("turnpike: error unmarshalling subscribe message: %s", err)
				continue
			}
			t.handleSubscribe(id, msg)
		case UNSUBSCRIBE:
			var msg UnsubscribeMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				log.Error("turnpike: error unmarshalling unsubscribe message: %s", err)
				continue
			}
			t.handleUnsubscribe(id, msg)
		case PUBLISH:
			var msg PublishMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				log.Error("turnpike: error unmarshalling publish message: %s", err)
				continue
			}
			t.handlePublish(id, msg)
		case WELCOME, CALLRESULT, CALLERROR, EVENT:
			log.Error("turnpike: server -> client message received, ignored: %s", TypeString(typ))
		default:
			log.Error("turnpike: invalid message format, message dropped: %s", data)
		}
	}

	delete(t.clients, id)
	close(c)
}

func (t *Server) RegisterRPC(uri string, f RPCHandler) {
	if f != nil {
		t.rpcHooks[uri] = f
	}
}

func (t *Server) UnregisterRPC(uri string) {
	delete(t.rpcHooks, uri)
}

func (t *Server) SendEvent(topic string, event interface{}) {
	t.handlePublish(topic, PublishMsg{
		TopicURI: topic,
		Event:    event,
	})
}
<|endoftext|>


<commit_before>// Image server
package imageserver

// Image server
type Server struct {
	Cache     Cache // optional
	Provider  Provider
	Processor Processor // optional
}

// Get an image
//
// The "source" parameter is required.
//
// Steps
//
// - get image from the cache and return it if available
//
// - get the image from the provider
//
// - process the image
//
// - store the image in the cache
func (server *Server) Get(parameters Parameters) (*Image, error) {
	var cacheKey string
	if server.Cache != nil {
		cacheKey = parameters.Hash()

		image, err := server.Cache.Get(cacheKey, parameters)

		if err == nil {
			return image, nil
		}

		if _, ok := err.(*CacheMissError); !ok {
			return nil, err
		}
	}

	source, err := parameters.Get("source")
	if err != nil {
		return nil, NewError("Missing source parameter")
	}

	image, err := server.Provider.Get(source, parameters)
	if err != nil {
		return nil, err
	}

	if server.Processor != nil {
		image, err = server.Processor.Process(image, parameters)
		if err != nil {
			return nil, err
		}
	}

	if server.Cache != nil {
		go func() {
			_ = server.Cache.Set(cacheKey, image, parameters)
		}()
	}

	return image, nil
}
<commit_msg>lint Server<commit_after>// Package imageserver provides an image server
package imageserver

// Server represents an image server
type Server struct {
	Cache     Cache // optional
	Provider  Provider
	Processor Processor // optional
}

// Get returns an Image for given Parameters
//
// The "source" parameter is required.
//
// Steps
//
// - get image from the cache and return it if available
//
// - get the image from the provider
//
// - process the image
//
// - store the image in the cache
func (server *Server) Get(parameters Parameters) (*Image, error) {
	var cacheKey string
	if server.Cache != nil {
		cacheKey = parameters.Hash()

		image, err := server.Cache.Get(cacheKey, parameters)

		if err == nil {
			return image, nil
		}

		if _, ok := err.(*CacheMissError); !ok {
			return nil, err
		}
	}

	source, err := parameters.Get("source")
	if err != nil {
		return nil, NewError("Missing source parameter")
	}

	image, err := server.Provider.Get(source, parameters)
	if err != nil {
		return nil, err
	}

	if server.Processor != nil {
		image, err = server.Processor.Process(image, parameters)
		if err != nil {
			return nil, err
		}
	}

	if server.Cache != nil {
		go func() {
			_ = server.Cache.Set(cacheKey, image, parameters)
		}()
	}

	return image, nil
}
<|endoftext|>


<commit_before>package main

import(  "fmt"
         "github.com/gorilla/mux"
         "net/http"
         "github.com/kidoman/embd"
       _ "github.com/kidoman/embd/host/rpi" // This loads the RPi driver
         "log"
      )

var light = 1
func Toggle(w http.ResponseWriter, r *http.Request) {
    if light == 1 {
        light = 0
        embd.DigitalWrite(10, embd.High)
        fmt.Fprintf(w, "OFF")
    } else {
        light = 1
        embd.DigitalWrite(10, embd.Low)
        fmt.Fprintf(w, "ON")
    }
}

func main() {
    embd.InitGPIO()
    defer embd.CloseGPIO()
    embd.SetDirection(10, embd.Out)
    r := mux.NewRouter()
    r.HandleFunc("/", Toggle)
    http.Handle("/", r)
    log.Fatal(http.ListenAndServe(":8", r))
}
<commit_msg>Improved implementation<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/kidoman/embd"
	_ "github.com/kidoman/embd/host/rpi" // This loads the RPi driver
)

type WebController struct {
	pin   int
	pinOn bool
}

func newWebController(pinNumber int) *WebController {
	w := new(WebController)
	w.pin = pinNumber
	w.pinOn = false

	return w
}

func (w *WebController) Toggle() (curState bool) {
	if w.pinOn {
		embd.DigitalWrite(w.pin, embd.High)
	} else {
		embd.DigitalWrite(w.pin, embd.Low)
	}

	w.pinOn = !w.pinOn

	return w.pinOn
}

func (wc *WebController) Handle(w http.ResponseWriter, r *http.Request) {
	pinOn := wc.Toggle()

	output := "Device is switched %s"
	if pinOn {
		fmt.Fprintf(w, output, "on")
	} else {
		fmt.Fprintf(w, output, "off")
	}
}

func startHttp() {
	// start web controller on pin 10
	wc := newWebController(10)

	http.HandleFunc("/", wc.Handle)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func main() {
	embd.InitGPIO()
	defer embd.CloseGPIO()
	embd.SetDirection(10, embd.Out)

	startHttp()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/gob"
	"fmt"
	"net"
	"os"
	"os/signal"
	"regexp"
	"strings"
	"syscall"
	"time"
)

type Server struct {
	unixpath string
	paths    Shortcuts
	file     *os.File
}

func NewServer(unixpath string, savefile string) (*Server, error) {
	// Open gob file to load paths
	file, err := os.OpenFile(savefile, os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		return nil, err
	}

	s := &Server{unixpath, NewShortcuts(), file}

	stat, err := file.Stat()
	if err != nil {
		return nil, err
	}

	if stat.Size() > 0 {
		if err = s.Load(); err != nil {
			return nil, err
		}
	}

	return s, nil
}

func (s Server) Load() error {
	return gob.NewDecoder(s.file).Decode(&s.paths)
}

func (s Server) Save() error {
	s.file.Seek(0, 0)
	s.file.Truncate(0)

	return gob.NewEncoder(s.file).Encode(s.paths)
}

func (s *Server) Close() {
	if err := s.Save(); err != nil {
		fmt.Println(err)
	}
	s.file.Close()
}

func (s *Server) complete(req string) (string, error) {
	matched := []string{}

	for key, _ := range s.paths {
		ok, err := regexp.MatchString(req, key)
		if err != nil {
			return "", err
		}

		if ok {
			matched = append(matched, key)
		}
	}

	return strings.Join(matched, "\n"), nil
}

func (s *Server) request(req string) (string, error) {
	// First, return shortcut if exists
	if shortcut := s.paths.Get(req); shortcut != "" {
		return shortcut, nil
	}

	// Here, shortcut not exists. Add it to shortcuts.
	// Add func returns absolute req path
	return s.paths.Add(req)
}

func (s *Server) handleConn(c *net.UnixConn) error {
	defer c.Close()

	// Get request
	var req Request
	err := gob.NewDecoder(c).Decode(&req)
	if err != nil {
		return err
	}
	if err = c.CloseRead(); err != nil {
		return err
	}

	// Treat
	errPath := ""
	var resp string

	fmt.Println("Request:", req)

	if req.Type == CompletionRequest {
		resp, err = s.complete(string(req.Req))
		if err != nil {
			errPath = err.Error()
		}
	} else {
		resp, err = s.request(string(req.Req))
		if err != nil {
			errPath = err.Error()
		}
	}

	// Send response
	err = gob.NewEncoder(c).Encode(Response{resp, errPath})
	if err != nil {
		return err
	}
	if err = c.CloseWrite(); err != nil {
		return err
	}

	return nil
}

func (s *Server) listen() error {
	run := true

	// Signals
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt)
	signal.Notify(signals, os.Kill)
	signal.Notify(signals, syscall.SIGTERM)

	// Conns
	conns := make(chan *net.UnixConn, 100)

	// Listen
	l, err := net.ListenUnix("unix", &net.UnixAddr{s.unixpath, "unix"})
	if err != nil {
		return err
	}

	defer l.Close()
	defer os.Remove(s.unixpath)

	// Listen connections and send them to conns chan
	go func() {
		for run {
			c, err := l.AcceptUnix()
			if err != nil {
				fmt.Println(err)
				continue
			}

			conns <- c
		}
	}()

	fmt.Println("Starting server")

	ticker := time.NewTicker(time.Hour)

	// Wait conn or signal
	for run {
		select {
		case <-ticker.C:
			fmt.Println("Save data")
			if err := s.Save(); err != nil {
				fmt.Println(err)
			}
		case c := <-conns:
			fmt.Println("Got new conn")
			err := s.handleConn(c)
			if err != nil {
				fmt.Println(err)
			}
		case s := <-signals:
			fmt.Println("Got signal:", s)
			run = false
		}
	}

	fmt.Println("Ending server")

	return nil
}
<commit_msg>Improve zsh completion<commit_after>package main

import (
	"encoding/gob"
	"fmt"
	"net"
	"os"
	"os/signal"
	"regexp"
	"strings"
	"syscall"
	"time"
)

type Server struct {
	unixpath string
	paths    Shortcuts
	file     *os.File
}

func NewServer(unixpath string, savefile string) (*Server, error) {
	// Open gob file to load paths
	file, err := os.OpenFile(savefile, os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		return nil, err
	}

	s := &Server{unixpath, NewShortcuts(), file}

	stat, err := file.Stat()
	if err != nil {
		return nil, err
	}

	if stat.Size() > 0 {
		if err = s.Load(); err != nil {
			return nil, err
		}
	}

	return s, nil
}

func (s Server) Load() error {
	return gob.NewDecoder(s.file).Decode(&s.paths)
}

func (s Server) Save() error {
	s.file.Seek(0, 0)
	s.file.Truncate(0)

	return gob.NewEncoder(s.file).Encode(s.paths)
}

func (s *Server) Close() {
	if err := s.Save(); err != nil {
		fmt.Println(err)
	}
	s.file.Close()
}

func (s *Server) complete(req string) (string, error) {
	matched := []string{}

	for key, shortcut := range s.paths {
		ok, err := regexp.MatchString(req, key)
		if err != nil {
			return "", err
		}

		if ok {
			matched = append(matched, key)
		}

		for path, _ := range shortcut.Paths {
			ok, err := regexp.MatchString(req, path)
			if err != nil {
				return "", err
			}

			if ok {
				matched = append(matched, key+":="+path)
			}

		}
	}

	return strings.Join(matched, "\n"), nil
}

func (s *Server) request(req string) (string, error) {
	// For zsh completion, request can have format: "<shortcut>:=<path>"
	// Get real request
	chunks := strings.Split(req, ":=")
	if len(chunks) == 2 {
		req = chunks[1]
	}

	// Return shortcut if exists
	if shortcut := s.paths.Get(req); shortcut != "" {
		return shortcut, nil
	}

	// Here, shortcut not exists. Add it to shortcuts.
	// Add func returns absolute req path
	return s.paths.Add(req)
}

func (s *Server) handleConn(c *net.UnixConn) error {
	defer c.Close()

	// Get request
	var req Request
	err := gob.NewDecoder(c).Decode(&req)
	if err != nil {
		return err
	}
	if err = c.CloseRead(); err != nil {
		return err
	}

	// Treat
	errPath := ""
	var resp string

	fmt.Println("Request:", req)

	if req.Type == CompletionRequest {
		resp, err = s.complete(string(req.Req))
		if err != nil {
			errPath = err.Error()
		}
	} else {
		resp, err = s.request(string(req.Req))
		if err != nil {
			errPath = err.Error()
		}
	}

	// Send response
	err = gob.NewEncoder(c).Encode(Response{resp, errPath})
	if err != nil {
		return err
	}
	if err = c.CloseWrite(); err != nil {
		return err
	}

	return nil
}

func (s *Server) listen() error {
	run := true

	// Signals
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt)
	signal.Notify(signals, os.Kill)
	signal.Notify(signals, syscall.SIGTERM)

	// Conns
	conns := make(chan *net.UnixConn, 100)

	// Listen
	l, err := net.ListenUnix("unix", &net.UnixAddr{s.unixpath, "unix"})
	if err != nil {
		return err
	}

	defer l.Close()
	defer os.Remove(s.unixpath)

	// Listen connections and send them to conns chan
	go func() {
		for run {
			c, err := l.AcceptUnix()
			if err != nil {
				fmt.Println(err)
				continue
			}

			conns <- c
		}
	}()

	fmt.Println("Starting server")

	ticker := time.NewTicker(time.Hour)

	// Wait conn or signal
	for run {
		select {
		case <-ticker.C:
			fmt.Println("Save data")
			if err := s.Save(); err != nil {
				fmt.Println(err)
			}
		case c := <-conns:
			fmt.Println("Got new conn")
			err := s.handleConn(c)
			if err != nil {
				fmt.Println(err)
			}
		case s := <-signals:
			fmt.Println("Got signal:", s)
			run = false
		}
	}

	fmt.Println("Ending server")

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"code.google.com/p/go-uuid/uuid"
	"github.com/clawio/service.auth/lib"
	pb "github.com/clawio/service.localstore.prop/proto"
	"github.com/jinzhu/gorm"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"path"
	"time"
)

const (
	dirPerm = 0755
)

var (
	unauthenticatedError = grpc.Errorf(codes.Unauthenticated, "identity not found")
	permissionDenied     = grpc.Errorf(codes.PermissionDenied, "access denied")
)

type newServerParams struct {
	dsn          string
	db           *gorm.DB
	sharedSecret string
}

func newServer(p *newServerParams) (*server, error) {

	db, err := newDB("mysql", p.dsn)
	if err != nil {
		log.Error(err)
		return nil, err
	}

	db.LogMode(true)

	err = db.AutoMigrate(&record{}).Error
	if err != nil {
		log.Error(err)
		return nil, err
	}

	log.Infof("automigration applied")

	s := &server{}
	s.p = p
	s.db = db
	return s, nil
}

type server struct {
	p  *newServerParams
	db *gorm.DB
}

func (s *server) Put(ctx context.Context, req *pb.PutReq) (*pb.Void, error) {

	idt, err := lib.ParseToken(req.AccessToken, s.p.sharedSecret)
	if err != nil {
		log.Error(err)
		return &pb.Void{}, unauthenticatedError
	}

	log.Infof("%s", idt)

	p := path.Clean(req.Path)

	log.Infof("path is %s", p)

	var id string
	var etag = uuid.New()
	var mtime = time.Now().Unix()

	r, err := s.getByPath(p)
	if err != nil {
		log.Error(err)
		if err == gorm.RecordNotFound {
			id = uuid.New()
		} else {
			return &pb.Void{}, err
		}
	} else {
		id = r.ID
	}

	log.Infof("record has %s", r.String())

	err = s.db.Exec(`INSERT INTO records (id,path,checksum, e_tag, m_time) VALUES (?,?,?,?,?)
  				ON DUPLICATE KEY UPDATE checksum=VALUES(checksum), e_tag=VALUES(e_tag), m_time=VALUES(m_time)`,
		id, p, req.Checksum, etag, mtime).Error

	if err != nil {
		log.Error(err)
		return &pb.Void{}, err
	}

	log.Infof("putted record into db")

	return &pb.Void{}, nil
}

func (s *server) getByPath(path string) (*record, error) {

	r := &record{}
	err := s.db.Where("path=?", path).First(r).Error
	return r, err
}
<commit_msg>Added Get support<commit_after>package main

import (
	"code.google.com/p/go-uuid/uuid"
	"github.com/clawio/service.auth/lib"
	pb "github.com/clawio/service.localstore.prop/proto"
	"github.com/jinzhu/gorm"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"path"
	"time"
)

const (
	dirPerm = 0755
)

var (
	unauthenticatedError = grpc.Errorf(codes.Unauthenticated, "identity not found")
	permissionDenied     = grpc.Errorf(codes.PermissionDenied, "access denied")
)

type newServerParams struct {
	dsn          string
	db           *gorm.DB
	sharedSecret string
}

func newServer(p *newServerParams) (*server, error) {

	db, err := newDB("mysql", p.dsn)
	if err != nil {
		log.Error(err)
		return nil, err
	}

	db.LogMode(true)

	err = db.AutoMigrate(&record{}).Error
	if err != nil {
		log.Error(err)
		return nil, err
	}

	log.Infof("automigration applied")

	s := &server{}
	s.p = p
	s.db = db
	return s, nil
}

type server struct {
	p  *newServerParams
	db *gorm.DB
}

func (s *server) Get(ctx context.Context, req *pb.GetReq) (*pb.Record, error) {

	idt, err := lib.ParseToken(req.AccessToken, s.p.sharedSecret)
	if err != nil {
		log.Error(err)
		return &pb.Record{}, unauthenticatedError
	}

	log.Infof("%s", idt)

	p := path.Clean(req.Path)

	log.Infof("path is %s", p)

	rec := &pb.Record{}
	err = s.db.Where("path=?", p).First(rec).Error
	// TODO(labkode) If record is not found putted. This will happen on storages with DAS
	if err != nil {
		log.Error(err)
		return rec, err
	}

	return rec, nil
}

func (s *server) Put(ctx context.Context, req *pb.PutReq) (*pb.Void, error) {

	idt, err := lib.ParseToken(req.AccessToken, s.p.sharedSecret)
	if err != nil {
		log.Error(err)
		return &pb.Void{}, unauthenticatedError
	}

	log.Infof("%s", idt)

	p := path.Clean(req.Path)

	log.Infof("path is %s", p)

	var id string
	var etag = uuid.New()
	var mtime = time.Now().Unix()

	r, err := s.getByPath(p)
	if err != nil {
		log.Error(err)
		if err == gorm.RecordNotFound {
			id = uuid.New()
		} else {
			return &pb.Void{}, err
		}
	} else {
		id = r.ID
	}

	log.Infof("record has %s", r.String())

	err = s.db.Exec(`INSERT INTO records (id,path,checksum, e_tag, m_time) VALUES (?,?,?,?,?)
  				ON DUPLICATE KEY UPDATE checksum=VALUES(checksum), e_tag=VALUES(e_tag), m_time=VALUES(m_time)`,
		id, p, req.Checksum, etag, mtime).Error

	if err != nil {
		log.Error(err)
		return &pb.Void{}, err
	}

	log.Infof("putted record into db")

	return &pb.Void{}, nil
}

func (s *server) getByPath(path string) (*record, error) {

	r := &record{}
	err := s.db.Where("path=?", path).First(r).Error
	return r, err
}
<|endoftext|>


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

package fuse

import (
	"fmt"
	"io"
	"log"
	"time"

	"github.com/jacobsa/gcsfuse/timeutil"
	"golang.org/x/net/context"

	bazilfuse "bazil.org/fuse"
)

// An object that terminates one end of the userspace <-> FUSE VFS connection.
type server struct {
	logger *log.Logger
	clock  timeutil.Clock
	fs     FileSystem
}

// Create a server that relays requests to the supplied file system.
func newServer(fs FileSystem) (s *server, err error) {
	s = &server{
		logger: getLogger(),
		clock:  timeutil.RealClock(),
		fs:     fs,
	}

	return
}

// Convert an absolute cache expiration time to a relative time from now for
// consumption by fuse.
func convertExpirationTime(t time.Time) time.Duration

func convertChildInodeEntry(
	clock timeutil.Clock,
	in *ChildInodeEntry,
	out *bazilfuse.LookupResponse) {
	out.Node = bazilfuse.NodeID(in.Child)
	out.Generation = uint64(in.Generation)
	out.Attr = convertAttributes(in.Child, in.Attributes)
	out.AttrValid = convertExpirationTime(in.AttributesExpiration)
	out.EntryValid = convertExpirationTime(in.EntryExpiration)
}

func convertHeader(
	in bazilfuse.Header) (out RequestHeader) {
	out.Uid = in.Uid
	out.Gid = in.Gid
	return
}

// Serve the fuse connection by repeatedly reading requests from the supplied
// FUSE connection, responding as dictated by the file system. Return when the
// connection is closed or an unexpected error occurs.
func (s *server) Serve(c *bazilfuse.Conn) (err error) {
	// Read a message at a time, dispatching to goroutines doing the actual
	// processing.
	for {
		var fuseReq bazilfuse.Request
		fuseReq, err = c.ReadRequest()

		// ReadRequest returns EOF when the connection has been closed.
		if err == io.EOF {
			err = nil
			return
		}

		// Otherwise, forward on errors.
		if err != nil {
			err = fmt.Errorf("Conn.ReadRequest: %v", err)
			return
		}

		go s.handleFuseRequest(fuseReq)
	}
}

func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
	// Log the request.
	s.logger.Println("Received:", fuseReq)

	// TODO(jacobsa): Support cancellation when interrupted, if we can coax the
	// system into reproducing such requests.
	ctx := context.Background()

	// Attempt to handle it.
	switch typed := fuseReq.(type) {
	case *bazilfuse.InitRequest:
		// Convert the request.
		req := &InitRequest{
			Header: convertHeader(typed.Header),
		}

		// Call the file system.
		_, err := s.fs.Init(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.InitResponse{}
		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.StatfsRequest:
		// Responding to this is required to make mounting work, at least on OS X.
		// We don't currently expose the capability for the file system to
		// intercept this.
		fuseResp := &bazilfuse.StatfsResponse{}
		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.LookupRequest:
		// Convert the request.
		req := &LookUpInodeRequest{
			Header: convertHeader(typed.Header),
			Parent: InodeID(typed.Header.Node),
			Name:   typed.Name,
		}

		// Call the file system.
		resp, err := s.fs.LookUpInode(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.LookupResponse{}
		convertChildInodeEntry(s.clock, &resp.Entry, fuseResp)

		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.GetattrRequest:
		// Convert the request.
		req := &GetInodeAttributesRequest{
			Header: convertHeader(typed.Header),
			Inode:  InodeID(typed.Header.Node),
		}

		// Call the file system.
		resp, err := s.fs.GetInodeAttributes(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.GetattrResponse{
			Attr:      convertAttributes(req.Inode, resp.Attributes),
			AttrValid: convertExpirationTime(resp.AttributesExpiration),
		}

		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.SetattrRequest:
		// Convert the request.
		req := &SetInodeAttributesRequest{
			Header: convertHeader(typed.Header),
			Inode:  InodeID(typed.Header.Node),
		}

		if typed.Valid&bazilfuse.SetattrSize != 0 {
			req.Size = &typed.Size
		}

		if typed.Valid&bazilfuse.SetattrMode != 0 {
			req.Mode = &typed.Mode
		}

		if typed.Valid&bazilfuse.SetattrAtime != 0 {
			req.Atime = &typed.Atime
		}

		if typed.Valid&bazilfuse.SetattrMtime != 0 {
			req.Mtime = &typed.Mtime
		}

		// Call the file system.
		resp, err := s.fs.SetInodeAttributes(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.SetattrResponse{
			Attr:      convertAttributes(req.Inode, resp.Attributes),
			AttrValid: convertExpirationTime(resp.AttributesExpiration),
		}

		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.MkdirRequest:
		// Convert the request.
		req := &MkDirRequest{
			Header: convertHeader(typed.Header),
			Parent: InodeID(typed.Header.Node),
			Name:   typed.Name,
			Mode:   typed.Mode,
		}

		// Call the file system.
		resp, err := s.fs.MkDir(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.MkdirResponse{}
		convertChildInodeEntry(s.clock, &resp.Entry, &fuseResp.LookupResponse)

		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.CreateRequest:
		// Convert the request.
		req := &CreateFileRequest{
			Header: convertHeader(typed.Header),
			Parent: InodeID(typed.Header.Node),
			Name:   typed.Name,
			Mode:   typed.Mode,
			Flags:  typed.Flags,
		}

		// Call the file system.
		resp, err := s.fs.CreateFile(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.CreateResponse{
			OpenResponse: bazilfuse.OpenResponse{
				Handle: bazilfuse.HandleID(resp.Handle),
			},
		}
		convertChildInodeEntry(s.clock, &resp.Entry, &fuseResp.LookupResponse)

		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.RemoveRequest:
		if typed.Dir {
			// Convert the request.
			req := &RmDirRequest{
				Header: convertHeader(typed.Header),
				Parent: InodeID(typed.Header.Node),
				Name:   typed.Name,
			}

			// Call the file system.
			_, err := s.fs.RmDir(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Respond successfully.
			s.logger.Println("Responding OK.")
			typed.Respond()
		} else {
			// Convert the request.
			req := &UnlinkRequest{
				Header: convertHeader(typed.Header),
				Parent: InodeID(typed.Header.Node),
				Name:   typed.Name,
			}

			// Call the file system.
			_, err := s.fs.Unlink(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Respond successfully.
			s.logger.Println("Responding OK.")
			typed.Respond()
		}

	case *bazilfuse.OpenRequest:
		// Directory or file?
		if typed.Dir {
			// Convert the request.
			req := &OpenDirRequest{
				Header: convertHeader(typed.Header),
				Inode:  InodeID(typed.Header.Node),
				Flags:  typed.Flags,
			}

			// Call the file system.
			resp, err := s.fs.OpenDir(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Convert the response.
			fuseResp := &bazilfuse.OpenResponse{
				Handle: bazilfuse.HandleID(resp.Handle),
			}

			s.logger.Println("Responding:", fuseResp)
			typed.Respond(fuseResp)
		} else {
			// Convert the request.
			req := &OpenFileRequest{
				Header: convertHeader(typed.Header),
				Inode:  InodeID(typed.Header.Node),
				Flags:  typed.Flags,
			}

			// Call the file system.
			resp, err := s.fs.OpenFile(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Convert the response.
			fuseResp := &bazilfuse.OpenResponse{
				Handle: bazilfuse.HandleID(resp.Handle),
			}

			s.logger.Println("Responding:", fuseResp)
			typed.Respond(fuseResp)
		}

	case *bazilfuse.ReadRequest:
		// Directory or file?
		if typed.Dir {
			// Convert the request.
			req := &ReadDirRequest{
				Header: convertHeader(typed.Header),
				Inode:  InodeID(typed.Header.Node),
				Handle: HandleID(typed.Handle),
				Offset: DirOffset(typed.Offset),
				Size:   typed.Size,
			}

			// Call the file system.
			resp, err := s.fs.ReadDir(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Convert the response.
			fuseResp := &bazilfuse.ReadResponse{
				Data: resp.Data,
			}

			s.logger.Println("Responding:", fuseResp)
			typed.Respond(fuseResp)
		} else {
			// Convert the request.
			req := &ReadFileRequest{
				Header: convertHeader(typed.Header),
				Inode:  InodeID(typed.Header.Node),
				Handle: HandleID(typed.Handle),
				Offset: typed.Offset,
				Size:   typed.Size,
			}

			// Call the file system.
			resp, err := s.fs.ReadFile(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Convert the response.
			fuseResp := &bazilfuse.ReadResponse{
				Data: resp.Data,
			}

			s.logger.Println("Responding:", fuseResp)
			typed.Respond(fuseResp)
		}

	case *bazilfuse.ReleaseRequest:
		// Directory or file?
		if typed.Dir {
			// Convert the request.
			req := &ReleaseDirHandleRequest{
				Header: convertHeader(typed.Header),
				Handle: HandleID(typed.Handle),
			}

			// Call the file system.
			_, err := s.fs.ReleaseDirHandle(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Respond successfully.
			s.logger.Println("Responding OK.")
			typed.Respond()
		} else {
			// Convert the request.
			req := &ReleaseFileHandleRequest{
				Header: convertHeader(typed.Header),
				Handle: HandleID(typed.Handle),
			}

			// Call the file system.
			_, err := s.fs.ReleaseFileHandle(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Respond successfully.
			s.logger.Println("Responding OK.")
			typed.Respond()
		}

	case *bazilfuse.WriteRequest:
		// Convert the request.
		req := &WriteFileRequest{
			Header: convertHeader(typed.Header),
			Inode:  InodeID(typed.Header.Node),
			Handle: HandleID(typed.Handle),
			Data:   typed.Data,
			Offset: typed.Offset,
		}

		// Call the file system.
		_, err := s.fs.WriteFile(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.WriteResponse{
			Size: len(typed.Data),
		}

		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	default:
		s.logger.Println("Unhandled type. Returning ENOSYS.")
		typed.RespondError(ENOSYS)
	}
}

func convertAttributes(inode InodeID, attr InodeAttributes) bazilfuse.Attr {
	return bazilfuse.Attr{
		Inode:  uint64(inode),
		Size:   attr.Size,
		Mode:   attr.Mode,
		Atime:  attr.Atime,
		Mtime:  attr.Mtime,
		Ctime:  attr.Ctime,
		Crtime: attr.Crtime,
		Uid:    attr.Uid,
		Gid:    attr.Gid,
	}
}
<commit_msg>Removed server.clock.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package fuse

import (
	"fmt"
	"io"
	"log"
	"time"

	"golang.org/x/net/context"

	bazilfuse "bazil.org/fuse"
)

// An object that terminates one end of the userspace <-> FUSE VFS connection.
type server struct {
	logger *log.Logger
	fs     FileSystem
}

// Create a server that relays requests to the supplied file system.
func newServer(fs FileSystem) (s *server, err error) {
	s = &server{
		logger: getLogger(),
		fs:     fs,
	}

	return
}

// Convert an absolute cache expiration time to a relative time from now for
// consumption by fuse.
func convertExpirationTime(t time.Time) time.Duration

func convertChildInodeEntry(
	in *ChildInodeEntry,
	out *bazilfuse.LookupResponse) {
	out.Node = bazilfuse.NodeID(in.Child)
	out.Generation = uint64(in.Generation)
	out.Attr = convertAttributes(in.Child, in.Attributes)
	out.AttrValid = convertExpirationTime(in.AttributesExpiration)
	out.EntryValid = convertExpirationTime(in.EntryExpiration)
}

func convertHeader(
	in bazilfuse.Header) (out RequestHeader) {
	out.Uid = in.Uid
	out.Gid = in.Gid
	return
}

// Serve the fuse connection by repeatedly reading requests from the supplied
// FUSE connection, responding as dictated by the file system. Return when the
// connection is closed or an unexpected error occurs.
func (s *server) Serve(c *bazilfuse.Conn) (err error) {
	// Read a message at a time, dispatching to goroutines doing the actual
	// processing.
	for {
		var fuseReq bazilfuse.Request
		fuseReq, err = c.ReadRequest()

		// ReadRequest returns EOF when the connection has been closed.
		if err == io.EOF {
			err = nil
			return
		}

		// Otherwise, forward on errors.
		if err != nil {
			err = fmt.Errorf("Conn.ReadRequest: %v", err)
			return
		}

		go s.handleFuseRequest(fuseReq)
	}
}

func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
	// Log the request.
	s.logger.Println("Received:", fuseReq)

	// TODO(jacobsa): Support cancellation when interrupted, if we can coax the
	// system into reproducing such requests.
	ctx := context.Background()

	// Attempt to handle it.
	switch typed := fuseReq.(type) {
	case *bazilfuse.InitRequest:
		// Convert the request.
		req := &InitRequest{
			Header: convertHeader(typed.Header),
		}

		// Call the file system.
		_, err := s.fs.Init(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.InitResponse{}
		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.StatfsRequest:
		// Responding to this is required to make mounting work, at least on OS X.
		// We don't currently expose the capability for the file system to
		// intercept this.
		fuseResp := &bazilfuse.StatfsResponse{}
		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.LookupRequest:
		// Convert the request.
		req := &LookUpInodeRequest{
			Header: convertHeader(typed.Header),
			Parent: InodeID(typed.Header.Node),
			Name:   typed.Name,
		}

		// Call the file system.
		resp, err := s.fs.LookUpInode(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.LookupResponse{}
		convertChildInodeEntry(&resp.Entry, fuseResp)

		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.GetattrRequest:
		// Convert the request.
		req := &GetInodeAttributesRequest{
			Header: convertHeader(typed.Header),
			Inode:  InodeID(typed.Header.Node),
		}

		// Call the file system.
		resp, err := s.fs.GetInodeAttributes(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.GetattrResponse{
			Attr:      convertAttributes(req.Inode, resp.Attributes),
			AttrValid: convertExpirationTime(resp.AttributesExpiration),
		}

		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.SetattrRequest:
		// Convert the request.
		req := &SetInodeAttributesRequest{
			Header: convertHeader(typed.Header),
			Inode:  InodeID(typed.Header.Node),
		}

		if typed.Valid&bazilfuse.SetattrSize != 0 {
			req.Size = &typed.Size
		}

		if typed.Valid&bazilfuse.SetattrMode != 0 {
			req.Mode = &typed.Mode
		}

		if typed.Valid&bazilfuse.SetattrAtime != 0 {
			req.Atime = &typed.Atime
		}

		if typed.Valid&bazilfuse.SetattrMtime != 0 {
			req.Mtime = &typed.Mtime
		}

		// Call the file system.
		resp, err := s.fs.SetInodeAttributes(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.SetattrResponse{
			Attr:      convertAttributes(req.Inode, resp.Attributes),
			AttrValid: convertExpirationTime(resp.AttributesExpiration),
		}

		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.MkdirRequest:
		// Convert the request.
		req := &MkDirRequest{
			Header: convertHeader(typed.Header),
			Parent: InodeID(typed.Header.Node),
			Name:   typed.Name,
			Mode:   typed.Mode,
		}

		// Call the file system.
		resp, err := s.fs.MkDir(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.MkdirResponse{}
		convertChildInodeEntry(&resp.Entry, &fuseResp.LookupResponse)

		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.CreateRequest:
		// Convert the request.
		req := &CreateFileRequest{
			Header: convertHeader(typed.Header),
			Parent: InodeID(typed.Header.Node),
			Name:   typed.Name,
			Mode:   typed.Mode,
			Flags:  typed.Flags,
		}

		// Call the file system.
		resp, err := s.fs.CreateFile(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.CreateResponse{
			OpenResponse: bazilfuse.OpenResponse{
				Handle: bazilfuse.HandleID(resp.Handle),
			},
		}
		convertChildInodeEntry(&resp.Entry, &fuseResp.LookupResponse)

		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	case *bazilfuse.RemoveRequest:
		if typed.Dir {
			// Convert the request.
			req := &RmDirRequest{
				Header: convertHeader(typed.Header),
				Parent: InodeID(typed.Header.Node),
				Name:   typed.Name,
			}

			// Call the file system.
			_, err := s.fs.RmDir(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Respond successfully.
			s.logger.Println("Responding OK.")
			typed.Respond()
		} else {
			// Convert the request.
			req := &UnlinkRequest{
				Header: convertHeader(typed.Header),
				Parent: InodeID(typed.Header.Node),
				Name:   typed.Name,
			}

			// Call the file system.
			_, err := s.fs.Unlink(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Respond successfully.
			s.logger.Println("Responding OK.")
			typed.Respond()
		}

	case *bazilfuse.OpenRequest:
		// Directory or file?
		if typed.Dir {
			// Convert the request.
			req := &OpenDirRequest{
				Header: convertHeader(typed.Header),
				Inode:  InodeID(typed.Header.Node),
				Flags:  typed.Flags,
			}

			// Call the file system.
			resp, err := s.fs.OpenDir(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Convert the response.
			fuseResp := &bazilfuse.OpenResponse{
				Handle: bazilfuse.HandleID(resp.Handle),
			}

			s.logger.Println("Responding:", fuseResp)
			typed.Respond(fuseResp)
		} else {
			// Convert the request.
			req := &OpenFileRequest{
				Header: convertHeader(typed.Header),
				Inode:  InodeID(typed.Header.Node),
				Flags:  typed.Flags,
			}

			// Call the file system.
			resp, err := s.fs.OpenFile(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Convert the response.
			fuseResp := &bazilfuse.OpenResponse{
				Handle: bazilfuse.HandleID(resp.Handle),
			}

			s.logger.Println("Responding:", fuseResp)
			typed.Respond(fuseResp)
		}

	case *bazilfuse.ReadRequest:
		// Directory or file?
		if typed.Dir {
			// Convert the request.
			req := &ReadDirRequest{
				Header: convertHeader(typed.Header),
				Inode:  InodeID(typed.Header.Node),
				Handle: HandleID(typed.Handle),
				Offset: DirOffset(typed.Offset),
				Size:   typed.Size,
			}

			// Call the file system.
			resp, err := s.fs.ReadDir(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Convert the response.
			fuseResp := &bazilfuse.ReadResponse{
				Data: resp.Data,
			}

			s.logger.Println("Responding:", fuseResp)
			typed.Respond(fuseResp)
		} else {
			// Convert the request.
			req := &ReadFileRequest{
				Header: convertHeader(typed.Header),
				Inode:  InodeID(typed.Header.Node),
				Handle: HandleID(typed.Handle),
				Offset: typed.Offset,
				Size:   typed.Size,
			}

			// Call the file system.
			resp, err := s.fs.ReadFile(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Convert the response.
			fuseResp := &bazilfuse.ReadResponse{
				Data: resp.Data,
			}

			s.logger.Println("Responding:", fuseResp)
			typed.Respond(fuseResp)
		}

	case *bazilfuse.ReleaseRequest:
		// Directory or file?
		if typed.Dir {
			// Convert the request.
			req := &ReleaseDirHandleRequest{
				Header: convertHeader(typed.Header),
				Handle: HandleID(typed.Handle),
			}

			// Call the file system.
			_, err := s.fs.ReleaseDirHandle(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Respond successfully.
			s.logger.Println("Responding OK.")
			typed.Respond()
		} else {
			// Convert the request.
			req := &ReleaseFileHandleRequest{
				Header: convertHeader(typed.Header),
				Handle: HandleID(typed.Handle),
			}

			// Call the file system.
			_, err := s.fs.ReleaseFileHandle(ctx, req)
			if err != nil {
				s.logger.Println("Responding:", err)
				typed.RespondError(err)
				return
			}

			// Respond successfully.
			s.logger.Println("Responding OK.")
			typed.Respond()
		}

	case *bazilfuse.WriteRequest:
		// Convert the request.
		req := &WriteFileRequest{
			Header: convertHeader(typed.Header),
			Inode:  InodeID(typed.Header.Node),
			Handle: HandleID(typed.Handle),
			Data:   typed.Data,
			Offset: typed.Offset,
		}

		// Call the file system.
		_, err := s.fs.WriteFile(ctx, req)
		if err != nil {
			s.logger.Println("Responding:", err)
			typed.RespondError(err)
			return
		}

		// Convert the response.
		fuseResp := &bazilfuse.WriteResponse{
			Size: len(typed.Data),
		}

		s.logger.Println("Responding:", fuseResp)
		typed.Respond(fuseResp)

	default:
		s.logger.Println("Unhandled type. Returning ENOSYS.")
		typed.RespondError(ENOSYS)
	}
}

func convertAttributes(inode InodeID, attr InodeAttributes) bazilfuse.Attr {
	return bazilfuse.Attr{
		Inode:  uint64(inode),
		Size:   attr.Size,
		Mode:   attr.Mode,
		Atime:  attr.Atime,
		Mtime:  attr.Mtime,
		Ctime:  attr.Ctime,
		Crtime: attr.Crtime,
		Uid:    attr.Uid,
		Gid:    attr.Gid,
	}
}
<|endoftext|>


<commit_before>package tarantool

import (
	"bufio"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"
	"net"
	"sync"
)

const saltSize = 32

type QueryHandler func(query Query) *Result
type OnCloseCallback func(err error)

type IprotoServer struct {
	sync.Mutex
	conn       net.Conn
	reader     *bufio.Reader
	writer     *bufio.Writer
	uuid       string
	salt       []byte // base64-encoded salt
	quit       chan bool
	handler    QueryHandler
	onClose    OnCloseCallback
	output     chan *packedPacket
	closeOnce  sync.Once
	firstError error
}

func NewIprotoServer(uuid string, handler QueryHandler, onClose OnCloseCallback) *IprotoServer {
	return &IprotoServer{
		conn:    nil,
		reader:  nil,
		writer:  nil,
		handler: handler,
		onClose: onClose,
		uuid:    uuid,
	}
}

func (s *IprotoServer) Accept(conn net.Conn) {
	s.conn = conn
	s.reader = bufio.NewReaderSize(conn, DefaultReaderBufSize)
	s.writer = bufio.NewWriterSize(conn, DefaultWriterBufSize)
	s.quit = make(chan bool)
	s.output = make(chan *packedPacket, 1024)

	err := s.greet()
	if err != nil {
		s.Close()
		return
	}

	go s.loop()
}

func (s *IprotoServer) CheckAuth(hash []byte, password string) bool {
	scr, err := scramble(s.salt, password)
	if err != nil {
		return false
	}

	if len(scr) != len(hash) {
		return false
	}

	for i, v := range hash {
		if v != scr[i] {
			return false
		}
	}
	return true
}

func (s *IprotoServer) setError(err error) {
	if err != nil && err != io.EOF {
		s.Lock()
		defer s.Unlock()
		if s.firstError == nil {
			s.firstError = err
		}
	}
}

func (s *IprotoServer) getError() error {
	s.Lock()
	defer s.Unlock()
	return s.firstError
}

func (s *IprotoServer) Close() error {
	err := s.getError()

	s.closeOnce.Do(func() {
		if s.onClose != nil {
			s.onClose(err)
		}
		close(s.quit)
		s.conn.Close()
	})

	return err
}

func (s *IprotoServer) greet() (err error) {
	var line1, line2 string
	var format, greeting string
	var n int

	salt := make([]byte, saltSize)
	_, err = rand.Read(salt)
	if err != nil {
		return
	}

	s.salt = []byte(base64.StdEncoding.EncodeToString(salt))

	line1 = fmt.Sprintf("%s %s", ServerIdent, s.uuid)
	line2 = fmt.Sprintf("%s", s.salt)

	format = fmt.Sprintf("%%-%ds\n%%-%ds\n", GreetingSize/2-1, GreetingSize/2-1)
	greeting = fmt.Sprintf(format, line1, line2)

	// send greeting
	n, err = fmt.Fprintf(s.writer, "%s", greeting)
	if err != nil || n != GreetingSize {
		return
	}

	return s.writer.Flush()
}

func (s *IprotoServer) loop() {
	go s.read()
	go s.write()
}

func (s *IprotoServer) read() {
	var err error
	var pp *packedPacket

	r := s.reader

READER_LOOP:
	for {
		select {
		case <-s.quit:
			break READER_LOOP
		default:
			// read raw bytes
			pp, err = readPacked(r)
			if err != nil {
				break READER_LOOP
			}

			go func(pp *packedPacket) {
				packet, err := decodePacket(pp)
				if err != nil {
					s.setError(err)
					s.Close()
					return
				}

				code := byte(packet.code)
				if code == PingRequest {
					s.output <- packIprotoOk(packet.requestID)
				} else {
					res := s.handler(packet.Request)
					outBody, _ := res.pack(packet.requestID)
					s.output <- outBody
				}
				pp.Release()
			}(pp)
		}
	}

	if err != nil {
		s.setError(err)
	}
	s.Close()
}

func (s *IprotoServer) write() {
	var err error

	w := s.writer
	wp := func(w io.Writer, packet *packedPacket) error {
		_, err = packet.WriteTo(w)
		defer packet.Release()
		return err
	}

WRITER_LOOP:
	for {
		select {
		case packet, ok := <-s.output:
			if !ok {
				break WRITER_LOOP
			}

			err = wp(w, packet)
			if err != nil {
				break WRITER_LOOP
			}
		case <-s.quit:
			w.Flush()
			break WRITER_LOOP
		default:
			if err = w.Flush(); err != nil {
				break WRITER_LOOP
			}

			// same without flush
			select {
			case packet, ok := <-s.output:
				if !ok {
					break WRITER_LOOP
				}
				err = wp(w, packet)
				if err != nil {
					break WRITER_LOOP
				}
			case <-s.quit:
				w.Flush()
				break WRITER_LOOP
			}

		}
	}

	if err != nil {
		s.setError(err)
	}

	s.Close()
}
<commit_msg>Use default bufio buffer sizes<commit_after>package tarantool

import (
	"bufio"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"
	"net"
	"sync"
)

const saltSize = 32

type QueryHandler func(query Query) *Result
type OnCloseCallback func(err error)

type IprotoServer struct {
	sync.Mutex
	conn       net.Conn
	reader     *bufio.Reader
	writer     *bufio.Writer
	uuid       string
	salt       []byte // base64-encoded salt
	quit       chan bool
	handler    QueryHandler
	onClose    OnCloseCallback
	output     chan *packedPacket
	closeOnce  sync.Once
	firstError error
}

func NewIprotoServer(uuid string, handler QueryHandler, onClose OnCloseCallback) *IprotoServer {
	return &IprotoServer{
		conn:    nil,
		reader:  nil,
		writer:  nil,
		handler: handler,
		onClose: onClose,
		uuid:    uuid,
	}
}

func (s *IprotoServer) Accept(conn net.Conn) {
	s.conn = conn
	s.reader = bufio.NewReader(conn)
	s.writer = bufio.NewWriter(conn)
	s.quit = make(chan bool)
	s.output = make(chan *packedPacket, 1024)

	err := s.greet()
	if err != nil {
		s.Close()
		return
	}

	go s.loop()
}

func (s *IprotoServer) CheckAuth(hash []byte, password string) bool {
	scr, err := scramble(s.salt, password)
	if err != nil {
		return false
	}

	if len(scr) != len(hash) {
		return false
	}

	for i, v := range hash {
		if v != scr[i] {
			return false
		}
	}
	return true
}

func (s *IprotoServer) setError(err error) {
	if err != nil && err != io.EOF {
		s.Lock()
		defer s.Unlock()
		if s.firstError == nil {
			s.firstError = err
		}
	}
}

func (s *IprotoServer) getError() error {
	s.Lock()
	defer s.Unlock()
	return s.firstError
}

func (s *IprotoServer) Close() error {
	err := s.getError()

	s.closeOnce.Do(func() {
		if s.onClose != nil {
			s.onClose(err)
		}
		close(s.quit)
		s.conn.Close()
	})

	return err
}

func (s *IprotoServer) greet() (err error) {
	var line1, line2 string
	var format, greeting string
	var n int

	salt := make([]byte, saltSize)
	_, err = rand.Read(salt)
	if err != nil {
		return
	}

	s.salt = []byte(base64.StdEncoding.EncodeToString(salt))

	line1 = fmt.Sprintf("%s %s", ServerIdent, s.uuid)
	line2 = fmt.Sprintf("%s", s.salt)

	format = fmt.Sprintf("%%-%ds\n%%-%ds\n", GreetingSize/2-1, GreetingSize/2-1)
	greeting = fmt.Sprintf(format, line1, line2)

	// send greeting
	n, err = fmt.Fprintf(s.writer, "%s", greeting)
	if err != nil || n != GreetingSize {
		return
	}

	return s.writer.Flush()
}

func (s *IprotoServer) loop() {
	go s.read()
	go s.write()
}

func (s *IprotoServer) read() {
	var err error
	var pp *packedPacket

	r := s.reader

READER_LOOP:
	for {
		select {
		case <-s.quit:
			break READER_LOOP
		default:
			// read raw bytes
			pp, err = readPacked(r)
			if err != nil {
				break READER_LOOP
			}

			go func(pp *packedPacket) {
				packet, err := decodePacket(pp)
				if err != nil {
					s.setError(err)
					s.Close()
					return
				}

				code := byte(packet.code)
				if code == PingRequest {
					s.output <- packIprotoOk(packet.requestID)
				} else {
					res := s.handler(packet.Request)
					outBody, _ := res.pack(packet.requestID)
					s.output <- outBody
				}
				pp.Release()
			}(pp)
		}
	}

	if err != nil {
		s.setError(err)
	}
	s.Close()
}

func (s *IprotoServer) write() {
	var err error

	w := s.writer
	wp := func(w io.Writer, packet *packedPacket) error {
		_, err = packet.WriteTo(w)
		defer packet.Release()
		return err
	}

WRITER_LOOP:
	for {
		select {
		case packet, ok := <-s.output:
			if !ok {
				break WRITER_LOOP
			}

			err = wp(w, packet)
			if err != nil {
				break WRITER_LOOP
			}
		case <-s.quit:
			w.Flush()
			break WRITER_LOOP
		default:
			if err = w.Flush(); err != nil {
				break WRITER_LOOP
			}

			// same without flush
			select {
			case packet, ok := <-s.output:
				if !ok {
					break WRITER_LOOP
				}
				err = wp(w, packet)
				if err != nil {
					break WRITER_LOOP
				}
			case <-s.quit:
				w.Flush()
				break WRITER_LOOP
			}

		}
	}

	if err != nil {
		s.setError(err)
	}

	s.Close()
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/golang/groupcache/lru"
	"github.com/mitchellh/colorstring"
	"log"
	"time"
)

type SyncServer struct {
	Spotify   *Spotify
	Radio     *Radio
	Interval  time.Duration
	Adaptive  bool
	CacheSize int
	playlist  *Playlist
	cache     *lru.Cache
}

func logColorf(format string, v ...interface{}) {
	log.Printf(colorstring.Color(format), v...)
}

func (sync *SyncServer) isCached(track *Track) bool {
	_, exists := sync.cache.Get(track.Id)
	return exists
}

func (sync *SyncServer) addTrack(track *Track) {
	if !sync.isCached(track) {
		sync.cache.Add(track.Id, track)
	}
}

func (sync *SyncServer) initPlaylist() error {
	playlist, err := sync.Spotify.GetOrCreatePlaylist(sync.Radio.Name)
	if err != nil {
		return err
	}
	sync.playlist = playlist
	return nil
}

func (sync *SyncServer) initCache() {
	sync.cache = lru.New(sync.CacheSize)
	for _, t := range sync.playlist.Tracks.Items {
		sync.addTrack(&t.Track)
	}
}

func (sync *SyncServer) Serve() {
	log.Printf("Server started")
	if err := sync.initPlaylist(); err != nil {
		log.Fatalf("Failed to get or create playlist: %s", err)
	}
	log.Printf("Spotify playlist: %s", sync.playlist.String())

	sync.initCache()
	log.Printf("LRU cache initialized: %d/%d", sync.cache.Len(),
		sync.cache.MaxEntries)

	if sync.Adaptive {
		log.Print("Automatically determining interval")
	} else {
		log.Printf("Syncing every %s", sync.Interval)
	}
	for {
		select {
		case <-sync.scheduleRun():
		}
	}
}

func (sync *SyncServer) scheduleRun() <-chan time.Time {
	duration, err := sync.run()
	if err != nil {
		log.Printf("Sync failed: %s\n", err)
		log.Print("Retrying in 1 minute")
		return time.After(1 * time.Minute)
	}
	log.Printf("Next sync in %s", duration)
	return time.After(duration)
}

func (sync *SyncServer) run() (time.Duration, error) {
	logColorf("[light_magenta]Running sync[reset]")

	radioPlaylist, err := sync.Radio.Playlist()
	if err != nil {
		return time.Duration(0), err
	}

	radioTracks := radioPlaylist.Tracks[1:] // Skip previous track
	if current, err := radioPlaylist.Current(); err != nil {
		logColorf("[red]Failed to get current track: %s[reset]", err)
	} else {
		if position, err := current.Position(); err != nil {
			logColorf("[red]Failed to parse metadata: %s[reset]",
				err)
		} else {
			logColorf("[cyan]%s is currently playing: %s - %s"+
				"[reset] (%s) [%s]",
				sync.Radio.Name, current.Artist, current.Track,
				position.String(),
				position.Symbol(10, true))
		}
	}

	added := 0
	for _, t := range radioTracks {
		logColorf("Searching for: %s", t.String())
		if !t.IsMusic() {
			logColorf("[green]Not music, skipping: %s[reset]",
				t.String())
			continue
		}
		tracks, err := sync.Spotify.SearchArtistTrack(t.Artist, t.Track)
		if err != nil {
			logColorf("[red]Search failed: %s (%s)[reset]",
				t.String(), err)
			continue
		}
		if len(tracks) == 0 {
			logColorf("[yellow]Track not found: %s (%s)[reset]",
				t.String(), err)
			continue
		}
		track := &tracks[0]
		if sync.isCached(track) {
			logColorf("[yellow]Already added: %s[reset]",
				track.String())
			added++
			continue
		}
		if err = sync.Spotify.AddTrack(sync.playlist,
			track); err != nil {
			logColorf("[red]Failed to add: %s (%s)[reset]",
				track.String(), err)
			continue
		}
		sync.addTrack(track)
		added++
		logColorf("[green]Added track: %s[reset]", track.String())
	}
	log.Printf("Cache size: %d/%d", sync.cache.Len(),
		sync.cache.MaxEntries)
	if !sync.Adaptive {
		return sync.Interval, nil
	}
	if added != len(radioTracks) {
		log.Printf("%d/%d tracks were added. Falling back to "+
			" regular interval", added, len(radioTracks))
		return sync.Interval, nil
	}
	return radioPlaylist.NextSync()
}
<commit_msg>Retry operations using exponential backoff<commit_after>package main

import (
	"github.com/cenkalti/backoff"
	"github.com/golang/groupcache/lru"
	"github.com/mitchellh/colorstring"
	"log"
	"time"
)

type SyncServer struct {
	Spotify   *Spotify
	Radio     *Radio
	Interval  time.Duration
	Adaptive  bool
	CacheSize int
	playlist  *Playlist
	cache     *lru.Cache
}

func logColorf(format string, v ...interface{}) {
	log.Printf(colorstring.Color(format), v...)
}

func (sync *SyncServer) isCached(track *Track) bool {
	_, exists := sync.cache.Get(track.Id)
	return exists
}

func (sync *SyncServer) addTrack(track *Track) {
	if !sync.isCached(track) {
		sync.cache.Add(track.Id, track)
	}
}

func (sync *SyncServer) initPlaylist() error {
	playlist, err := sync.Spotify.GetOrCreatePlaylist(sync.Radio.Name)
	if err != nil {
		return err
	}
	sync.playlist = playlist
	return nil
}

func (sync *SyncServer) initCache() {
	sync.cache = lru.New(sync.CacheSize)
	for _, t := range sync.playlist.Tracks.Items {
		sync.addTrack(&t.Track)
	}
}

func (sync *SyncServer) Serve() {
	log.Printf("Server started")

	log.Print("Initializing Spotify playlist")
	b := backoff.NewExponentialBackOff()
	b.MaxElapsedTime = time.Duration(5 * time.Minute)
	ticker := backoff.NewTicker(b)
	var err error
	for _ = range ticker.C {
		if err = sync.initPlaylist(); err != nil {
			log.Printf("Failed to get playlist: %s", err)
			log.Println("Retrying...")
			continue
		}
		break
	}
	if err != nil {
		log.Fatalf("Failed to get or create playlist: %s", err)
	}
	log.Printf("Playlist: %s", sync.playlist.String())

	log.Print("Initializing cache")
	sync.initCache()
	log.Printf("Size: %d/%d", sync.cache.Len(), sync.cache.MaxEntries)

	if sync.Adaptive {
		log.Print("Automatically determining interval")
	} else {
		log.Printf("Syncing every %s", sync.Interval)
	}
	for {
		select {
		case <-sync.scheduleRun():
		}
	}
}

func (sync *SyncServer) scheduleRun() <-chan time.Time {
	duration, err := sync.run()
	if err != nil {
		log.Printf("Sync failed: %s\n", err)
		log.Print("Retrying in 1 minute")
		return time.After(1 * time.Minute)
	}
	log.Printf("Next sync in %s", duration)
	return time.After(duration)
}

func (sync *SyncServer) retryPlaylist() (*RadioPlaylist, error) {
	b := backoff.NewExponentialBackOff()
	b.MaxElapsedTime = time.Duration(1 * time.Minute)
	ticker := backoff.NewTicker(b)
	var playlist *RadioPlaylist
	var err error
	for _ = range ticker.C {
		playlist, err = sync.Radio.Playlist()
		if err != nil {
			log.Printf("Retrieving radio playlist failed: %s", err)
			log.Println("Retrying...")
			continue
		}
		break
	}
	return playlist, err
}

func (sync *SyncServer) retrySearch(track *RadioTrack) ([]Track, error) {
	b := backoff.NewExponentialBackOff()
	b.MaxElapsedTime = time.Duration(1 * time.Minute)
	ticker := backoff.NewTicker(b)
	var tracks []Track
	var err error
	for _ = range ticker.C {
		tracks, err = sync.Spotify.SearchArtistTrack(track.Artist,
			track.Track)
		if err != nil {
			log.Printf("Search failed: %s", err)
			log.Println("Retrying...")
			continue
		}
		break
	}
	return tracks, err
}

func (sync *SyncServer) retryAddTrack(track *Track) error {
	b := backoff.NewExponentialBackOff()
	b.MaxElapsedTime = time.Duration(1 * time.Minute)
	ticker := backoff.NewTicker(b)
	var err error
	for _ = range ticker.C {
		err = sync.Spotify.AddTrack(sync.playlist, track)
		if err != nil {
			log.Printf("Add track failed: %s", err)
			log.Println("Retrying...")
			continue
		}
		break
	}
	return err
}

func (sync *SyncServer) logCurrentTrack(playlist *RadioPlaylist) {
	current, err := playlist.Current()
	if err != nil {
		logColorf("[red]Failed to get current track: %s[reset]", err)
		return
	}
	position, err := current.Position()
	if err != nil {
		logColorf("[red]Failed to parse metadata: %s[reset]", err)
		return
	}
	logColorf("[cyan]%s is currently playing: %s - %s[reset] (%s) [%s]",
		sync.Radio.Name, current.Artist, current.Track,
		position.String(), position.Symbol(10, true))
}

func (sync *SyncServer) run() (time.Duration, error) {
	logColorf("[light_magenta]Running sync[reset]")

	radioPlaylist, err := sync.retryPlaylist()
	if err != nil {
		return time.Duration(0), err
	}
	sync.logCurrentTrack(radioPlaylist)

	added := 0
	for _, t := range radioPlaylist.Tracks {
		logColorf("Searching for: %s", t.String())
		if !t.IsMusic() {
			logColorf("[green]Not music, skipping: %s[reset]",
				t.String())
			continue
		}
		tracks, err := sync.Spotify.SearchArtistTrack(t.Artist, t.Track)
		if err != nil {
			logColorf("[red]Search failed: %s (%s)[reset]",
				t.String(), err)
			continue
		}
		if len(tracks) == 0 {
			logColorf("[yellow]Track not found: %s (%s)[reset]",
				t.String(), err)
			continue
		}
		track := &tracks[0]
		if sync.isCached(track) {
			logColorf("[yellow]Already added: %s[reset]",
				track.String())
			added++
			continue
		}
		if err = sync.retryAddTrack(track); err != nil {
			logColorf("[red]Failed to add: %s (%s)[reset]",
				track.String(), err)
			continue
		}
		sync.addTrack(track)
		added++
		logColorf("[green]Added track: %s[reset]", track.String())
	}
	log.Printf("Cache size: %d/%d", sync.cache.Len(),
		sync.cache.MaxEntries)
	if !sync.Adaptive {
		return sync.Interval, nil
	}
	if added != len(radioPlaylist.Tracks) {
		log.Printf("%d/%d tracks were added. Falling back to "+
			" regular interval", added, len(radioPlaylist.Tracks))
		return sync.Interval, nil
	}
	return radioPlaylist.NextSync()
}
<|endoftext|>