<commit_before>/*
   Copyright 2015 The Httpgzip Authors. See the AUTHORS file at the
   top-level directory of this distribution and at
   <https://xi2.org/x/httpgzip/m/AUTHORS>.

   This file is part of Httpgzip.

   Httpgzip is free software: you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Httpgzip is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Httpgzip.  If not, see <https://www.gnu.org/licenses/>.
*/

// Package httpgzip implements an http.Handler wrapper adding gzip
// compression for appropriate requests.
//
// It attempts to properly parse the request's Accept-Encoding header
// according to RFC 2616 and does not do a simple string search for
// "gzip" (which will fail to do the correct thing for values such as
// "*" or "identity,gzip;q=0"). It will serve either gzip or identity
// content codings, or return 406 Not Acceptable status if it can do
// neither.
//
// It works correctly with handlers which honour Range request headers
// (such as http.FileServer) by removing the Range header for requests
// which prefer gzip encoding. This is necessary since Range requests
// apply to the gzipped content but the wrapped handler is not aware
// of the compression when it writes byte ranges. The Accept-Ranges
// header is also stripped from corresponding responses.
//
// For requests which prefer gzip encoding a Content-Type header is
// set using http.DetectContentType if it is not set by the wrapped
// handler.
package httpgzip // import "xi2.org/x/httpgzip"

import (
	"bytes"
	"compress/gzip"
	"mime"
	"net/http"
	"strconv"
	"strings"
	"sync"
)

// DefaultContentTypes is the default list of content types for which
// a Handler considers gzip compression. This list originates from the
// file compression.conf within the Apache configuration found at
// https://html5boilerplate.com/.
var DefaultContentTypes = []string{
	"application/atom+xml",
	"application/javascript",
	"application/json",
	"application/ld+json",
	"application/manifest+json",
	"application/rdf+xml",
	"application/rss+xml",
	"application/schema+json",
	"application/vnd.geo+json",
	"application/vnd.ms-fontobject",
	"application/x-font-ttf",
	"application/x-javascript",
	"application/x-web-app-manifest+json",
	"application/xhtml+xml",
	"application/xml",
	"font/eot",
	"font/opentype",
	"image/bmp",
	"image/svg+xml",
	"image/vnd.microsoft.icon",
	"image/x-icon",
	"text/cache-manifest",
	"text/css",
	"text/html",
	"text/javascript",
	"text/plain",
	"text/vcard",
	"text/vnd.rim.location.xloc",
	"text/vtt",
	"text/x-component",
	"text/x-cross-domain-policy",
	"text/xml",
}

var gzipWriterPool = sync.Pool{
	New: func() interface{} { return gzip.NewWriter(nil) },
}

var gzipBufPool = sync.Pool{
	New: func() interface{} { return new(bytes.Buffer) },
}

// A gzipResponseWriter is a modified http.ResponseWriter. It adds
// gzip compression to certain responses, and there are two cases
// where this is done. Case 1 is when encs only allows gzip encoding
// and forbids identity. Case 2 is when encs prefers gzip encoding,
// the response is greater than 512 bytes and the response's content
// type is in ctMap.
//
// A gzipResponseWriter sets the Content-Encoding and Content-Type
// headers when appropriate. It is important to call the Close method
// when writing is finished in order to flush and close the
// gzipResponseWriter. The slice encs must contain only encodings from
// {encGzip,encIdentity} and contain at least one encoding.
type gzipResponseWriter struct {
	http.ResponseWriter
	httpStatus int
	ctMap      map[string]struct{}
	encs       []encoding
	gw         *gzip.Writer
	buf        *bytes.Buffer
}

func newGzipResponseWriter(w http.ResponseWriter, ctMap map[string]struct{}, encs []encoding) *gzipResponseWriter {
	buf := gzipBufPool.Get().(*bytes.Buffer)
	buf.Reset()
	return &gzipResponseWriter{
		ResponseWriter: w,
		httpStatus:     http.StatusOK,
		ctMap:          ctMap,
		encs:           encs,
		buf:            buf}
}

// init gets called by Write once at least 512 bytes have been written
// to the temporary buffer buf, or by Close if it has not yet been
// called. Firstly it determines the content type, either from the
// Content-Type header, or by calling http.DetectContentType on
// buf. Then, if needed, a gzip.Writer is initialized. Lastly,
// appropriate headers are set and the ResponseWriter's WriteHeader
// method is called.
func (w *gzipResponseWriter) init() {
	cth := w.Header().Get("Content-Type")
	var ct string
	if cth != "" {
		ct = cth
	} else {
		ct = http.DetectContentType(w.buf.Bytes())
	}
	var gzipContentType bool
	if mt, _, err := mime.ParseMediaType(ct); err == nil {
		if _, ok := w.ctMap[mt]; ok {
			gzipContentType = true
		}
	}
	var useGzip bool
	if w.Header().Get("Content-Encoding") == "" && w.encs[0] == encGzip {
		if gzipContentType && w.buf.Len() >= 512 || len(w.encs) == 1 {
			useGzip = true
		}
	}
	if useGzip {
		w.gw = gzipWriterPool.Get().(*gzip.Writer)
		w.gw.Reset(w.ResponseWriter)
		w.Header().Del("Content-Length")
		w.Header().Set("Content-Encoding", "gzip")
	}
	w.Header().Del("Accept-Ranges")
	if cth == "" {
		w.Header().Set("Content-Type", ct)
	}
	w.ResponseWriter.WriteHeader(w.httpStatus)
}

func (w *gzipResponseWriter) Write(p []byte) (int, error) {
	var n, written int
	var err error
	if w.buf != nil {
		written = w.buf.Len()
		_, _ = w.buf.Write(p)
		if w.buf.Len() < 512 {
			return len(p), nil
		}
		w.init()
		p = w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
	}
	switch {
	case w.gw != nil:
		n, err = w.gw.Write(p)
	default:
		n, err = w.ResponseWriter.Write(p)
	}
	n -= written
	if n < 0 {
		n = 0
	}
	return n, err
}

func (w *gzipResponseWriter) WriteHeader(httpStatus int) {
	// postpone WriteHeader call until end of init method
	w.httpStatus = httpStatus
}

func (w *gzipResponseWriter) Close() (err error) {
	if w.buf != nil {
		w.init()
		p := w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
		switch {
		case w.gw != nil:
			_, err = w.gw.Write(p)
		default:
			_, err = w.ResponseWriter.Write(p)
		}
	}
	if w.gw != nil {
		e := w.gw.Close()
		if e != nil {
			err = e
		}
		gzipWriterPool.Put(w.gw)
		w.gw = nil
	}
	return err
}

// An encoding is a supported content coding.
type encoding int

const (
	encIdentity encoding = iota
	encGzip
)

// acceptedEncodings returns the supported content codings that are
// accepted by the request r. It returns a slice of encodings in
// client preference order.
//
// If the Sec-WebSocket-Key header is present then compressed content
// encodings are not considered.
//
// ref: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
func acceptedEncodings(r *http.Request) []encoding {
	h := r.Header.Get("Accept-Encoding")
	swk := r.Header.Get("Sec-WebSocket-Key")
	if h == "" {
		return []encoding{encIdentity}
	}
	gzip := float64(-1)    // -1 means not accepted, 0 -> 1 means value of q
	identity := float64(0) // -1 means not accepted, 0 -> 1 means value of q
	for _, s := range strings.Split(h, ",") {
		f := strings.Split(s, ";")
		f0 := strings.ToLower(strings.Trim(f[0], " "))
		q := float64(1.0)
		if len(f) > 1 {
			f1 := strings.ToLower(strings.Trim(f[1], " "))
			if strings.HasPrefix(f1, "q=") {
				if flt, err := strconv.ParseFloat(f1[2:], 32); err == nil {
					if flt >= 0 && flt <= 1 {
						q = flt
					}
				}
			}
		}
		if (f0 == "gzip" || f0 == "*") && q > gzip && swk == "" {
			gzip = q
		}
		if (f0 == "gzip" || f0 == "*") && q == 0 {
			gzip = -1
		}
		if (f0 == "identity" || f0 == "*") && q > identity {
			identity = q
		}
		if (f0 == "identity" || f0 == "*") && q == 0 {
			identity = -1
		}
	}
	switch {
	case gzip == -1 && identity == -1:
		return []encoding{}
	case gzip == -1:
		return []encoding{encIdentity}
	case identity == -1:
		return []encoding{encGzip}
	case identity > gzip:
		return []encoding{encIdentity, encGzip}
	default:
		return []encoding{encGzip, encIdentity}
	}
}

// NewHandler returns a new http.Handler which wraps a handler h
// adding gzip compression to certain responses. There are two cases
// where gzip compression is done. Case 1 is responses whose requests
// only allow gzip encoding and forbid identity encoding (identity
// encoding meaning no encoding). Case 2 is responses whose requests
// prefer gzip encoding, whose size is greater than 512 bytes and
// whose content types are in contentTypes. If contentTypes is nil
// then DefaultContentTypes is considered instead.
//
// The new http.Handler sets the Content-Encoding, Vary and
// Content-Type headers in its responses as appropriate. If a request
// expresses a preference for gzip encoding then any Range headers are
// removed from the request before it is passed through to h and
// Accept-Ranges headers are stripped from corresponding
// responses. This happens regardless of whether gzip encoding is
// eventually used in the response or not.
func NewHandler(h http.Handler, contentTypes []string) http.Handler {
	if contentTypes == nil {
		contentTypes = DefaultContentTypes
	}
	ctMap := map[string]struct{}{}
	for _, ct := range contentTypes {
		ctMap[ct] = struct{}{}
	}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// add Vary header
		w.Header().Add("Vary", "Accept-Encoding")
		// check client's accepted encodings
		encs := acceptedEncodings(r)
		// return if no acceptable encodings
		if len(encs) == 0 {
			w.WriteHeader(http.StatusNotAcceptable)
			return
		}
		if encs[0] == encGzip {
			// cannot accept Range requests for possibly gzipped
			// responses
			r.Header.Del("Range")
			// create new ResponseWriter
			w = newGzipResponseWriter(w, ctMap, encs)
			defer w.(*gzipResponseWriter).Close()
		}
		// call original handler's ServeHTTP
		h.ServeHTTP(w, r)
	})
}
<commit_msg>Correct documentation regarding minimum size for compression<commit_after>/*
   Copyright 2015 The Httpgzip Authors. See the AUTHORS file at the
   top-level directory of this distribution and at
   <https://xi2.org/x/httpgzip/m/AUTHORS>.

   This file is part of Httpgzip.

   Httpgzip is free software: you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Httpgzip is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Httpgzip.  If not, see <https://www.gnu.org/licenses/>.
*/

// Package httpgzip implements an http.Handler wrapper adding gzip
// compression for appropriate requests.
//
// It attempts to properly parse the request's Accept-Encoding header
// according to RFC 2616 and does not do a simple string search for
// "gzip" (which will fail to do the correct thing for values such as
// "*" or "identity,gzip;q=0"). It will serve either gzip or identity
// content codings, or return 406 Not Acceptable status if it can do
// neither.
//
// It works correctly with handlers which honour Range request headers
// (such as http.FileServer) by removing the Range header for requests
// which prefer gzip encoding. This is necessary since Range requests
// apply to the gzipped content but the wrapped handler is not aware
// of the compression when it writes byte ranges. The Accept-Ranges
// header is also stripped from corresponding responses.
//
// For requests which prefer gzip encoding a Content-Type header is
// set using http.DetectContentType if it is not set by the wrapped
// handler.
package httpgzip // import "xi2.org/x/httpgzip"

import (
	"bytes"
	"compress/gzip"
	"mime"
	"net/http"
	"strconv"
	"strings"
	"sync"
)

// DefaultContentTypes is the default list of content types for which
// a Handler considers gzip compression. This list originates from the
// file compression.conf within the Apache configuration found at
// https://html5boilerplate.com/.
var DefaultContentTypes = []string{
	"application/atom+xml",
	"application/javascript",
	"application/json",
	"application/ld+json",
	"application/manifest+json",
	"application/rdf+xml",
	"application/rss+xml",
	"application/schema+json",
	"application/vnd.geo+json",
	"application/vnd.ms-fontobject",
	"application/x-font-ttf",
	"application/x-javascript",
	"application/x-web-app-manifest+json",
	"application/xhtml+xml",
	"application/xml",
	"font/eot",
	"font/opentype",
	"image/bmp",
	"image/svg+xml",
	"image/vnd.microsoft.icon",
	"image/x-icon",
	"text/cache-manifest",
	"text/css",
	"text/html",
	"text/javascript",
	"text/plain",
	"text/vcard",
	"text/vnd.rim.location.xloc",
	"text/vtt",
	"text/x-component",
	"text/x-cross-domain-policy",
	"text/xml",
}

var gzipWriterPool = sync.Pool{
	New: func() interface{} { return gzip.NewWriter(nil) },
}

var gzipBufPool = sync.Pool{
	New: func() interface{} { return new(bytes.Buffer) },
}

// A gzipResponseWriter is a modified http.ResponseWriter. It adds
// gzip compression to certain responses, and there are two cases
// where this is done. Case 1 is when encs only allows gzip encoding
// and forbids identity. Case 2 is when encs prefers gzip encoding,
// the response is at least 512 bytes and the response's content type
// is in ctMap.
//
// A gzipResponseWriter sets the Content-Encoding and Content-Type
// headers when appropriate. It is important to call the Close method
// when writing is finished in order to flush and close the
// gzipResponseWriter. The slice encs must contain only encodings from
// {encGzip,encIdentity} and contain at least one encoding.
type gzipResponseWriter struct {
	http.ResponseWriter
	httpStatus int
	ctMap      map[string]struct{}
	encs       []encoding
	gw         *gzip.Writer
	buf        *bytes.Buffer
}

func newGzipResponseWriter(w http.ResponseWriter, ctMap map[string]struct{}, encs []encoding) *gzipResponseWriter {
	buf := gzipBufPool.Get().(*bytes.Buffer)
	buf.Reset()
	return &gzipResponseWriter{
		ResponseWriter: w,
		httpStatus:     http.StatusOK,
		ctMap:          ctMap,
		encs:           encs,
		buf:            buf}
}

// init gets called by Write once at least 512 bytes have been written
// to the temporary buffer buf, or by Close if it has not yet been
// called. Firstly it determines the content type, either from the
// Content-Type header, or by calling http.DetectContentType on
// buf. Then, if needed, a gzip.Writer is initialized. Lastly,
// appropriate headers are set and the ResponseWriter's WriteHeader
// method is called.
func (w *gzipResponseWriter) init() {
	cth := w.Header().Get("Content-Type")
	var ct string
	if cth != "" {
		ct = cth
	} else {
		ct = http.DetectContentType(w.buf.Bytes())
	}
	var gzipContentType bool
	if mt, _, err := mime.ParseMediaType(ct); err == nil {
		if _, ok := w.ctMap[mt]; ok {
			gzipContentType = true
		}
	}
	var useGzip bool
	if w.Header().Get("Content-Encoding") == "" && w.encs[0] == encGzip {
		if gzipContentType && w.buf.Len() >= 512 || len(w.encs) == 1 {
			useGzip = true
		}
	}
	if useGzip {
		w.gw = gzipWriterPool.Get().(*gzip.Writer)
		w.gw.Reset(w.ResponseWriter)
		w.Header().Del("Content-Length")
		w.Header().Set("Content-Encoding", "gzip")
	}
	w.Header().Del("Accept-Ranges")
	if cth == "" {
		w.Header().Set("Content-Type", ct)
	}
	w.ResponseWriter.WriteHeader(w.httpStatus)
}

func (w *gzipResponseWriter) Write(p []byte) (int, error) {
	var n, written int
	var err error
	if w.buf != nil {
		written = w.buf.Len()
		_, _ = w.buf.Write(p)
		if w.buf.Len() < 512 {
			return len(p), nil
		}
		w.init()
		p = w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
	}
	switch {
	case w.gw != nil:
		n, err = w.gw.Write(p)
	default:
		n, err = w.ResponseWriter.Write(p)
	}
	n -= written
	if n < 0 {
		n = 0
	}
	return n, err
}

func (w *gzipResponseWriter) WriteHeader(httpStatus int) {
	// postpone WriteHeader call until end of init method
	w.httpStatus = httpStatus
}

func (w *gzipResponseWriter) Close() (err error) {
	if w.buf != nil {
		w.init()
		p := w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
		switch {
		case w.gw != nil:
			_, err = w.gw.Write(p)
		default:
			_, err = w.ResponseWriter.Write(p)
		}
	}
	if w.gw != nil {
		e := w.gw.Close()
		if e != nil {
			err = e
		}
		gzipWriterPool.Put(w.gw)
		w.gw = nil
	}
	return err
}

// An encoding is a supported content coding.
type encoding int

const (
	encIdentity encoding = iota
	encGzip
)

// acceptedEncodings returns the supported content codings that are
// accepted by the request r. It returns a slice of encodings in
// client preference order.
//
// If the Sec-WebSocket-Key header is present then compressed content
// encodings are not considered.
//
// ref: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
func acceptedEncodings(r *http.Request) []encoding {
	h := r.Header.Get("Accept-Encoding")
	swk := r.Header.Get("Sec-WebSocket-Key")
	if h == "" {
		return []encoding{encIdentity}
	}
	gzip := float64(-1)    // -1 means not accepted, 0 -> 1 means value of q
	identity := float64(0) // -1 means not accepted, 0 -> 1 means value of q
	for _, s := range strings.Split(h, ",") {
		f := strings.Split(s, ";")
		f0 := strings.ToLower(strings.Trim(f[0], " "))
		q := float64(1.0)
		if len(f) > 1 {
			f1 := strings.ToLower(strings.Trim(f[1], " "))
			if strings.HasPrefix(f1, "q=") {
				if flt, err := strconv.ParseFloat(f1[2:], 32); err == nil {
					if flt >= 0 && flt <= 1 {
						q = flt
					}
				}
			}
		}
		if (f0 == "gzip" || f0 == "*") && q > gzip && swk == "" {
			gzip = q
		}
		if (f0 == "gzip" || f0 == "*") && q == 0 {
			gzip = -1
		}
		if (f0 == "identity" || f0 == "*") && q > identity {
			identity = q
		}
		if (f0 == "identity" || f0 == "*") && q == 0 {
			identity = -1
		}
	}
	switch {
	case gzip == -1 && identity == -1:
		return []encoding{}
	case gzip == -1:
		return []encoding{encIdentity}
	case identity == -1:
		return []encoding{encGzip}
	case identity > gzip:
		return []encoding{encIdentity, encGzip}
	default:
		return []encoding{encGzip, encIdentity}
	}
}

// NewHandler returns a new http.Handler which wraps a handler h
// adding gzip compression to certain responses. There are two cases
// where gzip compression is done. Case 1 is responses whose requests
// only allow gzip encoding and forbid identity encoding (identity
// encoding meaning no encoding). Case 2 is responses whose requests
// prefer gzip encoding, whose size is at least 512 bytes and whose
// content types are in contentTypes. If contentTypes is nil then
// DefaultContentTypes is considered instead.
//
// The new http.Handler sets the Content-Encoding, Vary and
// Content-Type headers in its responses as appropriate. If a request
// expresses a preference for gzip encoding then any Range headers are
// removed from the request before it is passed through to h and
// Accept-Ranges headers are stripped from corresponding
// responses. This happens regardless of whether gzip encoding is
// eventually used in the response or not.
func NewHandler(h http.Handler, contentTypes []string) http.Handler {
	if contentTypes == nil {
		contentTypes = DefaultContentTypes
	}
	ctMap := map[string]struct{}{}
	for _, ct := range contentTypes {
		ctMap[ct] = struct{}{}
	}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// add Vary header
		w.Header().Add("Vary", "Accept-Encoding")
		// check client's accepted encodings
		encs := acceptedEncodings(r)
		// return if no acceptable encodings
		if len(encs) == 0 {
			w.WriteHeader(http.StatusNotAcceptable)
			return
		}
		if encs[0] == encGzip {
			// cannot accept Range requests for possibly gzipped
			// responses
			r.Header.Del("Range")
			// create new ResponseWriter
			w = newGzipResponseWriter(w, ctMap, encs)
			defer w.(*gzipResponseWriter).Close()
		}
		// call original handler's ServeHTTP
		h.ServeHTTP(w, r)
	})
}
<|endoftext|>


<commit_before>package events

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/byuoitav/event-router-microservice/eventinfrastructure"
	"github.com/gorilla/websocket"
	"github.com/xuther/go-message-router/common"
	"github.com/xuther/go-message-router/subscriber"
)

var Sub subscriber.Subscriber

type filter func(common.Message) eventinfrastructure.EventInfo

type ClientManager struct {
	clients    map[*Client]bool
	Broadcast  chan common.Message
	register   chan *Client
	unregister chan *Client
}

type Client struct {
	socket *websocket.Conn
	send   chan []byte
}

var Manager = &ClientManager{
	Broadcast:  make(chan common.Message, 100),
	register:   make(chan *Client),
	unregister: make(chan *Client),
	clients:    make(map[*Client]bool),
}

func SubInit() {
	var err error
	Sub, err = subscriber.NewSubscriber(10)
	if err != nil {
		log.Fatalf("Could not create a subscriber. Error: %v\n", err.Error())
	}

	Sub.Subscribe("localhost:7000", []string{eventinfrastructure.UI})
	log.Printf("[Routing] Subscribed to %s events on port :7000", eventinfrastructure.UI)

	go Manager.Start(UIFilter)
	go SubListen()
}

func (manager *ClientManager) Start(f filter) {
	for {
		select {
		case conn := <-manager.register:
			log.Printf("[Socket] Registering connection")
			manager.clients[conn] = true

		case conn := <-manager.unregister:
			log.Printf("[Socket] Unregistering connection")
			if _, ok := manager.clients[conn]; ok {
				close(conn.send)
				delete(manager.clients, conn)
			}

		case event := <-manager.Broadcast:
			e := f(event)
			toSend, err := json.Marshal(&e)
			if err != nil {
				log.Printf("error: %s", err.Error())
				continue
			}
			for conn := range manager.clients {
				select {
				case conn.send <- toSend:
				default:
					close(conn.send)
					delete(manager.clients, conn)
				}
			}
		}
	}
}

func (c *Client) write() {
	for {
		select {
		case message, ok := <-c.send:
			if !ok {
				log.Printf("[Client] client send channel closed")
				return
			}

			log.Printf("[Client] Writing event to client")
			c.socket.WriteMessage(websocket.TextMessage, message)
		}
	}
}

func (c *Client) read() {
	for {
		if _, _, err := c.socket.NextReader(); err != nil {
			log.Printf("[Client] Unregistering connection")
			Manager.unregister <- c
			break
		}
	}
}

func StartWebClient(res http.ResponseWriter, req *http.Request) {
	conn, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(res, req, nil)
	if err != nil {
		http.NotFound(res, req)
		return
	}

	client := &Client{socket: conn, send: make(chan []byte)}

	Manager.register <- client

	go client.write()
	go client.read()
}

func UIFilter(event common.Message) eventinfrastructure.EventInfo {
	var e eventinfrastructure.Event
	err := json.Unmarshal(event.MessageBody, &e)
	if err != nil {
		log.Printf("error: %v", err.Error())
		return eventinfrastructure.EventInfo{}
	}

	return e.Event
}

func SubListen() {
	log.Printf("Subscriber is listening for events")

	for {
		message := Sub.Read()
		Manager.Broadcast <- message
	}
}
<commit_msg>retry connection to router<commit_after>package events

import (
	"encoding/json"
	"log"
	"net/http"
	"time"

	"github.com/byuoitav/event-router-microservice/eventinfrastructure"
	"github.com/gorilla/websocket"
	"github.com/xuther/go-message-router/common"
	"github.com/xuther/go-message-router/subscriber"
)

var Sub subscriber.Subscriber

type filter func(common.Message) eventinfrastructure.EventInfo

type ClientManager struct {
	clients    map[*Client]bool
	Broadcast  chan common.Message
	register   chan *Client
	unregister chan *Client
}

type Client struct {
	socket *websocket.Conn
	send   chan []byte
}

var Manager = &ClientManager{
	Broadcast:  make(chan common.Message, 100),
	register:   make(chan *Client),
	unregister: make(chan *Client),
	clients:    make(map[*Client]bool),
}

func SubInit() {
	var err error
	Sub, err = subscriber.NewSubscriber(10)
	if err != nil {
		log.Printf("Could not create a subscriber. Error: %v\n", err.Error())
	}

	for ok := true; ok; ok = (err != nil) {
		err = Sub.Subscribe("localhost:7000", []string{eventinfrastructure.UI})
		if err != nil {
			log.Printf("Failed to subscribe to events on port :7000. Trying again...")
			time.Sleep(1 * time.Second)
		}
	}

	log.Printf("[Routing] Subscribed to %s events on port :7000", eventinfrastructure.UI)

	go Manager.Start(UIFilter)
	go SubListen()
}

func (manager *ClientManager) Start(f filter) {
	for {
		select {
		case conn := <-manager.register:
			log.Printf("[Socket] Registering connection")
			manager.clients[conn] = true

		case conn := <-manager.unregister:
			log.Printf("[Socket] Unregistering connection")
			if _, ok := manager.clients[conn]; ok {
				close(conn.send)
				delete(manager.clients, conn)
			}

		case event := <-manager.Broadcast:
			e := f(event)
			toSend, err := json.Marshal(&e)
			if err != nil {
				log.Printf("error: %s", err.Error())
				continue
			}
			for conn := range manager.clients {
				select {
				case conn.send <- toSend:
				default:
					close(conn.send)
					delete(manager.clients, conn)
				}
			}
		}
	}
}

func (c *Client) write() {
	for {
		select {
		case message, ok := <-c.send:
			if !ok {
				log.Printf("[Client] client send channel closed")
				return
			}

			log.Printf("[Client] Writing event to client")
			c.socket.WriteMessage(websocket.TextMessage, message)
		}
	}
}

func (c *Client) read() {
	for {
		if _, _, err := c.socket.NextReader(); err != nil {
			log.Printf("[Client] Unregistering connection")
			Manager.unregister <- c
			break
		}
	}
}

func StartWebClient(res http.ResponseWriter, req *http.Request) {
	conn, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(res, req, nil)
	if err != nil {
		http.NotFound(res, req)
		return
	}

	client := &Client{socket: conn, send: make(chan []byte)}

	Manager.register <- client

	go client.write()
	go client.read()
}

func UIFilter(event common.Message) eventinfrastructure.EventInfo {
	var e eventinfrastructure.Event
	err := json.Unmarshal(event.MessageBody, &e)
	if err != nil {
		log.Printf("error: %v", err.Error())
		return eventinfrastructure.EventInfo{}
	}

	return e.Event
}

func SubListen() {
	log.Printf("Subscriber is listening for events")

	for {
		message := Sub.Read()
		Manager.Broadcast <- message
	}
}
<|endoftext|>


<commit_before>// Copyright 2019 The LUCI Authors.
//
// 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 internal

import (
	"net/http"

	"cloud.google.com/go/spanner"
	"github.com/golang/protobuf/proto"
	"golang.org/x/net/context"

	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/grpc/grpcutil"
	"go.chromium.org/luci/server/auth"
)

var httpClientCtxKey = "context key for a *http.Client"

// WithHTTPClient returns a context with the client embedded.
func WithHTTPClient(ctx context.Context, client *http.Client) context.Context {
	return context.WithValue(ctx, &httpClientCtxKey, client)
}

// HTTPClient retrieves the current http.client from the context.
func HTTPClient(ctx context.Context) *http.Client {
	client, ok := ctx.Value(&httpClientCtxKey).(*http.Client)
	if !ok {
		panic("no HTTP client in context")
	}
	return client
}

// CommonPrelude must be used as a prelude in all ResultDB services.
// Verifies access.
func CommonPrelude(ctx context.Context, methodName string, req proto.Message) (retCtx context.Context, err error) {
	defer func() {
		err = grpcutil.GRPCifyAndLogErr(ctx, err)
	}()

	// TODO(crbug.com/1013316): replace this with project-identified transport.
	tr, err := auth.GetRPCTransport(ctx, auth.AsSelf)
	if err != nil {
		return ctx, err
	}
	ctx = WithHTTPClient(ctx, &http.Client{Transport: tr})

	if err := verifyAccess(ctx); err != nil {
		return nil, err
	}
	return ctx, nil
}

// CommonPostlude must be used as a postlude in all ResultDB services.
// Extracts an error code from a grpcutil.Tag, logs a
// stack trace and returns a gRPC-native error.
func CommonPostlude(ctx context.Context, methodName string, rsp proto.Message, err error) error {
	// Make sure we never return spanner-level errors to the clients.
	if _, ok := err.(*spanner.Error); ok {
		err = errors.
			Annotate(err, "Internal server error").
			InternalReason("%s", err).
			Tag(grpcutil.InternalTag).
			Err()
	}

	return grpcutil.GRPCifyAndLogErr(ctx, err)
}

func verifyAccess(ctx context.Context) error {
	// TODO(crbug.com/1013316): use realms.

	// WARNING: removing this restriction requires removing AsSelf HTTP client
	// that is setup in CommonPrelude()
	// DO NOT REMOVE this until that's done.
	switch allowed, err := auth.IsMember(ctx, accessGroup); {
	case err != nil:
		return err

	case !allowed:
		return errors.
			Reason("%s is not in %q CIA group", auth.CurrentIdentity(ctx), accessGroup).
			Tag(grpcutil.PermissionDeniedTag).
			Err()

	default:
		return nil
	}
}
<commit_msg>[resultdb] Encapsulate internal errors<commit_after>// Copyright 2019 The LUCI Authors.
//
// 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 internal

import (
	"net/http"

	"github.com/golang/protobuf/proto"
	"golang.org/x/net/context"

	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/grpc/grpcutil"
	"go.chromium.org/luci/server/auth"
)

var httpClientCtxKey = "context key for a *http.Client"

// WithHTTPClient returns a context with the client embedded.
func WithHTTPClient(ctx context.Context, client *http.Client) context.Context {
	return context.WithValue(ctx, &httpClientCtxKey, client)
}

// HTTPClient retrieves the current http.client from the context.
func HTTPClient(ctx context.Context) *http.Client {
	client, ok := ctx.Value(&httpClientCtxKey).(*http.Client)
	if !ok {
		panic("no HTTP client in context")
	}
	return client
}

// CommonPrelude must be used as a prelude in all ResultDB services.
// Verifies access.
func CommonPrelude(ctx context.Context, methodName string, req proto.Message) (retCtx context.Context, err error) {
	defer func() {
		err = grpcutil.GRPCifyAndLogErr(ctx, err)
	}()

	// TODO(crbug.com/1013316): replace this with project-identified transport.
	tr, err := auth.GetRPCTransport(ctx, auth.AsSelf)
	if err != nil {
		return ctx, err
	}
	ctx = WithHTTPClient(ctx, &http.Client{Transport: tr})

	if err := verifyAccess(ctx); err != nil {
		return nil, err
	}
	return ctx, nil
}

// CommonPostlude must be used as a postlude in all ResultDB services.
// Extracts an error code from a grpcutil.Tag, logs a
// stack trace and returns a gRPC-native error.
func CommonPostlude(ctx context.Context, methodName string, rsp proto.Message, err error) error {
	// Resultdb codebase uses grpcutil tags for error codes.
	// If the error is not explicitly tagged, then it is an internal error.
	// This prevents returning internal gRPC errors to our clients.
	if _, ok := grpcutil.Tag.In(err); !ok {
		err = errors.
			Annotate(err, "Internal server error").
			InternalReason("%s", err).
			Tag(grpcutil.InternalTag).
			Err()
	}

	return grpcutil.GRPCifyAndLogErr(ctx, err)
}

func verifyAccess(ctx context.Context) error {
	// TODO(crbug.com/1013316): use realms.

	// WARNING: removing this restriction requires removing AsSelf HTTP client
	// that is setup in CommonPrelude()
	// DO NOT REMOVE this until that's done.
	switch allowed, err := auth.IsMember(ctx, accessGroup); {
	case err != nil:
		return err

	case !allowed:
		return errors.
			Reason("%s is not in %q CIA group", auth.CurrentIdentity(ctx), accessGroup).
			Tag(grpcutil.PermissionDeniedTag).
			Err()

	default:
		return nil
	}
}
<|endoftext|>


<commit_before>/*
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 main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"

	"golang.org/x/oauth2"
	"google.golang.org/api/googleapi"
	"google.golang.org/api/youtube/v3"
)

type chanChan chan chan struct{}

var (
	filename          = flag.String("filename", "", "video filename. Can be a URL. Read from stdin with '-'")
	thumbnail         = flag.String("thumbnail", "", "thumbnail filename. Can be a URL")
	caption           = flag.String("caption", "", "caption filename. Can be a URL")
	title             = flag.String("title", "video title", "video title")
	description       = flag.String("description", "uploaded by youtubeuploader", "video description")
	language          = flag.String("language", "en", "video language")
	categoryId        = flag.String("categoryId", "", "video category Id")
	tags              = flag.String("tags", "", "comma separated list of video tags")
	privacy           = flag.String("privacy", "private", "video privacy status")
	quiet             = flag.Bool("quiet", false, "suppress progress indicator")
	rate              = flag.Int("ratelimit", 0, "rate limit upload in Kbps. No limit by default")
	metaJSON          = flag.String("metaJSON", "", "JSON file containing title,description,tags etc (optional)")
	metaJSONout       = flag.String("metaJSONout", "", "filename to write uploaded video metadata into (optional)")
	limitBetween      = flag.String("limitBetween", "", "only rate limit between these times e.g. 10:00-14:00 (local time zone)")
	headlessAuth      = flag.Bool("headlessAuth", false, "set this if no browser available for the oauth authorisation step")
	oAuthPort         = flag.Int("oAuthPort", 8080, "TCP port to listen on when requesting an oAuth token")
	showAppVersion    = flag.Bool("version", false, "show version")
	chunksize         = flag.Int("chunksize", googleapi.DefaultUploadChunkSize, "size (in bytes) of each upload chunk. A zero value will cause all data to be uploaded in a single request")
	notifySubscribers = flag.Bool("notify", true, "notify channel subscribers of new video")

	// this is set by compile-time to match git tag
	appVersion string = "unknown"
)

func main() {
	flag.Parse()

	if *showAppVersion {
		fmt.Printf("Youtubeuploader version: %s\n", appVersion)
		os.Exit(0)
	}

	if *filename == "" {
		fmt.Printf("\nYou must provide a filename of a video file to upload\n")
		fmt.Printf("\nUsage:\n")
		flag.PrintDefaults()
		os.Exit(1)
	}

	var reader io.ReadCloser
	var filesize int64
	var err error

	var limitRange limitRange
	if *limitBetween != "" {
		limitRange, err = parseLimitBetween(*limitBetween)
		if err != nil {
			fmt.Printf("Invalid value for -limitBetween: %v", err)
			os.Exit(1)
		}
	}

	reader, filesize, err = Open(*filename)
	if err != nil {
		log.Fatal(err)
	}
	defer reader.Close()

	var thumbReader io.ReadCloser
	if *thumbnail != "" {
		thumbReader, _, err = Open(*thumbnail)
		if err != nil {
			log.Fatal(err)
		}
		defer thumbReader.Close()
	}

	var captionReader io.ReadCloser
	if *caption != "" {
		captionReader, _, err = Open(*caption)
		if err != nil {
			log.Fatal(err)
		}
		defer captionReader.Close()
	}

	ctx := context.Background()
	transport := &limitTransport{rt: http.DefaultTransport, lr: limitRange, filesize: filesize}
	ctx = context.WithValue(ctx, oauth2.HTTPClient, &http.Client{
		Transport: transport,
	})

	var quitChan chanChan
	if !*quiet {
		quitChan = make(chanChan)
		go func() {
			Progress(quitChan, transport, filesize)
		}()
	}
	client, err := buildOAuthHTTPClient(ctx, []string{youtube.YoutubeUploadScope, youtube.YoutubepartnerScope, youtube.YoutubeScope})
	if err != nil {
		log.Fatalf("Error building OAuth client: %v", err)
	}

	upload := &youtube.Video{
		Snippet:          &youtube.VideoSnippet{},
		RecordingDetails: &youtube.VideoRecordingDetails{},
		Status:           &youtube.VideoStatus{},
	}

	videoMeta, err := LoadVideoMeta(*metaJSON, upload)
	if err != nil {
		log.Fatalf("Error loading video meta data: %s", err)
	}

	service, err := youtube.New(client)
	if err != nil {
		log.Fatalf("Error creating Youtube client: %s", err)
	}

	if *filename == "-" {
		fmt.Printf("Uploading file from pipe\n")
	} else {
		fmt.Printf("Uploading file '%s'\n", *filename)
	}

	var option googleapi.MediaOption
	var video *youtube.Video

	option = googleapi.ChunkSize(*chunksize)

	call := service.Videos.Insert("snippet,status,recordingDetails", upload)
	video, err = call.NotifySubscribers(*notifySubscribers).Media(reader, option).Do()

	if quitChan != nil {
		quit := make(chan struct{})
		quitChan <- quit
		<-quit
	}

	if err != nil {
		if video != nil {
			log.Fatalf("Error making YouTube API call: %v, %v", err, video.HTTPStatusCode)
		} else {
			log.Fatalf("Error making YouTube API call: %v", err)
		}
	}
	fmt.Printf("Upload successful! Video ID: %v\n", video.Id)

	if *metaJSONout != "" {
		videoMeta, _ := json.Marshal(video)
		err = ioutil.WriteFile(*metaJSONout, videoMeta, 0644)
		if err != nil {
			log.Fatalf("Error writing to video metadata file '%s': %s\n", *metaJSONout, err)
		}
		fmt.Printf("Wrote video metadata to file '%s'\n", *metaJSONout)
	}

	if thumbReader != nil {
		log.Printf("Uploading thumbnail '%s'...\n", *thumbnail)
		_, err = service.Thumbnails.Set(video.Id).Media(thumbReader).Do()
		if err != nil {
			log.Fatalf("Error making YouTube API call: %v", err)
		}
		fmt.Printf("Thumbnail uploaded!\n")
	}

	// Insert caption
	if captionReader != nil {
		captionObj := &youtube.Caption{
			Snippet: &youtube.CaptionSnippet{},
		}
		captionObj.Snippet.VideoId = video.Id
		captionObj.Snippet.Language = *language
		captionObj.Snippet.Name = *language
		captionInsert := service.Captions.Insert("snippet", captionObj).Sync(true)
		captionRes, err := captionInsert.Media(captionReader).Do()
		if err != nil {
			if captionRes != nil {
				log.Fatalf("Error inserting caption: %v, %v", err, captionRes.HTTPStatusCode)
			} else {
				log.Fatalf("Error inserting caption: %v", err)
			}
		}
		fmt.Printf("Caption uploaded!\n")
	}

	plx := &Playlistx{}
	if upload.Status.PrivacyStatus != "" {
		plx.PrivacyStatus = upload.Status.PrivacyStatus
	}
	// PlaylistID is deprecated in favour of PlaylistIDs
	if videoMeta.PlaylistID != "" {
		plx.Id = videoMeta.PlaylistID
		err = plx.AddVideoToPlaylist(service, video.Id)
		if err != nil {
			log.Fatalf("Error adding video to playlist: %s", err)
		}
	}

	if len(videoMeta.PlaylistIDs) > 0 {
		plx.Title = ""
		for _, pid := range videoMeta.PlaylistIDs {
			plx.Id = pid
			err = plx.AddVideoToPlaylist(service, video.Id)
			if err != nil {
				log.Fatalf("Error adding video to playlist: %s", err)
			}
		}
	}

	if len(videoMeta.PlaylistTitles) > 0 {
		plx.Id = ""
		for _, title := range videoMeta.PlaylistTitles {
			plx.Title = title
			err = plx.AddVideoToPlaylist(service, video.Id)
			if err != nil {
				log.Fatalf("Error adding video to playlist: %s", err)
			}
		}
	}
}
<commit_msg>Set video title to video file name by default<commit_after>/*
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 main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"golang.org/x/oauth2"
	"google.golang.org/api/googleapi"
	"google.golang.org/api/youtube/v3"
)

type chanChan chan chan struct{}

var (
	filename          = flag.String("filename", "", "video filename. Can be a URL. Read from stdin with '-'")
	thumbnail         = flag.String("thumbnail", "", "thumbnail filename. Can be a URL")
	caption           = flag.String("caption", "", "caption filename. Can be a URL")
	title             = flag.String("title", "", "video title")
	description       = flag.String("description", "uploaded by youtubeuploader", "video description")
	language          = flag.String("language", "en", "video language")
	categoryId        = flag.String("categoryId", "", "video category Id")
	tags              = flag.String("tags", "", "comma separated list of video tags")
	privacy           = flag.String("privacy", "private", "video privacy status")
	quiet             = flag.Bool("quiet", false, "suppress progress indicator")
	rate              = flag.Int("ratelimit", 0, "rate limit upload in Kbps. No limit by default")
	metaJSON          = flag.String("metaJSON", "", "JSON file containing title,description,tags etc (optional)")
	metaJSONout       = flag.String("metaJSONout", "", "filename to write uploaded video metadata into (optional)")
	limitBetween      = flag.String("limitBetween", "", "only rate limit between these times e.g. 10:00-14:00 (local time zone)")
	headlessAuth      = flag.Bool("headlessAuth", false, "set this if no browser available for the oauth authorisation step")
	oAuthPort         = flag.Int("oAuthPort", 8080, "TCP port to listen on when requesting an oAuth token")
	showAppVersion    = flag.Bool("version", false, "show version")
	chunksize         = flag.Int("chunksize", googleapi.DefaultUploadChunkSize, "size (in bytes) of each upload chunk. A zero value will cause all data to be uploaded in a single request")
	notifySubscribers = flag.Bool("notify", true, "notify channel subscribers of new video")

	// this is set by compile-time to match git tag
	appVersion string = "unknown"
)

func main() {
	flag.Parse()

	if *showAppVersion {
		fmt.Printf("Youtubeuploader version: %s\n", appVersion)
		os.Exit(0)
	}

	if *filename == "" {
		fmt.Printf("\nYou must provide a filename of a video file to upload\n")
		fmt.Printf("\nUsage:\n")
		flag.PrintDefaults()
		os.Exit(1)
	}

	if *title == "" {
		*title = strings.Replace(filepath.Base(*filename),filepath.Ext(*filename),"",-1)
	}

	var reader io.ReadCloser
	var filesize int64
	var err error

	var limitRange limitRange
	if *limitBetween != "" {
		limitRange, err = parseLimitBetween(*limitBetween)
		if err != nil {
			fmt.Printf("Invalid value for -limitBetween: %v", err)
			os.Exit(1)
		}
	}

	reader, filesize, err = Open(*filename)
	if err != nil {
		log.Fatal(err)
	}
	defer reader.Close()

	var thumbReader io.ReadCloser
	if *thumbnail != "" {
		thumbReader, _, err = Open(*thumbnail)
		if err != nil {
			log.Fatal(err)
		}
		defer thumbReader.Close()
	}

	var captionReader io.ReadCloser
	if *caption != "" {
		captionReader, _, err = Open(*caption)
		if err != nil {
			log.Fatal(err)
		}
		defer captionReader.Close()
	}

	ctx := context.Background()
	transport := &limitTransport{rt: http.DefaultTransport, lr: limitRange, filesize: filesize}
	ctx = context.WithValue(ctx, oauth2.HTTPClient, &http.Client{
		Transport: transport,
	})

	var quitChan chanChan
	if !*quiet {
		quitChan = make(chanChan)
		go func() {
			Progress(quitChan, transport, filesize)
		}()
	}
	client, err := buildOAuthHTTPClient(ctx, []string{youtube.YoutubeUploadScope, youtube.YoutubepartnerScope, youtube.YoutubeScope})
	if err != nil {
		log.Fatalf("Error building OAuth client: %v", err)
	}

	upload := &youtube.Video{
		Snippet:          &youtube.VideoSnippet{},
		RecordingDetails: &youtube.VideoRecordingDetails{},
		Status:           &youtube.VideoStatus{},
	}

	videoMeta, err := LoadVideoMeta(*metaJSON, upload)
	if err != nil {
		log.Fatalf("Error loading video meta data: %s", err)
	}

	service, err := youtube.New(client)
	if err != nil {
		log.Fatalf("Error creating Youtube client: %s", err)
	}

	if *filename == "-" {
		fmt.Printf("Uploading file from pipe\n")
	} else {
		fmt.Printf("Uploading file '%s'\n", *filename)
	}

	var option googleapi.MediaOption
	var video *youtube.Video

	option = googleapi.ChunkSize(*chunksize)

	call := service.Videos.Insert("snippet,status,recordingDetails", upload)
	video, err = call.NotifySubscribers(*notifySubscribers).Media(reader, option).Do()

	if quitChan != nil {
		quit := make(chan struct{})
		quitChan <- quit
		<-quit
	}

	if err != nil {
		if video != nil {
			log.Fatalf("Error making YouTube API call: %v, %v", err, video.HTTPStatusCode)
		} else {
			log.Fatalf("Error making YouTube API call: %v", err)
		}
	}
	fmt.Printf("Upload successful! Video ID: %v\n", video.Id)

	if *metaJSONout != "" {
		videoMeta, _ := json.Marshal(video)
		err = ioutil.WriteFile(*metaJSONout, videoMeta, 0644)
		if err != nil {
			log.Fatalf("Error writing to video metadata file '%s': %s\n", *metaJSONout, err)
		}
		fmt.Printf("Wrote video metadata to file '%s'\n", *metaJSONout)
	}

	if thumbReader != nil {
		log.Printf("Uploading thumbnail '%s'...\n", *thumbnail)
		_, err = service.Thumbnails.Set(video.Id).Media(thumbReader).Do()
		if err != nil {
			log.Fatalf("Error making YouTube API call: %v", err)
		}
		fmt.Printf("Thumbnail uploaded!\n")
	}

	// Insert caption
	if captionReader != nil {
		captionObj := &youtube.Caption{
			Snippet: &youtube.CaptionSnippet{},
		}
		captionObj.Snippet.VideoId = video.Id
		captionObj.Snippet.Language = *language
		captionObj.Snippet.Name = *language
		captionInsert := service.Captions.Insert("snippet", captionObj).Sync(true)
		captionRes, err := captionInsert.Media(captionReader).Do()
		if err != nil {
			if captionRes != nil {
				log.Fatalf("Error inserting caption: %v, %v", err, captionRes.HTTPStatusCode)
			} else {
				log.Fatalf("Error inserting caption: %v", err)
			}
		}
		fmt.Printf("Caption uploaded!\n")
	}

	plx := &Playlistx{}
	if upload.Status.PrivacyStatus != "" {
		plx.PrivacyStatus = upload.Status.PrivacyStatus
	}
	// PlaylistID is deprecated in favour of PlaylistIDs
	if videoMeta.PlaylistID != "" {
		plx.Id = videoMeta.PlaylistID
		err = plx.AddVideoToPlaylist(service, video.Id)
		if err != nil {
			log.Fatalf("Error adding video to playlist: %s", err)
		}
	}

	if len(videoMeta.PlaylistIDs) > 0 {
		plx.Title = ""
		for _, pid := range videoMeta.PlaylistIDs {
			plx.Id = pid
			err = plx.AddVideoToPlaylist(service, video.Id)
			if err != nil {
				log.Fatalf("Error adding video to playlist: %s", err)
			}
		}
	}

	if len(videoMeta.PlaylistTitles) > 0 {
		plx.Id = ""
		for _, title := range videoMeta.PlaylistTitles {
			plx.Title = title
			err = plx.AddVideoToPlaylist(service, video.Id)
			if err != nil {
				log.Fatalf("Error adding video to playlist: %s", err)
			}
		}
	}
}
<|endoftext|>


<commit_before>package hlt

import (
	"fmt"
	"math"
	"strconv"
)

// DockingStatus represents possible ship.DockingStatus values
type DockingStatus int

const (
	// UNDOCKED ship.DockingStatus value
	UNDOCKED DockingStatus = iota
	// DOCKING ship.DockingStatus value
	DOCKING
	// DOCKED ship.DockingStatus value
	DOCKED
	// UNDOCKING ship.DockingStatus value
	UNDOCKING
)

// Entity captures spacial and ownership state for Planets and Ships
type Entity struct {
	X      float64
	Y      float64
	Radius float64
	Health float64
	Owner  int
	ID     int
}

// Position in 2D space
type Position struct {
	X, Y float64
}

// Planet object from which Halite is mined
type Planet struct {
	Entity
	NumDockingSpots    float64
	NumDockedShips     float64
	CurrentProduction  float64
	RemainingResources float64
	DockedShipIDs      []int
	DockedShips        []Ship
	Owned              float64
	Distance           float64
}

// Ship is a player controlled Entity made for the purpose of doing combat and mining Halite
type Ship struct {
	Entity
	VelX float64
	VelY float64

	PlanetID        int
	Planet          Planet
	DockingStatus   DockingStatus
	DockingProgress float64
	WeaponCooldown  float64
}

// CalculateDistanceTo returns a euclidean distance to the target
func (entity Entity) CalculateDistanceTo(target Entity) float64 {
	dx := target.X - entity.X
	dy := target.Y - entity.Y

	return math.Sqrt(dx*dx + dy*dy)
}

// CalculateAngleTo returns an angle in degrees to the target
func (entity Entity) CalculateAngleTo(target Entity) float64 {
	return RadToDeg(entity.CalculateRadAngleTo(target))
}

// CalculateRadAngleTo returns an angle in radians to the target
func (entity Entity) CalculateRadAngleTo(target Entity) float64 {
	dx := target.X - entity.X
	dy := target.Y - entity.Y

	return math.Atan2(dy, dx)
}

// ClosestPointTo returns the closest point that is at least minDistance from the target
func (entity Entity) ClosestPointTo(target Entity, minDistance float64) Entity {
	dist := entity.CalculateDistanceTo(target) - target.Radius - minDistance
	angle := target.CalculateRadAngleTo(entity)
	x := target.X + dist*math.Cos(angle)
	y := target.Y + dist*math.Sin(angle)
	return Entity{
		X:      x,
		Y:      y,
		Radius: 0,
		Health: 0,
		Owner:  -1,
		ID:     -1,
	}
}

// ParseShip from a slice of game state tokens
func ParseShip(playerID int, tokens []string) (Ship, []string) {
	shipID, _ := strconv.Atoi(tokens[0])
	shipX, _ := strconv.ParseFloat(tokens[1], 64)
	shipY, _ := strconv.ParseFloat(tokens[2], 64)
	shipHealth, _ := strconv.ParseFloat(tokens[3], 64)
	shipVelX, _ := strconv.ParseFloat(tokens[4], 64)
	shipVelY, _ := strconv.ParseFloat(tokens[5], 64)
	shipDockingStatus, _ := strconv.Atoi(tokens[6])
	shipPlanetID, _ := strconv.Atoi(tokens[7])
	shipDockingProgress, _ := strconv.ParseFloat(tokens[8], 64)
	shipWeaponCooldown, _ := strconv.ParseFloat(tokens[9], 64)

	shipEntity := Entity{
		X:      shipX,
		Y:      shipY,
		Radius: .5,
		Health: shipHealth,
		Owner:  playerID,
		ID:     shipID,
	}

	ship := Ship{
		PlanetID:        shipPlanetID,
		DockingStatus:   IntToDockingStatus(shipDockingStatus),
		DockingProgress: shipDockingProgress,
		WeaponCooldown:  shipWeaponCooldown,
		VelX:            shipVelX,
		VelY:            shipVelY,
		Entity:          shipEntity,
	}

	return ship, tokens[10:]
}

// ParsePlanet from a slice of game state tokens
func ParsePlanet(tokens []string) (Planet, []string) {
	planetID, _ := strconv.Atoi(tokens[0])
	planetX, _ := strconv.ParseFloat(tokens[1], 64)
	planetY, _ := strconv.ParseFloat(tokens[2], 64)
	planetHealth, _ := strconv.ParseFloat(tokens[3], 64)
	planetRadius, _ := strconv.ParseFloat(tokens[4], 64)
	planetNumDockingSpots, _ := strconv.ParseFloat(tokens[5], 64)
	planetCurrentProduction, _ := strconv.ParseFloat(tokens[6], 64)
	planetRemainingResources, _ := strconv.ParseFloat(tokens[7], 64)
	planetOwned, _ := strconv.ParseFloat(tokens[8], 64)
	planetOwner, _ := strconv.Atoi(tokens[9])
	planetNumDockedShips, _ := strconv.ParseFloat(tokens[10], 64)

	planetEntity := Entity{
		X:      planetX,
		Y:      planetY,
		Radius: planetRadius,
		Health: planetHealth,
		Owner:  planetOwner,
		ID:     planetID,
	}

	planet := Planet{
		NumDockingSpots:    planetNumDockingSpots,
		NumDockedShips:     planetNumDockedShips,
		CurrentProduction:  planetCurrentProduction,
		RemainingResources: planetRemainingResources,
		DockedShipIDs:      nil,
		DockedShips:        nil,
		Owned:              planetOwned,
		Entity:             planetEntity,
	}

	for i := 0; i < int(planetNumDockedShips); i++ {
		dockedShipID, _ := strconv.Atoi(tokens[11+i])
		planet.DockedShipIDs = append(planet.DockedShipIDs, dockedShipID)
	}
	return planet, tokens[11+int(planetNumDockedShips):]
}

// IntToDockingStatus converts an int to a DockingStatus
func IntToDockingStatus(i int) DockingStatus {
	statuses := [4]DockingStatus{UNDOCKED, DOCKING, DOCKED, UNDOCKING}
	return statuses[i]
}

// Thrust generates a string describing the ship's intension to move during the current turn
func (ship Ship) Thrust(magnitude float64, angle float64) string {
	if angle < 0 {
		angle += 360
	} else if boundedAngle >= 360 {
		angle -= 360
	}
	boundedAngle := int(math.Floor(angle + .5))
	return fmt.Sprintf("t %s %s %s", strconv.Itoa(ship.ID), strconv.Itoa(int(magnitude)), strconv.Itoa(boundedAngle))
}

// Dock generates a string describing the ship's intension to dock during the current turn
func (ship Ship) Dock(planet Planet) string {
	return fmt.Sprintf("d %s %s", strconv.Itoa(ship.ID), strconv.Itoa(planet.ID))
}

// Undock generates a string describing the ship's intension to undock during the current turn
func (ship Ship) Undock() string {
	return fmt.Sprintf("u %s", strconv.Itoa(ship.ID))
}

// NavigateBasic demonstrates how the player might move ships through space
func (ship Ship) NavigateBasic(target Entity, gameMap Map) string {
	distance := ship.CalculateDistanceTo(target)
	safeDistance := distance - ship.Entity.Radius - target.Entity.Radius - .1

	angle := ship.CalculateAngleTo(target)
	speed := 7.0
	if distance < 10 {
		speed = 3.0
	}

	speed = math.Min(speed, safeDistance)
	return ship.Thrust(speed, angle)
}

// CanDock indicates that a ship is close enough to a given planet to dock
func (ship Ship) CanDock(planet Planet) bool {
	dist := ship.CalculateDistanceTo(planet.Entity)

	return dist <= (planet.Radius + 4)
}

// Navigate demonstrates how the player might negotiate obsticles between
// a ship and its target
func (ship Ship) Navigate(target Entity, gameMap Map) string {
	ob := gameMap.ObstaclesBetween(ship.Entity, target)

	if !ob {
		return ship.NavigateBasic(target, gameMap)
	}

	x0 := math.Min(ship.X, target.X)
	x2 := math.Max(ship.X, target.X)
	y0 := math.Min(ship.Y, target.Y)
	y2 := math.Max(ship.Y, target.Y)

	dx := (x2 - x0) / 5
	dy := (y2 - y0) / 5
	bestdist := 1000.0
	bestTarget := target

	for x1 := x0; x1 <= x2; x1 += dx {
		for y1 := y0; y1 <= y2; y1 += dy {
			intermediateTarget := Entity{
				X:      x1,
				Y:      y1,
				Radius: 0,
				Health: 0,
				Owner:  0,
				ID:     -1,
			}
			ob1 := gameMap.ObstaclesBetween(ship.Entity, intermediateTarget)
			if !ob1 {
				ob2 := gameMap.ObstaclesBetween(intermediateTarget, target)
				if !ob2 {
					totdist := math.Sqrt(math.Pow(x1-x0, 2)+math.Pow(y1-y0, 2)) + math.Sqrt(math.Pow(x1-x2, 2)+math.Pow(y1-y2, 2))
					if totdist < bestdist {
						bestdist = totdist
						bestTarget = intermediateTarget

					}
				}
			}
		}
	}

	return ship.NavigateBasic(bestTarget, gameMap)
}
<commit_msg>cleaner bounding<commit_after>package hlt

import (
	"fmt"
	"math"
	"strconv"
)

// DockingStatus represents possible ship.DockingStatus values
type DockingStatus int

const (
	// UNDOCKED ship.DockingStatus value
	UNDOCKED DockingStatus = iota
	// DOCKING ship.DockingStatus value
	DOCKING
	// DOCKED ship.DockingStatus value
	DOCKED
	// UNDOCKING ship.DockingStatus value
	UNDOCKING
)

// Entity captures spacial and ownership state for Planets and Ships
type Entity struct {
	X      float64
	Y      float64
	Radius float64
	Health float64
	Owner  int
	ID     int
}

// Position in 2D space
type Position struct {
	X, Y float64
}

// Planet object from which Halite is mined
type Planet struct {
	Entity
	NumDockingSpots    float64
	NumDockedShips     float64
	CurrentProduction  float64
	RemainingResources float64
	DockedShipIDs      []int
	DockedShips        []Ship
	Owned              float64
	Distance           float64
}

// Ship is a player controlled Entity made for the purpose of doing combat and mining Halite
type Ship struct {
	Entity
	VelX float64
	VelY float64

	PlanetID        int
	Planet          Planet
	DockingStatus   DockingStatus
	DockingProgress float64
	WeaponCooldown  float64
}

// CalculateDistanceTo returns a euclidean distance to the target
func (entity Entity) CalculateDistanceTo(target Entity) float64 {
	dx := target.X - entity.X
	dy := target.Y - entity.Y

	return math.Sqrt(dx*dx + dy*dy)
}

// CalculateAngleTo returns an angle in degrees to the target
func (entity Entity) CalculateAngleTo(target Entity) float64 {
	return RadToDeg(entity.CalculateRadAngleTo(target))
}

// CalculateRadAngleTo returns an angle in radians to the target
func (entity Entity) CalculateRadAngleTo(target Entity) float64 {
	dx := target.X - entity.X
	dy := target.Y - entity.Y

	return math.Atan2(dy, dx)
}

// ClosestPointTo returns the closest point that is at least minDistance from the target
func (entity Entity) ClosestPointTo(target Entity, minDistance float64) Entity {
	dist := entity.CalculateDistanceTo(target) - target.Radius - minDistance
	angle := target.CalculateRadAngleTo(entity)
	x := target.X + dist*math.Cos(angle)
	y := target.Y + dist*math.Sin(angle)
	return Entity{
		X:      x,
		Y:      y,
		Radius: 0,
		Health: 0,
		Owner:  -1,
		ID:     -1,
	}
}

// ParseShip from a slice of game state tokens
func ParseShip(playerID int, tokens []string) (Ship, []string) {
	shipID, _ := strconv.Atoi(tokens[0])
	shipX, _ := strconv.ParseFloat(tokens[1], 64)
	shipY, _ := strconv.ParseFloat(tokens[2], 64)
	shipHealth, _ := strconv.ParseFloat(tokens[3], 64)
	shipVelX, _ := strconv.ParseFloat(tokens[4], 64)
	shipVelY, _ := strconv.ParseFloat(tokens[5], 64)
	shipDockingStatus, _ := strconv.Atoi(tokens[6])
	shipPlanetID, _ := strconv.Atoi(tokens[7])
	shipDockingProgress, _ := strconv.ParseFloat(tokens[8], 64)
	shipWeaponCooldown, _ := strconv.ParseFloat(tokens[9], 64)

	shipEntity := Entity{
		X:      shipX,
		Y:      shipY,
		Radius: .5,
		Health: shipHealth,
		Owner:  playerID,
		ID:     shipID,
	}

	ship := Ship{
		PlanetID:        shipPlanetID,
		DockingStatus:   IntToDockingStatus(shipDockingStatus),
		DockingProgress: shipDockingProgress,
		WeaponCooldown:  shipWeaponCooldown,
		VelX:            shipVelX,
		VelY:            shipVelY,
		Entity:          shipEntity,
	}

	return ship, tokens[10:]
}

// ParsePlanet from a slice of game state tokens
func ParsePlanet(tokens []string) (Planet, []string) {
	planetID, _ := strconv.Atoi(tokens[0])
	planetX, _ := strconv.ParseFloat(tokens[1], 64)
	planetY, _ := strconv.ParseFloat(tokens[2], 64)
	planetHealth, _ := strconv.ParseFloat(tokens[3], 64)
	planetRadius, _ := strconv.ParseFloat(tokens[4], 64)
	planetNumDockingSpots, _ := strconv.ParseFloat(tokens[5], 64)
	planetCurrentProduction, _ := strconv.ParseFloat(tokens[6], 64)
	planetRemainingResources, _ := strconv.ParseFloat(tokens[7], 64)
	planetOwned, _ := strconv.ParseFloat(tokens[8], 64)
	planetOwner, _ := strconv.Atoi(tokens[9])
	planetNumDockedShips, _ := strconv.ParseFloat(tokens[10], 64)

	planetEntity := Entity{
		X:      planetX,
		Y:      planetY,
		Radius: planetRadius,
		Health: planetHealth,
		Owner:  planetOwner,
		ID:     planetID,
	}

	planet := Planet{
		NumDockingSpots:    planetNumDockingSpots,
		NumDockedShips:     planetNumDockedShips,
		CurrentProduction:  planetCurrentProduction,
		RemainingResources: planetRemainingResources,
		DockedShipIDs:      nil,
		DockedShips:        nil,
		Owned:              planetOwned,
		Entity:             planetEntity,
	}

	for i := 0; i < int(planetNumDockedShips); i++ {
		dockedShipID, _ := strconv.Atoi(tokens[11+i])
		planet.DockedShipIDs = append(planet.DockedShipIDs, dockedShipID)
	}
	return planet, tokens[11+int(planetNumDockedShips):]
}

// IntToDockingStatus converts an int to a DockingStatus
func IntToDockingStatus(i int) DockingStatus {
	statuses := [4]DockingStatus{UNDOCKED, DOCKING, DOCKED, UNDOCKING}
	return statuses[i]
}

// Thrust generates a string describing the ship's intension to move during the current turn
func (ship Ship) Thrust(magnitude float64, angle float64) string {
	if (angle > 0) {
		boundedAngle := int(math.Floor(angle + .5))
	} else {
		boundedAngle := int(math.Ceil(angle - .5))
	}
	boundedAngle = ((boundedAngle % 360) + 360) % 360
	return fmt.Sprintf("t %s %s %s", strconv.Itoa(ship.ID), strconv.Itoa(int(magnitude)), strconv.Itoa(boundedAngle))
}

// Dock generates a string describing the ship's intension to dock during the current turn
func (ship Ship) Dock(planet Planet) string {
	return fmt.Sprintf("d %s %s", strconv.Itoa(ship.ID), strconv.Itoa(planet.ID))
}

// Undock generates a string describing the ship's intension to undock during the current turn
func (ship Ship) Undock() string {
	return fmt.Sprintf("u %s", strconv.Itoa(ship.ID))
}

// NavigateBasic demonstrates how the player might move ships through space
func (ship Ship) NavigateBasic(target Entity, gameMap Map) string {
	distance := ship.CalculateDistanceTo(target)
	safeDistance := distance - ship.Entity.Radius - target.Entity.Radius - .1

	angle := ship.CalculateAngleTo(target)
	speed := 7.0
	if distance < 10 {
		speed = 3.0
	}

	speed = math.Min(speed, safeDistance)
	return ship.Thrust(speed, angle)
}

// CanDock indicates that a ship is close enough to a given planet to dock
func (ship Ship) CanDock(planet Planet) bool {
	dist := ship.CalculateDistanceTo(planet.Entity)

	return dist <= (planet.Radius + 4)
}

// Navigate demonstrates how the player might negotiate obsticles between
// a ship and its target
func (ship Ship) Navigate(target Entity, gameMap Map) string {
	ob := gameMap.ObstaclesBetween(ship.Entity, target)

	if !ob {
		return ship.NavigateBasic(target, gameMap)
	}

	x0 := math.Min(ship.X, target.X)
	x2 := math.Max(ship.X, target.X)
	y0 := math.Min(ship.Y, target.Y)
	y2 := math.Max(ship.Y, target.Y)

	dx := (x2 - x0) / 5
	dy := (y2 - y0) / 5
	bestdist := 1000.0
	bestTarget := target

	for x1 := x0; x1 <= x2; x1 += dx {
		for y1 := y0; y1 <= y2; y1 += dy {
			intermediateTarget := Entity{
				X:      x1,
				Y:      y1,
				Radius: 0,
				Health: 0,
				Owner:  0,
				ID:     -1,
			}
			ob1 := gameMap.ObstaclesBetween(ship.Entity, intermediateTarget)
			if !ob1 {
				ob2 := gameMap.ObstaclesBetween(intermediateTarget, target)
				if !ob2 {
					totdist := math.Sqrt(math.Pow(x1-x0, 2)+math.Pow(y1-y0, 2)) + math.Sqrt(math.Pow(x1-x2, 2)+math.Pow(y1-y2, 2))
					if totdist < bestdist {
						bestdist = totdist
						bestTarget = intermediateTarget

					}
				}
			}
		}
	}

	return ship.NavigateBasic(bestTarget, gameMap)
}
<|endoftext|>


<commit_before>// Copyright (c) 2018 Cisco and/or its affiliates.
//
// 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 descriptor

import (
	"bytes"
	"fmt"
	"net"
	"strings"

	"github.com/gogo/protobuf/proto"
	"github.com/pkg/errors"

	"github.com/ligato/cn-infra/logging"
	scheduler "github.com/ligato/vpp-agent/plugins/kvscheduler/api"
	ifdescriptor "github.com/ligato/vpp-agent/plugins/vppv2/ifplugin/descriptor"
	"github.com/ligato/vpp-agent/plugins/vppv2/l3plugin/descriptor/adapter"
	"github.com/ligato/vpp-agent/plugins/vppv2/l3plugin/vppcalls"
	"github.com/ligato/vpp-agent/plugins/vppv2/model/interfaces"
	"github.com/ligato/vpp-agent/plugins/vppv2/model/l3"
)

const (
	// StaticRouteDescriptorName is the name of the descriptor for static routes.
	StaticRouteDescriptorName = "vpp-static-route"

	// dependency labels
	routeOutInterfaceDep = "interface-exists"
)

// RouteDescriptor teaches KVScheduler how to configure VPP routes.
type RouteDescriptor struct {
	log          logging.Logger
	routeHandler vppcalls.RouteVppAPI
	scheduler    scheduler.KVScheduler
}

// NewRouteDescriptor creates a new instance of the Route descriptor.
func NewRouteDescriptor(scheduler scheduler.KVScheduler,
	routeHandler vppcalls.RouteVppAPI, log logging.PluginLogger) *RouteDescriptor {

	return &RouteDescriptor{
		scheduler:    scheduler,
		routeHandler: routeHandler,
		log:          log.NewLogger("static-route-descriptor"),
	}
}

// GetDescriptor returns descriptor suitable for registration (via adapter) with
// the KVScheduler.
func (d *RouteDescriptor) GetDescriptor() *adapter.StaticRouteDescriptor {
	return &adapter.StaticRouteDescriptor{
		Name: StaticRouteDescriptorName,
		KeySelector: func(key string) bool {
			return strings.HasPrefix(key, l3.RoutePrefix)
		},
		ValueTypeName:   proto.MessageName(&l3.StaticRoute{}),
		ValueComparator: d.EquivalentRoutes,
		NBKeyPrefix:     l3.RoutePrefix,
		Add:             d.Add,
		Delete:          d.Delete,
		ModifyWithRecreate: func(key string, oldValue, newValue *l3.StaticRoute, metadata interface{}) bool {
			return true
		},
		IsRetriableFailure: d.IsRetriableFailure,
		Dependencies:       d.Dependencies,
		DerivedValues:      d.DerivedValues,
		Dump:               d.Dump,
		DumpDependencies:   []string{ifdescriptor.InterfaceDescriptorName},
	}
}

// EquivalentRoutes is case-insensitive comparison function for l3.StaticRoute.
func (d *RouteDescriptor) EquivalentRoutes(key string, oldRoute, newRoute *l3.StaticRoute) bool {

	if oldRoute.GetType() != newRoute.GetType() ||
		oldRoute.GetVrfId() != newRoute.GetVrfId() ||
		oldRoute.GetViaVrfId() != newRoute.GetViaVrfId() ||
		oldRoute.GetOutgoingInterface() != newRoute.GetOutgoingInterface() ||
		oldRoute.GetWeight() != newRoute.GetWeight() ||
		oldRoute.GetPreference() != newRoute.GetPreference() {
		return false
	}

	// compare dst networks
	if !equalNetworks(oldRoute.DstNetwork, newRoute.DstNetwork) {
		return false
	}

	// compare gw addresses (next hop)
	if !equalAddrs(getGwAddr(oldRoute), getGwAddr(newRoute)) {
		return false
	}

	return true
}

// IsRetriableFailure returns <false> for errors related to invalid configuration.
func (d *RouteDescriptor) IsRetriableFailure(err error) bool {
	return false // nothing retriable
}

// Add adds VPP static route.
func (d *RouteDescriptor) Add(key string, route *l3.StaticRoute) (metadata interface{}, err error) {

	err = d.routeHandler.VppAddRoute(route)
	if err != nil {
		return nil, err
	}

	return nil, nil
}

// Delete removes VPP static route.
func (d *RouteDescriptor) Delete(key string, route *l3.StaticRoute, metadata interface{}) error {

	err := d.routeHandler.VppDelRoute(route)
	if err != nil {
		return err
	}

	return nil
}

// Dependencies lists dependencies for a VPP route.
func (d *RouteDescriptor) Dependencies(key string, route *l3.StaticRoute) []scheduler.Dependency {
	var dependencies []scheduler.Dependency
	// the outgoing interface must exist and be UP
	if route.OutgoingInterface != "" {
		dependencies = append(dependencies, scheduler.Dependency{
			Label: routeOutInterfaceDep,
			Key:   interfaces.InterfaceKey(route.OutgoingInterface),
		})
	}
	// GW must be routable
	/*gwAddr := net.ParseIP(getGwAddr(route))
	if gwAddr != nil && !gwAddr.IsUnspecified() {
		dependencies = append(dependencies, scheduler.Dependency{
			Label: routeGwReachabilityDep,
			AnyOf: func(key string) bool {
				dstAddr, ifName, err := l3.ParseStaticLinkLocalRouteKey(key)
				if err == nil && ifName == route.OutgoingInterface && dstAddr.Contains(gwAddr) {
					// GW address is neighbour as told by another link-local route
					return true
				}
				ifName, addr, err := ifmodel.ParseInterfaceAddressKey(key)
				if err == nil && ifName == route.OutgoingInterface && addr.Contains(gwAddr) {
					// GW address is inside the local network of the outgoing interface
					// as given by the assigned IP address
					return true
				}
				return false
			},
		})
	}*/
	return dependencies
}

// DerivedValues derives empty value under StaticLinkLocalRouteKey if route is link-local.
// It is used in dependencies for network reachability of a route gateway (see above).
func (d *RouteDescriptor) DerivedValues(key string, route *l3.StaticRoute) (derValues []scheduler.KeyValuePair) {
	/*if route.Scope == l3.LinuxStaticRoute_LINK {
		derValues = append(derValues, scheduler.KeyValuePair{
			Key:   l3.StaticLinkLocalRouteKey(route.DstNetwork, route.OutgoingInterface),
			Value: &prototypes.Empty{},
		})
	}*/
	return derValues
}

// Dump returns all routes associated with interfaces managed by this agent.
func (d *RouteDescriptor) Dump(correlate []adapter.StaticRouteKVWithMetadata) (
	dump []adapter.StaticRouteKVWithMetadata, err error) {

	// Retrieve VPP route configuration
	staticRoutes, err := d.routeHandler.DumpStaticRoutes()
	if err != nil {
		return nil, errors.Errorf("failed to dump VPP routes: %v", err)
	}

	for _, staticRoute := range staticRoutes {
		dump = append(dump, adapter.StaticRouteKVWithMetadata{
			Key:    l3.RouteKey(staticRoute.Route.VrfId, staticRoute.Route.DstNetwork, staticRoute.Route.NextHopAddr),
			Value:  staticRoute.Route,
			Origin: scheduler.UnknownOrigin,
		})
	}

	var routes string
	for _, route := range dump {
		routes += fmt.Sprintf(" - %+v\n", route)
	}
	d.log.Debugf("Dumped %d Static Routes:\n%s", len(dump), routes)
	return dump, nil
}

// equalAddrs compares two IP addresses for equality.
func equalAddrs(addr1, addr2 string) bool {
	a1 := net.ParseIP(addr1)
	a2 := net.ParseIP(addr2)
	if a1 == nil || a2 == nil {
		// if parsing fails, compare as strings
		return strings.ToLower(addr1) == strings.ToLower(addr2)
	}
	return a1.Equal(a2)
}

// getGwAddr returns the GW address chosen in the given route, handling the cases
// when it is left undefined.
func getGwAddr(route *l3.StaticRoute) string {
	if route.GetNextHopAddr() != "" {
		return route.GetNextHopAddr()
	}
	// return zero address
	_, dstIPNet, err := net.ParseCIDR(route.GetDstNetwork())
	if err != nil {
		return ""
	}
	if dstIPNet.IP.To4() == nil {
		return net.IPv6zero.String()
	}
	return net.IPv4zero.String()
}

// equalNetworks compares two IP networks for equality.
func equalNetworks(net1, net2 string) bool {
	_, n1, err1 := net.ParseCIDR(net1)
	_, n2, err2 := net.ParseCIDR(net2)
	if err1 != nil || err2 != nil {
		// if parsing fails, compare as strings
		return strings.ToLower(net1) == strings.ToLower(net2)
	}
	return n1.IP.Equal(n2.IP) && bytes.Equal(n1.Mask, n2.Mask)
}
<commit_msg>Default route weight is 1.<commit_after>// Copyright (c) 2018 Cisco and/or its affiliates.
//
// 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 descriptor

import (
	"bytes"
	"fmt"
	"net"
	"strings"

	"github.com/gogo/protobuf/proto"
	"github.com/pkg/errors"

	"github.com/ligato/cn-infra/logging"
	scheduler "github.com/ligato/vpp-agent/plugins/kvscheduler/api"
	ifdescriptor "github.com/ligato/vpp-agent/plugins/vppv2/ifplugin/descriptor"
	"github.com/ligato/vpp-agent/plugins/vppv2/l3plugin/descriptor/adapter"
	"github.com/ligato/vpp-agent/plugins/vppv2/l3plugin/vppcalls"
	"github.com/ligato/vpp-agent/plugins/vppv2/model/interfaces"
	"github.com/ligato/vpp-agent/plugins/vppv2/model/l3"
)

const (
	// StaticRouteDescriptorName is the name of the descriptor for static routes.
	StaticRouteDescriptorName = "vpp-static-route"

	// dependency labels
	routeOutInterfaceDep = "interface-exists"

	// static route weight by default
	defaultWeight = 1
)

// RouteDescriptor teaches KVScheduler how to configure VPP routes.
type RouteDescriptor struct {
	log          logging.Logger
	routeHandler vppcalls.RouteVppAPI
	scheduler    scheduler.KVScheduler
}

// NewRouteDescriptor creates a new instance of the Route descriptor.
func NewRouteDescriptor(scheduler scheduler.KVScheduler,
	routeHandler vppcalls.RouteVppAPI, log logging.PluginLogger) *RouteDescriptor {

	return &RouteDescriptor{
		scheduler:    scheduler,
		routeHandler: routeHandler,
		log:          log.NewLogger("static-route-descriptor"),
	}
}

// GetDescriptor returns descriptor suitable for registration (via adapter) with
// the KVScheduler.
func (d *RouteDescriptor) GetDescriptor() *adapter.StaticRouteDescriptor {
	return &adapter.StaticRouteDescriptor{
		Name: StaticRouteDescriptorName,
		KeySelector: func(key string) bool {
			return strings.HasPrefix(key, l3.RoutePrefix)
		},
		ValueTypeName:   proto.MessageName(&l3.StaticRoute{}),
		ValueComparator: d.EquivalentRoutes,
		NBKeyPrefix:     l3.RoutePrefix,
		Add:             d.Add,
		Delete:          d.Delete,
		ModifyWithRecreate: func(key string, oldValue, newValue *l3.StaticRoute, metadata interface{}) bool {
			return true
		},
		IsRetriableFailure: d.IsRetriableFailure,
		Dependencies:       d.Dependencies,
		DerivedValues:      d.DerivedValues,
		Dump:               d.Dump,
		DumpDependencies:   []string{ifdescriptor.InterfaceDescriptorName},
	}
}

// EquivalentRoutes is case-insensitive comparison function for l3.StaticRoute.
func (d *RouteDescriptor) EquivalentRoutes(key string, oldRoute, newRoute *l3.StaticRoute) bool {

	if oldRoute.GetType() != newRoute.GetType() ||
		oldRoute.GetVrfId() != newRoute.GetVrfId() ||
		oldRoute.GetViaVrfId() != newRoute.GetViaVrfId() ||
		oldRoute.GetOutgoingInterface() != newRoute.GetOutgoingInterface() ||
		getWeight(oldRoute) != getWeight(newRoute) ||
		oldRoute.GetPreference() != newRoute.GetPreference() {
		return false
	}

	// compare dst networks
	if !equalNetworks(oldRoute.DstNetwork, newRoute.DstNetwork) {
		return false
	}

	// compare gw addresses (next hop)
	if !equalAddrs(getGwAddr(oldRoute), getGwAddr(newRoute)) {
		return false
	}

	return true
}

// IsRetriableFailure returns <false> for errors related to invalid configuration.
func (d *RouteDescriptor) IsRetriableFailure(err error) bool {
	return false // nothing retriable
}

// Add adds VPP static route.
func (d *RouteDescriptor) Add(key string, route *l3.StaticRoute) (metadata interface{}, err error) {

	err = d.routeHandler.VppAddRoute(route)
	if err != nil {
		return nil, err
	}

	return nil, nil
}

// Delete removes VPP static route.
func (d *RouteDescriptor) Delete(key string, route *l3.StaticRoute, metadata interface{}) error {

	err := d.routeHandler.VppDelRoute(route)
	if err != nil {
		return err
	}

	return nil
}

// Dependencies lists dependencies for a VPP route.
func (d *RouteDescriptor) Dependencies(key string, route *l3.StaticRoute) []scheduler.Dependency {
	var dependencies []scheduler.Dependency
	// the outgoing interface must exist and be UP
	if route.OutgoingInterface != "" {
		dependencies = append(dependencies, scheduler.Dependency{
			Label: routeOutInterfaceDep,
			Key:   interfaces.InterfaceKey(route.OutgoingInterface),
		})
	}
	// GW must be routable
	/*gwAddr := net.ParseIP(getGwAddr(route))
	if gwAddr != nil && !gwAddr.IsUnspecified() {
		dependencies = append(dependencies, scheduler.Dependency{
			Label: routeGwReachabilityDep,
			AnyOf: func(key string) bool {
				dstAddr, ifName, err := l3.ParseStaticLinkLocalRouteKey(key)
				if err == nil && ifName == route.OutgoingInterface && dstAddr.Contains(gwAddr) {
					// GW address is neighbour as told by another link-local route
					return true
				}
				ifName, addr, err := ifmodel.ParseInterfaceAddressKey(key)
				if err == nil && ifName == route.OutgoingInterface && addr.Contains(gwAddr) {
					// GW address is inside the local network of the outgoing interface
					// as given by the assigned IP address
					return true
				}
				return false
			},
		})
	}*/
	return dependencies
}

// DerivedValues derives empty value under StaticLinkLocalRouteKey if route is link-local.
// It is used in dependencies for network reachability of a route gateway (see above).
func (d *RouteDescriptor) DerivedValues(key string, route *l3.StaticRoute) (derValues []scheduler.KeyValuePair) {
	/*if route.Scope == l3.LinuxStaticRoute_LINK {
		derValues = append(derValues, scheduler.KeyValuePair{
			Key:   l3.StaticLinkLocalRouteKey(route.DstNetwork, route.OutgoingInterface),
			Value: &prototypes.Empty{},
		})
	}*/
	return derValues
}

// Dump returns all routes associated with interfaces managed by this agent.
func (d *RouteDescriptor) Dump(correlate []adapter.StaticRouteKVWithMetadata) (
	dump []adapter.StaticRouteKVWithMetadata, err error) {

	// Retrieve VPP route configuration
	staticRoutes, err := d.routeHandler.DumpStaticRoutes()
	if err != nil {
		return nil, errors.Errorf("failed to dump VPP routes: %v", err)
	}

	for _, staticRoute := range staticRoutes {
		dump = append(dump, adapter.StaticRouteKVWithMetadata{
			Key:    l3.RouteKey(staticRoute.Route.VrfId, staticRoute.Route.DstNetwork, staticRoute.Route.NextHopAddr),
			Value:  staticRoute.Route,
			Origin: scheduler.UnknownOrigin,
		})
	}

	var routes string
	for _, route := range dump {
		routes += fmt.Sprintf(" - %+v\n", route)
	}
	d.log.Debugf("Dumped %d Static Routes:\n%s", len(dump), routes)
	return dump, nil
}

// equalAddrs compares two IP addresses for equality.
func equalAddrs(addr1, addr2 string) bool {
	a1 := net.ParseIP(addr1)
	a2 := net.ParseIP(addr2)
	if a1 == nil || a2 == nil {
		// if parsing fails, compare as strings
		return strings.ToLower(addr1) == strings.ToLower(addr2)
	}
	return a1.Equal(a2)
}

// getGwAddr returns the GW address chosen in the given route, handling the cases
// when it is left undefined.
func getGwAddr(route *l3.StaticRoute) string {
	if route.GetNextHopAddr() != "" {
		return route.GetNextHopAddr()
	}
	// return zero address
	_, dstIPNet, err := net.ParseCIDR(route.GetDstNetwork())
	if err != nil {
		return ""
	}
	if dstIPNet.IP.To4() == nil {
		return net.IPv6zero.String()
	}
	return net.IPv4zero.String()
}

// getWeight returns static route weight, handling the cases when it is left undefined.
func getWeight(route *l3.StaticRoute) uint32 {
	if route.Weight == 0 {
		return defaultWeight
	}
	return route.Weight
}

// equalNetworks compares two IP networks for equality.
func equalNetworks(net1, net2 string) bool {
	_, n1, err1 := net.ParseCIDR(net1)
	_, n2, err2 := net.ParseCIDR(net2)
	if err1 != nil || err2 != nil {
		// if parsing fails, compare as strings
		return strings.ToLower(net1) == strings.ToLower(net2)
	}
	return n1.IP.Equal(n2.IP) && bytes.Equal(n1.Mask, n2.Mask)
}
<|endoftext|>


<commit_before>package main

import (
	// Stdlib
	"fmt"
	"os"

	// Internal
	"github.com/salsaflow/salsaflow/errs"
	"github.com/salsaflow/salsaflow/git"
	"github.com/salsaflow/salsaflow/hooks"

	// Vendor
	"github.com/fatih/color"
)

func main() {
	// Register the magical -salsaflow.version flag.
	hooks.IdentifyYourself()

	// Run the hook logic itself.
	if err := hook(); err != nil {
		errs.Fatal(err)
	}
}

func hook() error {
	// There are always 3 arguments passed to this hook.
	prevRef, newRef, flag := os.Args[1], os.Args[2], os.Args[3]

	// Return in case prevRef is the zero hash since that means
	// that this hook is being run right after 'git clone'.
	if prevRef == git.ZeroHash {
		return nil
	}

	// Return in case flag is '0'. That signals retrieving a file from the index.
	if flag == "0" {
		return nil
	}

	// Return unless the new HEAD is a core branch.
	isCore, err := isCoreBranchHash(newRef)
	if err != nil {
		return err
	}
	if !isCore {
		return nil
	}

	// Get the relevant commits.
	// These are the commits specified by newRef..prevRef, e.g. trunk..story/foobar.
	commits, err := git.ShowCommitRange(fmt.Sprintf("%v..%v", newRef, prevRef))
	if err != nil {
		return err
	}

	// Collect the commits with missing Story-Id tag.
	missing := make([]*git.Commit, 0, len(commits))
	for _, commit := range commits {
		if commit.StoryIdTag == "" {
			missing = append(missing, commit)
		}
	}
	if len(missing) == 0 {
		return nil
	}

	// Print the warning.
	printWarning(missing)
	return nil
}

func isCoreBranchHash(hash string) (bool, error) {
	hashes, err := git.CoreBranchHashes()
	if err != nil {
		return false, err
	}
	for _, h := range hashes {
		if h == hash {
			return true, nil
		}
	}
	return false, nil
}

func printWarning(commits []*git.Commit) {
	// Let's be colorful!
	redBold := color.New(color.FgRed).Add(color.Bold)
	redBold.Println("\nWarning: There are some commits missing the Story-Id tag.")

	red := color.New(color.FgRed)
	red.Println("Make sure this is really what you want before proceeding further.\n")

	yellow := color.New(color.FgYellow).SprintFunc()
	for _, commit := range commits {
		fmt.Printf("  %v %v\n", yellow(commit.SHA), commit.MessageTitle)
	}
	fmt.Println()
}
<commit_msg>hooks/post-checkout: Skip check for merge commits<commit_after>package main

import (
	// Stdlib
	"fmt"
	"os"

	// Internal
	"github.com/salsaflow/salsaflow/errs"
	"github.com/salsaflow/salsaflow/git"
	"github.com/salsaflow/salsaflow/hooks"

	// Vendor
	"github.com/fatih/color"
)

func main() {
	// Register the magical -salsaflow.version flag.
	hooks.IdentifyYourself()

	// Run the hook logic itself.
	if err := hook(); err != nil {
		errs.Fatal(err)
	}
}

func hook() error {
	// There are always 3 arguments passed to this hook.
	prevRef, newRef, flag := os.Args[1], os.Args[2], os.Args[3]

	// Return in case prevRef is the zero hash since that means
	// that this hook is being run right after 'git clone'.
	if prevRef == git.ZeroHash {
		return nil
	}

	// Return in case flag is '0'. That signals retrieving a file from the index.
	if flag == "0" {
		return nil
	}

	// Return unless the new HEAD is a core branch.
	isCore, err := isCoreBranchHash(newRef)
	if err != nil {
		return err
	}
	if !isCore {
		return nil
	}

	// Get the relevant commits.
	// These are the commits specified by newRef..prevRef, e.g. trunk..story/foobar.
	commits, err := git.ShowCommitRange(fmt.Sprintf("%v..%v", newRef, prevRef))
	if err != nil {
		return err
	}

	// Collect the commits with missing Story-Id tag.
	missing := make([]*git.Commit, 0, len(commits))
	for _, commit := range commits {
		// Skip merge commits.
		if commit.Merge != "" {
			continue
		}

		// Add the commit in case Story-Id tag is not set.
		if commit.StoryIdTag == "" {
			missing = append(missing, commit)
		}
	}
	if len(missing) == 0 {
		return nil
	}

	// Print the warning.
	printWarning(missing)
	return nil
}

func isCoreBranchHash(hash string) (bool, error) {
	hashes, err := git.CoreBranchHashes()
	if err != nil {
		return false, err
	}
	for _, h := range hashes {
		if h == hash {
			return true, nil
		}
	}
	return false, nil
}

func printWarning(commits []*git.Commit) {
	// Let's be colorful!
	redBold := color.New(color.FgRed).Add(color.Bold)
	redBold.Println("\nWarning: There are some commits missing the Story-Id tag.")

	red := color.New(color.FgRed)
	red.Println("Make sure this is really what you want before proceeding further.\n")

	yellow := color.New(color.FgYellow).SprintFunc()
	for _, commit := range commits {
		fmt.Printf("  %v %v\n", yellow(commit.SHA), commit.MessageTitle)
	}
	fmt.Println()
}
<|endoftext|>


<commit_before>package proton

import (
	"bytes"
	"errors"
	"fmt"
	"hash/fnv"
	"log"
	"math"
	"time"

	"golang.org/x/net/context"

	"github.com/coreos/etcd/raft"
	"github.com/coreos/etcd/raft/raftpb"
)

var (
	ErrConnectionRefused = errors.New("Connection refused to the node")
	ErrConfChangeRefused = errors.New("Can't add node to the cluster")
)

type Node struct {
	Client  ProtonClient
	Cluster *Cluster
	Ctx     context.Context

	ID     uint64
	Addr   string
	Port   int
	Status Status
	Error  error

	PStore map[string]string
	Store  *raft.MemoryStorage
	Cfg    *raft.Config
	Raft   raft.Node
	ticker <-chan time.Time
	done   <-chan struct{}
}

type Status int

const (
	UP Status = iota
	DOWN
	PENDING
)

const hb = 1

func NewNode(id uint64, addr string) *Node {
	store := raft.NewMemoryStorage()
	peers := []raft.Peer{{ID: id}}

	n := &Node{
		ID:      id,
		Ctx:     context.TODO(),
		Cluster: NewCluster(),
		Store:   store,
		Addr:    addr,
		Cfg: &raft.Config{
			ID:              id,
			ElectionTick:    5 * hb,
			HeartbeatTick:   hb,
			Storage:         store,
			MaxSizePerMsg:   math.MaxUint16,
			MaxInflightMsgs: 256,
		},
		PStore: make(map[string]string),
		ticker: time.Tick(time.Second),
		done:   make(chan struct{}),
	}

	n.Cluster.AddNodes(
		&Node{
			ID:   id,
			Addr: addr,
		},
	)

	n.Raft = raft.StartNode(n.Cfg, peers)
	return n
}

func GenID(hostname string) uint64 {
	h := fnv.New64a()
	h.Write([]byte(hostname))
	return h.Sum64()
}

func (n *Node) Start() {
	for {
		select {
		case <-n.ticker:
			n.Raft.Tick()
		case rd := <-n.Raft.Ready():
			n.saveToStorage(rd.HardState, rd.Entries, rd.Snapshot)
			n.send(rd.Messages)
			if !raft.IsEmptySnap(rd.Snapshot) {
				n.processSnapshot(rd.Snapshot)
			}
			for _, entry := range rd.CommittedEntries {
				n.process(entry)
				if entry.Type == raftpb.EntryConfChange {
					var cc raftpb.ConfChange
					cc.Unmarshal(entry.Data)
					n.Raft.ApplyConfChange(cc)
				}
			}
			n.Raft.Advance()
		case <-n.done:
			return
		}
	}
}

func (n *Node) JoinCluster(ctx context.Context, info *NodeInfo) (*JoinClusterResponse, error) {
	err := n.RegisterNode(info)
	if err != nil {
		return &JoinClusterResponse{
			Success: false,
			Error:   err.Error(),
		}, nil
	}

	nodes := []*NodeInfo{}
	for _, node := range n.Cluster.Nodes {
		nodes = append(nodes, &NodeInfo{
			ID:   node.ID,
			Addr: node.Addr,
		})
	}

	return &JoinClusterResponse{
		Success: true,
		Error:   "",
		Info:    nodes,
	}, nil
}

func (n *Node) JoinRaft(ctx context.Context, info *NodeInfo) (*JoinRaftResponse, error) {
	confChange := raftpb.ConfChange{
		ID:      info.ID,
		Type:    raftpb.ConfChangeAddNode,
		NodeID:  info.ID,
		Context: []byte(""),
	}

	err := n.Raft.ProposeConfChange(n.Ctx, confChange)
	if err != nil {
		return &JoinRaftResponse{
			Success: false,
			Error:   ErrConfChangeRefused.Error(),
		}, nil
	}

	return &JoinRaftResponse{
		Success: true,
		Error:   "",
	}, nil
}

func (n *Node) Send(ctx context.Context, message *raftpb.Message) (*Acknowledgment, error) {
	n.Raft.Step(n.Ctx, *message)

	return &Acknowledgment{}, nil
}

func (n *Node) saveToStorage(hardState raftpb.HardState, entries []raftpb.Entry, snapshot raftpb.Snapshot) {
	n.Store.Append(entries)

	if !raft.IsEmptyHardState(hardState) {
		n.Store.SetHardState(hardState)
	}

	if !raft.IsEmptySnap(snapshot) {
		n.Store.ApplySnapshot(snapshot)
	}
}

func (n *Node) send(messages []raftpb.Message) {
	for key, _ := range n.Cluster.Nodes {
		fmt.Println("node value in cluster list: ", key)
	}

	for _, m := range messages {
		// Process locally
		if m.To == n.ID {
			n.Raft.Step(n.Ctx, m)
		}

		log.Println(raft.DescribeMessage(m, nil))

		n.Cluster.Nodes[m.To].Client.Send(n.Ctx, &m)
	}
}

func (n *Node) processSnapshot(snapshot raftpb.Snapshot) {
	panic(fmt.Sprintf("Applying snapshot on node %v is not implemented", n.ID))
}

func (n *Node) process(entry raftpb.Entry) {
	log.Printf("node %v: processing entry: %v\n", n.ID, entry)
	if entry.Type == raftpb.EntryNormal && entry.Data != nil {
		parts := bytes.SplitN(entry.Data, []byte(":"), 2)
		n.PStore[string(parts[0])] = string(parts[1])
	}
}

func (n *Node) receive(ctx context.Context, message raftpb.Message) {
	n.Raft.Step(ctx, message)
}

// Register a new node on the cluster
func (n *Node) RegisterNode(node *NodeInfo) error {
	var (
		client ProtonClient
		err    error
	)

	for i := 1; i <= MaxRetryTime; i++ {
		client, err = GetProtonClient(node.Addr)
		if err != nil {
			if i == MaxRetryTime {
				return ErrConnectionRefused
			}
		}
	}

	n.Cluster.AddNodes(
		&Node{
			ID:     node.ID,
			Addr:   node.Addr,
			Client: client,
			Error:  err,
		},
	)

	return nil
}
<commit_msg>remove unused receive function in node.go<commit_after>package proton

import (
	"bytes"
	"errors"
	"fmt"
	"hash/fnv"
	"log"
	"math"
	"time"

	"golang.org/x/net/context"

	"github.com/coreos/etcd/raft"
	"github.com/coreos/etcd/raft/raftpb"
)

var (
	ErrConnectionRefused = errors.New("Connection refused to the node")
	ErrConfChangeRefused = errors.New("Can't add node to the cluster")
)

type Node struct {
	Client  ProtonClient
	Cluster *Cluster
	Ctx     context.Context

	ID     uint64
	Addr   string
	Port   int
	Status Status
	Error  error

	PStore map[string]string
	Store  *raft.MemoryStorage
	Cfg    *raft.Config
	Raft   raft.Node
	ticker <-chan time.Time
	done   <-chan struct{}
}

type Status int

const (
	UP Status = iota
	DOWN
	PENDING
)

const hb = 1

func NewNode(id uint64, addr string) *Node {
	store := raft.NewMemoryStorage()
	peers := []raft.Peer{{ID: id}}

	n := &Node{
		ID:      id,
		Ctx:     context.TODO(),
		Cluster: NewCluster(),
		Store:   store,
		Addr:    addr,
		Cfg: &raft.Config{
			ID:              id,
			ElectionTick:    5 * hb,
			HeartbeatTick:   hb,
			Storage:         store,
			MaxSizePerMsg:   math.MaxUint16,
			MaxInflightMsgs: 256,
		},
		PStore: make(map[string]string),
		ticker: time.Tick(time.Second),
		done:   make(chan struct{}),
	}

	n.Cluster.AddNodes(
		&Node{
			ID:   id,
			Addr: addr,
		},
	)

	n.Raft = raft.StartNode(n.Cfg, peers)
	return n
}

func GenID(hostname string) uint64 {
	h := fnv.New64a()
	h.Write([]byte(hostname))
	return h.Sum64()
}

func (n *Node) Start() {
	for {
		select {
		case <-n.ticker:
			n.Raft.Tick()
		case rd := <-n.Raft.Ready():
			n.saveToStorage(rd.HardState, rd.Entries, rd.Snapshot)
			n.send(rd.Messages)
			if !raft.IsEmptySnap(rd.Snapshot) {
				n.processSnapshot(rd.Snapshot)
			}
			for _, entry := range rd.CommittedEntries {
				n.process(entry)
				if entry.Type == raftpb.EntryConfChange {
					var cc raftpb.ConfChange
					cc.Unmarshal(entry.Data)
					n.Raft.ApplyConfChange(cc)
				}
			}
			n.Raft.Advance()
		case <-n.done:
			return
		}
	}
}

func (n *Node) JoinCluster(ctx context.Context, info *NodeInfo) (*JoinClusterResponse, error) {
	err := n.RegisterNode(info)
	if err != nil {
		return &JoinClusterResponse{
			Success: false,
			Error:   err.Error(),
		}, nil
	}

	nodes := []*NodeInfo{}
	for _, node := range n.Cluster.Nodes {
		nodes = append(nodes, &NodeInfo{
			ID:   node.ID,
			Addr: node.Addr,
		})
	}

	return &JoinClusterResponse{
		Success: true,
		Error:   "",
		Info:    nodes,
	}, nil
}

func (n *Node) JoinRaft(ctx context.Context, info *NodeInfo) (*JoinRaftResponse, error) {
	confChange := raftpb.ConfChange{
		ID:      info.ID,
		Type:    raftpb.ConfChangeAddNode,
		NodeID:  info.ID,
		Context: []byte(""),
	}

	err := n.Raft.ProposeConfChange(n.Ctx, confChange)
	if err != nil {
		return &JoinRaftResponse{
			Success: false,
			Error:   ErrConfChangeRefused.Error(),
		}, nil
	}

	return &JoinRaftResponse{
		Success: true,
		Error:   "",
	}, nil
}

func (n *Node) Send(ctx context.Context, message *raftpb.Message) (*Acknowledgment, error) {
	n.Raft.Step(n.Ctx, *message)

	return &Acknowledgment{}, nil
}

func (n *Node) saveToStorage(hardState raftpb.HardState, entries []raftpb.Entry, snapshot raftpb.Snapshot) {
	n.Store.Append(entries)

	if !raft.IsEmptyHardState(hardState) {
		n.Store.SetHardState(hardState)
	}

	if !raft.IsEmptySnap(snapshot) {
		n.Store.ApplySnapshot(snapshot)
	}
}

func (n *Node) send(messages []raftpb.Message) {
	for key, _ := range n.Cluster.Nodes {
		fmt.Println("node value in cluster list: ", key)
	}

	for _, m := range messages {
		// Process locally
		if m.To == n.ID {
			n.Raft.Step(n.Ctx, m)
		}

		log.Println(raft.DescribeMessage(m, nil))

		n.Cluster.Nodes[m.To].Client.Send(n.Ctx, &m)
	}
}

func (n *Node) processSnapshot(snapshot raftpb.Snapshot) {
	panic(fmt.Sprintf("Applying snapshot on node %v is not implemented", n.ID))
}

func (n *Node) process(entry raftpb.Entry) {
	log.Printf("node %v: processing entry: %v\n", n.ID, entry)
	if entry.Type == raftpb.EntryNormal && entry.Data != nil {
		parts := bytes.SplitN(entry.Data, []byte(":"), 2)
		n.PStore[string(parts[0])] = string(parts[1])
	}
}

// Register a new node on the cluster
func (n *Node) RegisterNode(node *NodeInfo) error {
	var (
		client ProtonClient
		err    error
	)

	for i := 1; i <= MaxRetryTime; i++ {
		client, err = GetProtonClient(node.Addr)
		if err != nil {
			if i == MaxRetryTime {
				return ErrConnectionRefused
			}
		}
	}

	n.Cluster.AddNodes(
		&Node{
			ID:     node.ID,
			Addr:   node.Addr,
			Client: client,
			Error:  err,
		},
	)

	return nil
}
<|endoftext|>