<commit_before>package web

import (
	"fmt"
	"net/http"

	log "github.com/Sirupsen/logrus"
	"github.com/hellofresh/janus/pkg/api"
	"github.com/hellofresh/janus/pkg/config"
	"github.com/hellofresh/janus/pkg/jwt"
	"github.com/hellofresh/janus/pkg/middleware"
	"github.com/hellofresh/janus/pkg/notifier"
	"github.com/hellofresh/janus/pkg/oauth"
	"github.com/hellofresh/janus/pkg/router"
	chimiddleware "github.com/pressly/chi/middleware"
	"github.com/rs/cors"
)

// Provider is a provider.Provider implementation that provides the REST API.
type Provider struct {
	Port     int    `description:"Web administration port"`
	CertFile string `description:"SSL certificate"`
	KeyFile  string `description:"SSL certificate"`
	ReadOnly bool   `description:"Enable read only API"`
	Cred     config.Credentials
	APIRepo  api.Repository
	AuthRepo oauth.Repository
	Notifier notifier.Notifier
}

// Provide executes the provider functionality
// This is normally the entry point of the
// provider.
func (p *Provider) Provide(version string) error {
	r := router.NewChiRouter()

	// create authentication for Janus
	authConfig := jwt.NewConfig(p.Cred)
	authMiddleware := jwt.NewMiddleware(authConfig)
	r.Use(
		chimiddleware.StripSlashes,
		chimiddleware.DefaultCompress,
		middleware.NewLogger().Handler,
		middleware.NewRecovery(RecoveryHandler).Handler,
		middleware.NewOpenTracing(p.IsHTTPS()).Handler,
		cors.New(cors.Options{
			AllowedOrigins:   []string{"*"},
			AllowedHeaders:   []string{"*"},
			AllowedMethods:   []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
			AllowCredentials: true,
		}).Handler,
	)

	// create endpoints
	r.GET("/", Home(version))
	r.GET("/status", Heartbeat(p.APIRepo))

	handlers := jwt.Handler{Config: authConfig}
	r.POST("/login", handlers.Login())
	authGroup := r.Group("/auth")
	{
		authGroup.GET("/refresh_token", handlers.Refresh())
	}

	p.loadAPIEndpoints(r, authMiddleware.Handler)
	p.loadOAuthEndpoints(r)

	go func() {
		log.Fatal(p.listenAndServe(r))
	}()
	return nil
}

func (p *Provider) listenAndServe(handler http.Handler) error {
	address := fmt.Sprintf(":%v", p.Port)
	log.WithField("address", address).Info("Listening on")
	log.Info("Janus Admin API started")
	if p.IsHTTPS() {
		return http.ListenAndServeTLS(address, p.CertFile, p.KeyFile, handler)
	}

	log.Info("Certificate and certificate key were not found, defaulting to HTTP")
	return http.ListenAndServe(address, handler)
}

//loadAPIEndpoints register api endpoints
func (p *Provider) loadAPIEndpoints(router router.Router, handlers ...router.Constructor) {
	log.Debug("Loading API Endpoints")

	// Apis endpoints
	handler := api.NewController(p.APIRepo, p.Notifier)
	group := router.Group("/apis")
	{
		group.GET("/", handler.Get())
		group.GET("/:name", handler.GetBy())

		if false == p.ReadOnly {
			group.POST("/", handler.Post())
			group.PUT("/:name", handler.PutBy())
			group.DELETE("/:name", handler.DeleteBy())
		}
	}
}

//loadOAuthEndpoints register api endpoints
func (p *Provider) loadOAuthEndpoints(router router.Router, handlers ...router.Constructor) {
	log.Debug("Loading OAuth Endpoints")

	// Oauth servers endpoints
	oAuthHandler := oauth.NewController(p.AuthRepo, p.Notifier)
	oauthGroup := router.Group("/oauth/servers")
	{
		oauthGroup.GET("/", oAuthHandler.Get(), handlers...)
		oauthGroup.GET("/:name", oAuthHandler.GetBy(), handlers...)

		if false == p.ReadOnly {
			oauthGroup.POST("/", oAuthHandler.Post(), handlers...)
			oauthGroup.PUT("/:name", oAuthHandler.PutBy(), handlers...)
			oauthGroup.DELETE("/:name", oAuthHandler.DeleteBy(), handlers...)
		}
	}
}

// IsHTTPS checks if you have https enabled
func (p *Provider) IsHTTPS() bool {
	return len(p.CertFile) > 0 && len(p.KeyFile) > 0
}
<commit_msg>enabled auth again<commit_after>package web

import (
	"fmt"
	"net/http"

	log "github.com/Sirupsen/logrus"
	"github.com/hellofresh/janus/pkg/api"
	"github.com/hellofresh/janus/pkg/config"
	"github.com/hellofresh/janus/pkg/jwt"
	"github.com/hellofresh/janus/pkg/middleware"
	"github.com/hellofresh/janus/pkg/notifier"
	"github.com/hellofresh/janus/pkg/oauth"
	"github.com/hellofresh/janus/pkg/router"
	chimiddleware "github.com/pressly/chi/middleware"
	"github.com/rs/cors"
)

// Provider is a provider.Provider implementation that provides the REST API.
type Provider struct {
	Port     int    `description:"Web administration port"`
	CertFile string `description:"SSL certificate"`
	KeyFile  string `description:"SSL certificate"`
	ReadOnly bool   `description:"Enable read only API"`
	Cred     config.Credentials
	APIRepo  api.Repository
	AuthRepo oauth.Repository
	Notifier notifier.Notifier
}

// Provide executes the provider functionality
// This is normally the entry point of the
// provider.
func (p *Provider) Provide(version string) error {
	r := router.NewChiRouter()

	// create authentication for Janus
	authConfig := jwt.NewConfig(p.Cred)
	authMiddleware := jwt.NewMiddleware(authConfig)
	r.Use(
		chimiddleware.StripSlashes,
		chimiddleware.DefaultCompress,
		middleware.NewLogger().Handler,
		middleware.NewRecovery(RecoveryHandler).Handler,
		middleware.NewOpenTracing(p.IsHTTPS()).Handler,
		cors.New(cors.Options{
			AllowedOrigins:   []string{"*"},
			AllowedHeaders:   []string{"*"},
			AllowedMethods:   []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
			AllowCredentials: true,
		}).Handler,
	)

	// create endpoints
	r.GET("/", Home(version))
	r.GET("/status", Heartbeat(p.APIRepo))

	handlers := jwt.Handler{Config: authConfig}
	r.POST("/login", handlers.Login())
	authGroup := r.Group("/auth")
	{
		authGroup.GET("/refresh_token", handlers.Refresh())
	}

	p.loadAPIEndpoints(r, authMiddleware.Handler)
	p.loadOAuthEndpoints(r, authMiddleware.Handler)

	go func() {
		log.Fatal(p.listenAndServe(r))
	}()
	return nil
}

func (p *Provider) listenAndServe(handler http.Handler) error {
	address := fmt.Sprintf(":%v", p.Port)
	log.WithField("address", address).Info("Listening on")
	log.Info("Janus Admin API started")
	if p.IsHTTPS() {
		return http.ListenAndServeTLS(address, p.CertFile, p.KeyFile, handler)
	}

	log.Info("Certificate and certificate key were not found, defaulting to HTTP")
	return http.ListenAndServe(address, handler)
}

//loadAPIEndpoints register api endpoints
func (p *Provider) loadAPIEndpoints(router router.Router, handlers ...router.Constructor) {
	log.Debug("Loading API Endpoints")

	// Apis endpoints
	handler := api.NewController(p.APIRepo, p.Notifier)
	group := router.Group("/apis")
	group.Use(handlers...)
	{
		group.GET("/", handler.Get())
		group.GET("/:name", handler.GetBy())

		if false == p.ReadOnly {
			group.POST("/", handler.Post())
			group.PUT("/:name", handler.PutBy())
			group.DELETE("/:name", handler.DeleteBy())
		}
	}
}

//loadOAuthEndpoints register api endpoints
func (p *Provider) loadOAuthEndpoints(router router.Router, handlers ...router.Constructor) {
	log.Debug("Loading OAuth Endpoints")

	// Oauth servers endpoints
	oAuthHandler := oauth.NewController(p.AuthRepo, p.Notifier)
	oauthGroup := router.Group("/oauth/servers")
	{
		oauthGroup.GET("/", oAuthHandler.Get(), handlers...)
		oauthGroup.GET("/:name", oAuthHandler.GetBy(), handlers...)

		if false == p.ReadOnly {
			oauthGroup.POST("/", oAuthHandler.Post(), handlers...)
			oauthGroup.PUT("/:name", oAuthHandler.PutBy(), handlers...)
			oauthGroup.DELETE("/:name", oAuthHandler.DeleteBy(), handlers...)
		}
	}
}

// IsHTTPS checks if you have https enabled
func (p *Provider) IsHTTPS() bool {
	return len(p.CertFile) > 0 && len(p.KeyFile) > 0
}
<|endoftext|>


<commit_before>package goka

import (
	"errors"
	"fmt"
	"sync"

	"github.com/lovoo/goka/kafka"
	"github.com/lovoo/goka/logger"
	"github.com/lovoo/goka/multierr"
	"github.com/lovoo/goka/storage"
)

// Getter functions return a value for a key or an error. If no value exists for the key, nil is returned without errors.
type Getter func(string) (interface{}, error)

// View is a materialized (i.e. persistent) cache of a group table.
type View struct {
	brokers    []string
	topic      string
	opts       *voptions
	partitions []*partition
	consumer   kafka.Consumer
	done       chan bool
	dead       chan bool

	errors     multierr.Errors
	stopOnce   sync.Once
	mInit      sync.Mutex
	terminated bool
}

// NewView creates a new View object from a group.
func NewView(brokers []string, topic Table, codec Codec, options ...ViewOption) (*View, error) {
	options = append(
		// default options comes first
		[]ViewOption{
			WithViewLogger(logger.Default()),
			WithViewCallback(DefaultUpdate),
			WithViewPartitionChannelSize(defaultPartitionChannelSize),
			WithViewStorageBuilder(storage.DefaultBuilder(DefaultViewStoragePath())),
		},

		// then the user passed options
		options...,
	)

	// figure out how many partitions the group has
	opts := new(voptions)
	err := opts.applyOptions(topic, options...)
	if err != nil {
		return nil, fmt.Errorf("Error applying user-defined options: %v", err)
	}

	opts.tableCodec = codec

	v := &View{
		brokers: brokers,
		topic:   string(topic),
		opts:    opts,
	}

	if err = v.createPartitions(brokers); err != nil {
		return nil, err
	}

	return v, err
}

func (v *View) createPartitions(brokers []string) (err error) {
	tm, err := v.opts.builders.topicmgr(brokers)
	if err != nil {
		return fmt.Errorf("Error creating topic manager: %v", err)
	}
	defer func() {
		e := tm.Close()
		if e != nil && err == nil {
			err = fmt.Errorf("Error closing topic manager: %v", e)
		}
	}()

	partitions, err := tm.Partitions(v.topic)
	if err != nil {
		return fmt.Errorf("Error getting partitions for topic %s: %v", v.topic, err)
	}

	// check assumption that partitions are gap-less
	for i, p := range partitions {
		if i != int(p) {
			return fmt.Errorf("Partition numbers are not sequential for topic %s", v.topic)
		}
	}

	v.opts.log.Printf("Table %s has %d partitions", v.topic, len(partitions))
	for _, p := range partitions {
		st, err := v.opts.builders.storage(v.topic, p)
		if err != nil {
			// TODO(diogo): gracefully terminate all partitions
			return fmt.Errorf("Error creating local storage for partition %d: %v", p, err)
		}

		po := newPartition(v.opts.log, v.topic, nil,
			&storageProxy{Storage: st, partition: p, update: v.opts.updateCallback},
			&proxy{p, nil},
			v.opts.partitionChannelSize,
		)
		v.partitions = append(v.partitions, po)
	}

	return nil
}

// reinit (re)initializes the view and its partitions to connect to Kafka
func (v *View) reinit() error {
	v.mInit.Lock()
	defer v.mInit.Unlock()
	if v.terminated {
		return fmt.Errorf("view: cannot reinitialize terminated view")
	}

	consumer, err := v.opts.builders.consumer(v.brokers, "goka-view", v.opts.clientID)
	if err != nil {
		return fmt.Errorf("view: cannot create Kafka consumer: %v", err)
	}
	v.consumer = consumer
	v.done = make(chan bool)
	v.dead = make(chan bool)
	v.errors = multierr.Errors{}

	for i, p := range v.partitions {
		p.reinit(&proxy{int32(i), v.consumer})
	}
	return nil
}

// Start starts consuming the view's topic.
func (v *View) Start() error {
	if err := v.reinit(); err != nil {
		return err
	}
	go v.run()

	var wg sync.WaitGroup
	wg.Add(len(v.partitions))
	for id, p := range v.partitions {
		go func(id int32, p *partition) {
			defer wg.Done()
			if err := p.st.Open(); err != nil {
				v.fail(fmt.Errorf("view: error opening storage partition %d: %v", id, err))
			}
			if err := p.startCatchup(); err != nil {
				v.fail(fmt.Errorf("view: error opening partition %d: %v", id, err))
			}
		}(int32(id), p)
	}
	wg.Wait()

	<-v.dead

	if v.opts.restartable {
		// At this point, stop() has finished and goroutines exited,
		// except for start and stop goroutines. Prepare the once object for a
		// subsequent start().
		defer func() { v.stopOnce = sync.Once{} }()
	}

	if v.errors.HasErrors() {
		return &v.errors
	}
	return nil
}

func (v *View) fail(err error) {
	v.opts.log.Printf("View: failing: %v", err)
	v.errors.Collect(err)
	go v.stop()
}

func (v *View) stop() {
	v.stopOnce.Do(func() {
		defer close(v.dead)
		// stop consumer
		if err := v.consumer.Close(); err != nil {
			err = fmt.Errorf("failed to close consumer on stopping the view: %v", err)
			v.opts.log.Printf("error: %v", err)
			v.errors.Collect(err)
		}
		<-v.done
		v.opts.log.Printf("View: stopping partitions")

		var wg sync.WaitGroup
		for i, par := range v.partitions {
			wg.Add(1)
			go func(pid int, p *partition) {
				p.stop()
				wg.Done()
				v.opts.log.Printf("View: partition %d stopped", pid)
			}(i, par)
		}
		wg.Wait()

		if !v.opts.restartable {
			v.close()
		}
		v.opts.log.Printf("View: shutdown complete")
	})
}

// close closes all storage partitions
func (v *View) close() {
	for _, p := range v.partitions {
		v.errors.Collect(p.st.Close())
	}
}

// Stop stops the view, closes storage partitions, and frees resources
func (v *View) Stop() {
	v.opts.log.Printf("View: stopping")

	// do not allow any reinitialization
	v.mInit.Lock()
	if v.terminated {
		return
		v.mInit.Unlock()
	}
	v.terminated = true
	v.mInit.Unlock()

	v.stop()

	if v.opts.restartable {
		v.close()
	}
}

func (v *View) hash(key string) (int32, error) {
	// create a new hasher every time. Alternative would be to store the hash in
	// view and every time reset the hasher (ie, hasher.Reset()). But that would
	// also require us to protect the access of the hasher with a mutex.
	hasher := v.opts.hasher()

	_, err := hasher.Write([]byte(key))
	if err != nil {
		return -1, err
	}
	hash := int32(hasher.Sum32())
	if hash < 0 {
		hash = -hash
	}
	if len(v.partitions) == 0 {
		return 0, errors.New("No partitions found.")
	}
	return hash % int32(len(v.partitions)), nil
}

func (v *View) find(key string) (storage.Storage, error) {
	h, err := v.hash(key)
	if err != nil {
		return nil, err
	}
	return v.partitions[h].st, nil
}

// Topic returns  the view's topic
func (v *View) Topic() string {
	return v.topic
}

// Get returns the value for the key in the view, if exists. Nil if it doesn't.
// Get can be called by multiple goroutines concurrently.
// Get can only be called after Recovered returns true.
func (v *View) Get(key string) (interface{}, error) {
	// find partition where key is located
	s, err := v.find(key)
	if err != nil {
		return nil, err
	}

	// get key and return
	data, err := s.Get(key)
	if err != nil {
		return nil, fmt.Errorf("error getting value (key %s): err", key, err)
	} else if data == nil {
		return nil, nil
	}

	// decode value
	value, err := v.opts.tableCodec.Decode(data)
	if err != nil {
		return nil, fmt.Errorf("error decoding value (key %s): err", key, err)
	}

	// if the key does not exist the return value is nil
	return value, nil
}

// Has checks whether a value for passed key exists in the view.
func (v *View) Has(key string) (bool, error) {
	// find partition where key is located
	s, err := v.find(key)
	if err != nil {
		return false, err
	}

	return s.Has(key)
}

// Iterator returns an iterator that iterates over the state of the View.
func (v *View) Iterator() (Iterator, error) {
	iters := make([]storage.Iterator, 0, len(v.partitions))
	for i := range v.partitions {
		iter, err := v.partitions[i].st.Iterator()
		if err != nil {
			// release already opened iterators
			for i := range iters {
				iters[i].Release()
			}

			return nil, fmt.Errorf("error opening partition iterator: %v", err)
		}

		iters = append(iters, iter)
	}

	return &iterator{
		iter:  storage.NewMultiIterator(iters),
		codec: v.opts.tableCodec,
	}, nil
}

// IteratorWithRange returns an iterator that iterates over the state of the View. This iterator is build using the range.
func (v *View) IteratorWithRange(start, limit string) (Iterator, error) {
	iters := make([]storage.Iterator, 0, len(v.partitions))
	for i := range v.partitions {
		iter, err := v.partitions[i].st.IteratorWithRange([]byte(start), []byte(limit))
		if err != nil {
			// release already opened iterators
			for i := range iters {
				iters[i].Release()
			}

			return nil, fmt.Errorf("error opening partition iterator: %v", err)
		}

		iters = append(iters, iter)
	}

	return &iterator{
		iter:  storage.NewMultiIterator(iters),
		codec: v.opts.tableCodec,
	}, nil
}

// Evict removes the given key only from the local cache. In order to delete a
// key from Kafka and other Views, context.Delete should be used on a Processor.
func (v *View) Evict(key string) error {
	s, err := v.find(key)
	if err != nil {
		return err
	}

	return s.Delete(key)
}

func (v *View) run() {
	defer close(v.done)
	v.opts.log.Printf("View: started")
	defer v.opts.log.Printf("View: stopped")

	for ev := range v.consumer.Events() {
		switch ev := ev.(type) {
		case *kafka.Message:
			partition := v.partitions[int(ev.Partition)]
			partition.ch <- ev
		case *kafka.BOF:
			partition := v.partitions[int(ev.Partition)]
			partition.ch <- ev
		case *kafka.EOF:
			partition := v.partitions[int(ev.Partition)]
			partition.ch <- ev
		case *kafka.Error:
			v.fail(fmt.Errorf("view: error from kafka consumer: %v", ev))
			return
		default:
			v.fail(fmt.Errorf("view: cannot handle %T = %v", ev, ev))
			return
		}
	}
}

// Recovered returns true when the view has caught up with events from kafka.
func (v *View) Recovered() bool {
	for _, p := range v.partitions {
		if !p.recovered() {
			return false
		}
	}

	return true
}

func (v *View) Stats() *ViewStats {
	var (
		m     sync.Mutex
		wg    sync.WaitGroup
		stats = newViewStats()
	)

	wg.Add(len(v.partitions))
	for i, p := range v.partitions {
		go func(pid int32, par *partition) {
			s := par.fetchStats()
			m.Lock()
			stats.Partitions[pid] = s
			m.Unlock()
			wg.Done()
		}(int32(i), p)
	}
	wg.Wait()
	return stats
}
<commit_msg>fix embarrasing dead unlock<commit_after>package goka

import (
	"errors"
	"fmt"
	"sync"

	"github.com/lovoo/goka/kafka"
	"github.com/lovoo/goka/logger"
	"github.com/lovoo/goka/multierr"
	"github.com/lovoo/goka/storage"
)

// Getter functions return a value for a key or an error. If no value exists for the key, nil is returned without errors.
type Getter func(string) (interface{}, error)

// View is a materialized (i.e. persistent) cache of a group table.
type View struct {
	brokers    []string
	topic      string
	opts       *voptions
	partitions []*partition
	consumer   kafka.Consumer
	done       chan bool
	dead       chan bool

	errors     multierr.Errors
	stopOnce   sync.Once
	mInit      sync.Mutex
	terminated bool
}

// NewView creates a new View object from a group.
func NewView(brokers []string, topic Table, codec Codec, options ...ViewOption) (*View, error) {
	options = append(
		// default options comes first
		[]ViewOption{
			WithViewLogger(logger.Default()),
			WithViewCallback(DefaultUpdate),
			WithViewPartitionChannelSize(defaultPartitionChannelSize),
			WithViewStorageBuilder(storage.DefaultBuilder(DefaultViewStoragePath())),
		},

		// then the user passed options
		options...,
	)

	// figure out how many partitions the group has
	opts := new(voptions)
	err := opts.applyOptions(topic, options...)
	if err != nil {
		return nil, fmt.Errorf("Error applying user-defined options: %v", err)
	}

	opts.tableCodec = codec

	v := &View{
		brokers: brokers,
		topic:   string(topic),
		opts:    opts,
	}

	if err = v.createPartitions(brokers); err != nil {
		return nil, err
	}

	return v, err
}

func (v *View) createPartitions(brokers []string) (err error) {
	tm, err := v.opts.builders.topicmgr(brokers)
	if err != nil {
		return fmt.Errorf("Error creating topic manager: %v", err)
	}
	defer func() {
		e := tm.Close()
		if e != nil && err == nil {
			err = fmt.Errorf("Error closing topic manager: %v", e)
		}
	}()

	partitions, err := tm.Partitions(v.topic)
	if err != nil {
		return fmt.Errorf("Error getting partitions for topic %s: %v", v.topic, err)
	}

	// check assumption that partitions are gap-less
	for i, p := range partitions {
		if i != int(p) {
			return fmt.Errorf("Partition numbers are not sequential for topic %s", v.topic)
		}
	}

	v.opts.log.Printf("Table %s has %d partitions", v.topic, len(partitions))
	for _, p := range partitions {
		st, err := v.opts.builders.storage(v.topic, p)
		if err != nil {
			// TODO(diogo): gracefully terminate all partitions
			return fmt.Errorf("Error creating local storage for partition %d: %v", p, err)
		}

		po := newPartition(v.opts.log, v.topic, nil,
			&storageProxy{Storage: st, partition: p, update: v.opts.updateCallback},
			&proxy{p, nil},
			v.opts.partitionChannelSize,
		)
		v.partitions = append(v.partitions, po)
	}

	return nil
}

// reinit (re)initializes the view and its partitions to connect to Kafka
func (v *View) reinit() error {
	v.mInit.Lock()
	defer v.mInit.Unlock()
	if v.terminated {
		return fmt.Errorf("view: cannot reinitialize terminated view")
	}

	consumer, err := v.opts.builders.consumer(v.brokers, "goka-view", v.opts.clientID)
	if err != nil {
		return fmt.Errorf("view: cannot create Kafka consumer: %v", err)
	}
	v.consumer = consumer
	v.done = make(chan bool)
	v.dead = make(chan bool)
	v.errors = multierr.Errors{}

	for i, p := range v.partitions {
		p.reinit(&proxy{int32(i), v.consumer})
	}
	return nil
}

// Start starts consuming the view's topic.
func (v *View) Start() error {
	if err := v.reinit(); err != nil {
		return err
	}
	go v.run()

	var wg sync.WaitGroup
	wg.Add(len(v.partitions))
	for id, p := range v.partitions {
		go func(id int32, p *partition) {
			defer wg.Done()
			if err := p.st.Open(); err != nil {
				v.fail(fmt.Errorf("view: error opening storage partition %d: %v", id, err))
			}
			if err := p.startCatchup(); err != nil {
				v.fail(fmt.Errorf("view: error opening partition %d: %v", id, err))
			}
		}(int32(id), p)
	}
	wg.Wait()

	<-v.dead

	if v.opts.restartable {
		// At this point, stop() has finished and goroutines exited,
		// except for start and stop goroutines. Prepare the once object for a
		// subsequent start().
		defer func() { v.stopOnce = sync.Once{} }()
	}

	if v.errors.HasErrors() {
		return &v.errors
	}
	return nil
}

func (v *View) fail(err error) {
	v.opts.log.Printf("View: failing: %v", err)
	v.errors.Collect(err)
	go v.stop()
}

func (v *View) stop() {
	v.stopOnce.Do(func() {
		defer close(v.dead)
		// stop consumer
		if err := v.consumer.Close(); err != nil {
			err = fmt.Errorf("failed to close consumer on stopping the view: %v", err)
			v.opts.log.Printf("error: %v", err)
			v.errors.Collect(err)
		}
		<-v.done
		v.opts.log.Printf("View: stopping partitions")

		var wg sync.WaitGroup
		for i, par := range v.partitions {
			wg.Add(1)
			go func(pid int, p *partition) {
				p.stop()
				wg.Done()
				v.opts.log.Printf("View: partition %d stopped", pid)
			}(i, par)
		}
		wg.Wait()

		if !v.opts.restartable {
			v.close()
		}
		v.opts.log.Printf("View: shutdown complete")
	})
}

// close closes all storage partitions
func (v *View) close() {
	for _, p := range v.partitions {
		v.errors.Collect(p.st.Close())
	}
}

// Stop stops the view, closes storage partitions, and frees resources
func (v *View) Stop() {
	v.opts.log.Printf("View: stopping")

	// do not allow any reinitialization
	v.mInit.Lock()
	if v.terminated {
		v.mInit.Unlock()
		return
	}
	v.terminated = true
	v.mInit.Unlock()

	v.stop()

	if v.opts.restartable {
		v.close()
	}
}

func (v *View) hash(key string) (int32, error) {
	// create a new hasher every time. Alternative would be to store the hash in
	// view and every time reset the hasher (ie, hasher.Reset()). But that would
	// also require us to protect the access of the hasher with a mutex.
	hasher := v.opts.hasher()

	_, err := hasher.Write([]byte(key))
	if err != nil {
		return -1, err
	}
	hash := int32(hasher.Sum32())
	if hash < 0 {
		hash = -hash
	}
	if len(v.partitions) == 0 {
		return 0, errors.New("No partitions found.")
	}
	return hash % int32(len(v.partitions)), nil
}

func (v *View) find(key string) (storage.Storage, error) {
	h, err := v.hash(key)
	if err != nil {
		return nil, err
	}
	return v.partitions[h].st, nil
}

// Topic returns  the view's topic
func (v *View) Topic() string {
	return v.topic
}

// Get returns the value for the key in the view, if exists. Nil if it doesn't.
// Get can be called by multiple goroutines concurrently.
// Get can only be called after Recovered returns true.
func (v *View) Get(key string) (interface{}, error) {
	// find partition where key is located
	s, err := v.find(key)
	if err != nil {
		return nil, err
	}

	// get key and return
	data, err := s.Get(key)
	if err != nil {
		return nil, fmt.Errorf("error getting value (key %s): err", key, err)
	} else if data == nil {
		return nil, nil
	}

	// decode value
	value, err := v.opts.tableCodec.Decode(data)
	if err != nil {
		return nil, fmt.Errorf("error decoding value (key %s): err", key, err)
	}

	// if the key does not exist the return value is nil
	return value, nil
}

// Has checks whether a value for passed key exists in the view.
func (v *View) Has(key string) (bool, error) {
	// find partition where key is located
	s, err := v.find(key)
	if err != nil {
		return false, err
	}

	return s.Has(key)
}

// Iterator returns an iterator that iterates over the state of the View.
func (v *View) Iterator() (Iterator, error) {
	iters := make([]storage.Iterator, 0, len(v.partitions))
	for i := range v.partitions {
		iter, err := v.partitions[i].st.Iterator()
		if err != nil {
			// release already opened iterators
			for i := range iters {
				iters[i].Release()
			}

			return nil, fmt.Errorf("error opening partition iterator: %v", err)
		}

		iters = append(iters, iter)
	}

	return &iterator{
		iter:  storage.NewMultiIterator(iters),
		codec: v.opts.tableCodec,
	}, nil
}

// IteratorWithRange returns an iterator that iterates over the state of the View. This iterator is build using the range.
func (v *View) IteratorWithRange(start, limit string) (Iterator, error) {
	iters := make([]storage.Iterator, 0, len(v.partitions))
	for i := range v.partitions {
		iter, err := v.partitions[i].st.IteratorWithRange([]byte(start), []byte(limit))
		if err != nil {
			// release already opened iterators
			for i := range iters {
				iters[i].Release()
			}

			return nil, fmt.Errorf("error opening partition iterator: %v", err)
		}

		iters = append(iters, iter)
	}

	return &iterator{
		iter:  storage.NewMultiIterator(iters),
		codec: v.opts.tableCodec,
	}, nil
}

// Evict removes the given key only from the local cache. In order to delete a
// key from Kafka and other Views, context.Delete should be used on a Processor.
func (v *View) Evict(key string) error {
	s, err := v.find(key)
	if err != nil {
		return err
	}

	return s.Delete(key)
}

func (v *View) run() {
	defer close(v.done)
	v.opts.log.Printf("View: started")
	defer v.opts.log.Printf("View: stopped")

	for ev := range v.consumer.Events() {
		switch ev := ev.(type) {
		case *kafka.Message:
			partition := v.partitions[int(ev.Partition)]
			partition.ch <- ev
		case *kafka.BOF:
			partition := v.partitions[int(ev.Partition)]
			partition.ch <- ev
		case *kafka.EOF:
			partition := v.partitions[int(ev.Partition)]
			partition.ch <- ev
		case *kafka.Error:
			v.fail(fmt.Errorf("view: error from kafka consumer: %v", ev))
			return
		default:
			v.fail(fmt.Errorf("view: cannot handle %T = %v", ev, ev))
			return
		}
	}
}

// Recovered returns true when the view has caught up with events from kafka.
func (v *View) Recovered() bool {
	for _, p := range v.partitions {
		if !p.recovered() {
			return false
		}
	}

	return true
}

func (v *View) Stats() *ViewStats {
	var (
		m     sync.Mutex
		wg    sync.WaitGroup
		stats = newViewStats()
	)

	wg.Add(len(v.partitions))
	for i, p := range v.partitions {
		go func(pid int32, par *partition) {
			s := par.fetchStats()
			m.Lock()
			stats.Partitions[pid] = s
			m.Unlock()
			wg.Done()
		}(int32(i), p)
	}
	wg.Wait()
	return stats
}
<|endoftext|>


<commit_before>package gcp

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"reflect"

	"github.com/EngineerBetter/concourse-up/util"
	"github.com/asaskevich/govalidator"
)

// InputVars holds all the parameters GCP IAAS needs
type InputVars struct {
	Region             string
	Zone               string
	Tags               string
	Project            string
	GCPCredentialsJSON string
	ExternalIP         string
	Deployment         string
	ConfigBucket       string
	DBUsername         string
	DBPassword         string
	DBTier             string
	DBName             string
}

// ConfigureTerraform interpolates terraform contents and returns terraform config
func (v *InputVars) ConfigureTerraform(terraformContents string) (string, error) {
	terraformConfig, err := util.RenderTemplate(terraformContents, v)
	if terraformConfig == nil {
		return "", err
	}
	return string(terraformConfig), err
}

// Build fills the InputVars struct with values from the map input
func (v *InputVars) Build(data map[string]interface{}) error {
	reflectValue := reflect.ValueOf(v)
	env := reflectValue.Elem()
	reflectType := env.Type()
	for i := 0; i < env.NumField(); i++ {
		value, ok := data[reflectType.Field(i).Name]
		if !ok {
			return errors.New("terraform:gcp field " + reflectType.Field(i).Name + " should be provided")
		}
		if !env.Field(i).CanSet() {
			return errors.New("terraform:gcp field " + reflectType.Field(i).Name + " cannot be set")
		}
		switch trueValue := value.(type) {
		case string:
			env.Field(i).SetString(trueValue)
		case bool:
			env.Field(i).SetBool(trueValue)
		default:
			return fmt.Errorf("Value: %v of type %T not supported", value, value)
		}
	}
	i := env.Interface()
	tempStruct, ok := i.(InputVars)
	if !ok {
		return errors.New("could not Build terraform input data")
	}
	v = &tempStruct // nolint
	return nil
}

// MetadataStringValue is a terraform output string variable
type MetadataStringValue struct {
	Value string `json:"value"`
}

// Metadata represents output from terraform on GCP or GCP
type Metadata struct {
	Network                    MetadataStringValue `json:"network" valid:"required"`
	PrivateSubnetworkName      MetadataStringValue `json:"private_subnetwork_name" valid:"required"`
	PublicSubnetworkName       MetadataStringValue `json:"public_subnetwork_name" valid:"required"`
	PublicSubnetworkCidr       MetadataStringValue `json:"public_subnetwork_cidr" valid:"required"`
	PrivateSubnetworkCidr      MetadataStringValue `json:"private_subnetwork_cidr" valid:"required"`
	PrivateSubnetworInternalGw MetadataStringValue `json:"private_subnetwor_internal_gw" valid:"required"`
	PublicSubnetworInternalGw  MetadataStringValue `json:"public_subnetwor_internal_gw" valid:"required"`
	ATCPublicIP                MetadataStringValue `json:"atc_public_ip" valid:"required"`
	DirectorAccountCreds       MetadataStringValue `json:"director_account_creds" valid:"required"`
	DirectorPublicIP           MetadataStringValue `json:"director_public_ip" valid:"required"`
	BoshDBAddress              MetadataStringValue `json:"bosh_db_address" valid:"required"`
	SQLCACert                  MetadataStringValue `json:"sql_ca_cert" valid:"required"`
	DBName                     MetadataStringValue `json:"db_name" valid:"required"`
	NatGatewayIP               MetadataStringValue `json:"nat_gateway_ip" valid:"required"`
	JumpboxIP                  MetadataStringValue `json:"jumpbox_public_ip" valid:"required"`
}

// AssertValid returns an error if the struct contains any missing fields
func (metadata *Metadata) AssertValid() error {
	_, err := govalidator.ValidateStruct(metadata)
	return err
}

// Init populates metadata struct with values from the buffer
func (metadata *Metadata) Init(buffer *bytes.Buffer) error {
	if err := json.NewDecoder(buffer).Decode(&metadata); err != nil {
		return err
	}

	return nil
}

// Get returns a the specified value from the metadata struct
func (metadata *Metadata) Get(key string) (string, error) {
	reflectValue := reflect.ValueOf(metadata)
	reflectStruct := reflectValue.Elem()
	value := reflectStruct.FieldByName(key)
	if !value.IsValid() {
		return "", errors.New(key + " key not found")
	}

	return value.FieldByName("Value").String(), nil
}
<commit_msg>removes jumpbox ip<commit_after>package gcp

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"reflect"

	"github.com/EngineerBetter/concourse-up/util"
	"github.com/asaskevich/govalidator"
)

// InputVars holds all the parameters GCP IAAS needs
type InputVars struct {
	Region             string
	Zone               string
	Tags               string
	Project            string
	GCPCredentialsJSON string
	ExternalIP         string
	Deployment         string
	ConfigBucket       string
	DBUsername         string
	DBPassword         string
	DBTier             string
	DBName             string
}

// ConfigureTerraform interpolates terraform contents and returns terraform config
func (v *InputVars) ConfigureTerraform(terraformContents string) (string, error) {
	terraformConfig, err := util.RenderTemplate(terraformContents, v)
	if terraformConfig == nil {
		return "", err
	}
	return string(terraformConfig), err
}

// Build fills the InputVars struct with values from the map input
func (v *InputVars) Build(data map[string]interface{}) error {
	reflectValue := reflect.ValueOf(v)
	env := reflectValue.Elem()
	reflectType := env.Type()
	for i := 0; i < env.NumField(); i++ {
		value, ok := data[reflectType.Field(i).Name]
		if !ok {
			return errors.New("terraform:gcp field " + reflectType.Field(i).Name + " should be provided")
		}
		if !env.Field(i).CanSet() {
			return errors.New("terraform:gcp field " + reflectType.Field(i).Name + " cannot be set")
		}
		switch trueValue := value.(type) {
		case string:
			env.Field(i).SetString(trueValue)
		case bool:
			env.Field(i).SetBool(trueValue)
		default:
			return fmt.Errorf("Value: %v of type %T not supported", value, value)
		}
	}
	i := env.Interface()
	tempStruct, ok := i.(InputVars)
	if !ok {
		return errors.New("could not Build terraform input data")
	}
	v = &tempStruct // nolint
	return nil
}

// MetadataStringValue is a terraform output string variable
type MetadataStringValue struct {
	Value string `json:"value"`
}

// Metadata represents output from terraform on GCP or GCP
type Metadata struct {
	Network                    MetadataStringValue `json:"network" valid:"required"`
	PrivateSubnetworkName      MetadataStringValue `json:"private_subnetwork_name" valid:"required"`
	PublicSubnetworkName       MetadataStringValue `json:"public_subnetwork_name" valid:"required"`
	PublicSubnetworkCidr       MetadataStringValue `json:"public_subnetwork_cidr" valid:"required"`
	PrivateSubnetworkCidr      MetadataStringValue `json:"private_subnetwork_cidr" valid:"required"`
	PrivateSubnetworInternalGw MetadataStringValue `json:"private_subnetwor_internal_gw" valid:"required"`
	PublicSubnetworInternalGw  MetadataStringValue `json:"public_subnetwor_internal_gw" valid:"required"`
	ATCPublicIP                MetadataStringValue `json:"atc_public_ip" valid:"required"`
	DirectorAccountCreds       MetadataStringValue `json:"director_account_creds" valid:"required"`
	DirectorPublicIP           MetadataStringValue `json:"director_public_ip" valid:"required"`
	BoshDBAddress              MetadataStringValue `json:"bosh_db_address" valid:"required"`
	SQLCACert                  MetadataStringValue `json:"sql_ca_cert" valid:"required"`
	DBName                     MetadataStringValue `json:"db_name" valid:"required"`
	NatGatewayIP               MetadataStringValue `json:"nat_gateway_ip" valid:"required"`
}

// AssertValid returns an error if the struct contains any missing fields
func (metadata *Metadata) AssertValid() error {
	_, err := govalidator.ValidateStruct(metadata)
	return err
}

// Init populates metadata struct with values from the buffer
func (metadata *Metadata) Init(buffer *bytes.Buffer) error {
	if err := json.NewDecoder(buffer).Decode(&metadata); err != nil {
		return err
	}

	return nil
}

// Get returns a the specified value from the metadata struct
func (metadata *Metadata) Get(key string) (string, error) {
	reflectValue := reflect.ValueOf(metadata)
	reflectStruct := reflectValue.Elem()
	value := reflectStruct.FieldByName(key)
	if !value.IsValid() {
		return "", errors.New(key + " key not found")
	}

	return value.FieldByName("Value").String(), nil
}
<|endoftext|>


<commit_before>package scenarios

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"sync"
	"time"

	"github.com/golang/protobuf/proto"
	"v2ray.com/core"
	"v2ray.com/core/app/log"
	"v2ray.com/core/common"
	v2net "v2ray.com/core/common/net"
	"v2ray.com/core/common/retry"
)

func pickPort() v2net.Port {
	listener, err := net.Listen("tcp4", ":0")
	common.Must(err)
	defer listener.Close()

	addr := listener.Addr().(*net.TCPAddr)
	return v2net.Port(addr.Port)
}

func xor(b []byte) []byte {
	r := make([]byte, len(b))
	for i, v := range b {
		r[i] = v ^ 'c'
	}
	return r
}

func readFrom(conn net.Conn, timeout time.Duration, length int) []byte {
	b := make([]byte, length)
	deadline := time.Now().Add(timeout)
	conn.SetReadDeadline(deadline)
	n, err := io.ReadFull(conn, b[:length])
	if err != nil {
		fmt.Println("Unexpected error from readFrom:", err)
	}
	return b[:n]
}

func InitializeServerConfig(config *core.Config) error {
	err := BuildV2Ray()
	if err != nil {
		return err
	}

	configBytes, err := proto.Marshal(config)
	if err != nil {
		return err
	}
	proc := RunV2RayProtobuf(configBytes)

	err = proc.Start()
	if err != nil {
		return err
	}

	time.Sleep(time.Second)

	runningServers = append(runningServers, proc)

	return nil
}

var (
	runningServers    = make([]*exec.Cmd, 0, 10)
	testBinaryPath    string
	testBinaryPathGen sync.Once
)

func genTestBinaryPath() {
	testBinaryPathGen.Do(func() {
		var tempDir string
		err := retry.Timed(5, 100).On(func() error {
			dir, err := ioutil.TempDir("", "v2ray")
			if err != nil {
				return err
			}
			tempDir = dir
			return nil
		})
		if err != nil {
			panic(err)
		}
		file := filepath.Join(tempDir, "v2ray.test")
		if runtime.GOOS == "windows" {
			file += ".exe"
		}
		testBinaryPath = file
		fmt.Printf("Generated binary path: %s\n", file)
	})
}

func GetSourcePath() string {
	return filepath.Join("v2ray.com", "core", "main")
}

func CloseAllServers() {
	log.Trace(errors.New("Closing all servers."))
	for _, server := range runningServers {
		server.Process.Signal(os.Interrupt)
		server.Process.Wait()
	}
	runningServers = make([]*exec.Cmd, 0, 10)
	log.Trace(errors.New("All server closed."))
}
<commit_msg>kill server faster<commit_after>package scenarios

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"sync"
	"time"

	"github.com/golang/protobuf/proto"
	"v2ray.com/core"
	"v2ray.com/core/app/log"
	"v2ray.com/core/common"
	v2net "v2ray.com/core/common/net"
	"v2ray.com/core/common/retry"
)

func pickPort() v2net.Port {
	listener, err := net.Listen("tcp4", ":0")
	common.Must(err)
	defer listener.Close()

	addr := listener.Addr().(*net.TCPAddr)
	return v2net.Port(addr.Port)
}

func xor(b []byte) []byte {
	r := make([]byte, len(b))
	for i, v := range b {
		r[i] = v ^ 'c'
	}
	return r
}

func readFrom(conn net.Conn, timeout time.Duration, length int) []byte {
	b := make([]byte, length)
	deadline := time.Now().Add(timeout)
	conn.SetReadDeadline(deadline)
	n, err := io.ReadFull(conn, b[:length])
	if err != nil {
		fmt.Println("Unexpected error from readFrom:", err)
	}
	return b[:n]
}

func InitializeServerConfig(config *core.Config) error {
	err := BuildV2Ray()
	if err != nil {
		return err
	}

	configBytes, err := proto.Marshal(config)
	if err != nil {
		return err
	}
	proc := RunV2RayProtobuf(configBytes)

	err = proc.Start()
	if err != nil {
		return err
	}

	time.Sleep(time.Second)

	runningServers = append(runningServers, proc)

	return nil
}

var (
	runningServers    = make([]*exec.Cmd, 0, 10)
	testBinaryPath    string
	testBinaryPathGen sync.Once
)

func genTestBinaryPath() {
	testBinaryPathGen.Do(func() {
		var tempDir string
		err := retry.Timed(5, 100).On(func() error {
			dir, err := ioutil.TempDir("", "v2ray")
			if err != nil {
				return err
			}
			tempDir = dir
			return nil
		})
		if err != nil {
			panic(err)
		}
		file := filepath.Join(tempDir, "v2ray.test")
		if runtime.GOOS == "windows" {
			file += ".exe"
		}
		testBinaryPath = file
		fmt.Printf("Generated binary path: %s\n", file)
	})
}

func GetSourcePath() string {
	return filepath.Join("v2ray.com", "core", "main")
}

func CloseAllServers() {
	log.Trace(errors.New("Closing all servers."))
	for _, server := range runningServers {
		server.Process.Signal(os.Interrupt)
	}
	for _, server := range runningServers {
		server.Process.Wait()
	}
	runningServers = make([]*exec.Cmd, 0, 10)
	log.Trace(errors.New("All server closed."))
}
<|endoftext|>


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

// Package dl implements a simple Go downloads frontend server.
//
// It accepts HTTP POST requests to create a new download metadata entity,
// and lists entities with sorting and filtering.
//
// The list of downloads, as well as individual files, are served at:
//
//	https://go.dev/dl/
//	https://go.dev/dl/{file}
//
// An optional query param, mode=json, serves the list of stable release
// downloads in JSON format:
//
//	https://go.dev/dl/?mode=json
//
// An additional query param, include=all, when used with the mode=json
// query param, will serve a full list of available downloads, including
// unstable, stable, and archived releases, in JSON format:
//
//	https://go.dev/dl/?mode=json&include=all
//
// Releases returned in JSON modes are sorted by version, newest to oldest.
package dl

import (
	"fmt"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
)

const (
	cacheKey      = "download_list_5" // increment if listTemplateData changes
	cacheDuration = time.Hour
)

// File represents a file on the go.dev downloads page.
// It should be kept in sync with the upload code in x/build/internal/relui.
type File struct {
	Filename       string    `json:"filename"`
	OS             string    `json:"os"`
	Arch           string    `json:"arch"`
	Version        string    `json:"version"`
	Checksum       string    `json:"-" datastore:",noindex"` // SHA1; deprecated
	ChecksumSHA256 string    `json:"sha256" datastore:",noindex"`
	Size           int64     `json:"size" datastore:",noindex"`
	Kind           string    `json:"kind"` // "archive", "installer", "source"
	Uploaded       time.Time `json:"-"`
}

func (f File) ChecksumType() string {
	if f.ChecksumSHA256 != "" {
		return "SHA256"
	}
	return "SHA1"
}

func (f File) PrettyArch() string { return pretty(f.Arch) }
func (f File) PrettyKind() string { return pretty(f.Kind) }

func (f File) PrettyChecksum() string {
	if f.ChecksumSHA256 != "" {
		return f.ChecksumSHA256
	}
	return f.Checksum
}

func (f File) PrettyOS() string {
	if f.OS == "darwin" {
		// Some older releases, like Go 1.4,
		// still contain "osx" in the filename.
		switch {
		case strings.Contains(f.Filename, "osx10.8"):
			return "OS X 10.8+"
		case strings.Contains(f.Filename, "osx10.6"):
			return "OS X 10.6+"
		}
	}
	return pretty(f.OS)
}

func (f File) PrettySize() string {
	const mb = 1 << 20
	if f.Size == 0 {
		return ""
	}
	if f.Size < mb {
		// All Go releases are >1mb, but handle this case anyway.
		return fmt.Sprintf("%v bytes", f.Size)
	}
	return fmt.Sprintf("%.0fMB", float64(f.Size)/mb)
}

var primaryPorts = map[string]bool{
	"darwin/amd64":  true,
	"darwin/arm64":  true,
	"linux/386":     true,
	"linux/amd64":   true,
	"linux/armv6l":  true,
	"linux/arm64":   true,
	"windows/386":   true,
	"windows/amd64": true,
}

func (f File) PrimaryPort() bool {
	if f.Kind == "source" {
		return true
	}
	return primaryPorts[f.OS+"/"+f.Arch]
}

func (f File) Highlight() bool {
	switch {
	case f.Kind == "source":
		return true
	case f.OS == "linux" && f.Arch == "amd64":
		return true
	case f.OS == "windows" && f.Kind == "installer" && (f.Arch == "amd64" || f.Arch == "arm64"):
		return true
	case f.OS == "darwin" && f.Kind == "installer" && !strings.Contains(f.Filename, "osx10.6"):
		return true
	}
	return false
}

// URL returns the canonical URL of the file.
func (f File) URL() string {
	// The download URL of a Go release file is /dl/{name}. It is handled by getHandler.
	// Use a relative URL so it works for any host like go.dev and golang.google.cn.
	// Don't shortcut to the redirect target here, we want canonical URLs to be visible. See issue 38713.
	return "/dl/" + f.Filename
}

type Release struct {
	Version        string `json:"version"`
	Stable         bool   `json:"stable"`
	Files          []File `json:"files"`
	Visible        bool   `json:"-"` // show files on page load
	SplitPortTable bool   `json:"-"` // whether files should be split by primary/other ports.
}

type Feature struct {
	// The File field will be filled in by the first stable File
	// whose name matches the given fileRE.
	File
	fileRE *regexp.Regexp

	Platform     string // "Microsoft Windows", "Apple macOS", "Linux"
	Requirements string // "Windows XP and above, 64-bit Intel Processor"
}

// featuredFiles lists the platforms and files to be featured
// at the top of the downloads page.
var featuredFiles = []Feature{
	{
		Platform:     "Microsoft Windows",
		Requirements: "Windows 7 or later, Intel 64-bit processor",
		fileRE:       regexp.MustCompile(`\.windows-amd64\.msi$`),
	},
	{
		Platform:     "Apple macOS (ARM64)",
		Requirements: "macOS 11 or later, Apple 64-bit processor",
		fileRE:       regexp.MustCompile(`\.darwin-arm64\.pkg$`),
	},
	{
		Platform:     "Apple macOS (x86-64)",
		Requirements: "macOS 10.13 or later, Intel 64-bit processor",
		fileRE:       regexp.MustCompile(`\.darwin-amd64\.pkg$`),
	},
	{
		Platform:     "Linux",
		Requirements: "Linux 2.6.23 or later, Intel 64-bit processor",
		fileRE:       regexp.MustCompile(`\.linux-amd64\.tar\.gz$`),
	},
	{
		Platform: "Source",
		fileRE:   regexp.MustCompile(`\.src\.tar\.gz$`),
	},
}

// data to send to the template; increment cacheKey if you change this.
type listTemplateData struct {
	Featured                  []Feature
	Stable, Unstable, Archive []Release
}

func filesToFeatured(fs []File) (featured []Feature) {
	for _, feature := range featuredFiles {
		for _, file := range fs {
			if feature.fileRE.MatchString(file.Filename) {
				feature.File = file
				featured = append(featured, feature)
				break
			}
		}
	}
	return
}

func filesToReleases(fs []File) (stable, unstable, archive []Release) {
	sort.Sort(fileOrder(fs))

	var r *Release
	var stableMaj, stableMin int
	add := func() {
		if r == nil {
			return
		}
		if !r.Stable {
			if len(unstable) != 0 {
				// Only show one (latest) unstable version,
				// consider the older ones to be archived.
				archive = append(archive, *r)
				return
			}
			maj, min, _ := parseVersion(r.Version)
			if maj < stableMaj || maj == stableMaj && min <= stableMin {
				// Display unstable version only if newer than the
				// latest stable release, otherwise consider it archived.
				archive = append(archive, *r)
				return
			}
			unstable = append(unstable, *r)
			return
		}

		// Reports whether the release is the most recent minor version of the
		// two most recent major versions.
		shouldAddStable := func() bool {
			if len(stable) >= 2 {
				// Show up to two stable versions.
				return false
			}
			if len(stable) == 0 {
				// Most recent stable version.
				stableMaj, stableMin, _ = parseVersion(r.Version)
				return true
			}
			if maj, _, _ := parseVersion(r.Version); maj == stableMaj {
				// Older minor version of most recent major version.
				return false
			}
			// Second most recent stable version.
			return true
		}
		if !shouldAddStable() {
			archive = append(archive, *r)
			return
		}

		// Split the file list into primary/other ports for the stable releases.
		// NOTE(cbro): This is only done for stable releases because maintaining the historical
		// nature of primary/other ports for older versions is infeasible.
		// If freebsd is considered primary some time in the future, we'd not want to
		// mark all of the older freebsd binaries as "primary".
		// It might be better if we set that as a flag when uploading.
		r.SplitPortTable = true
		r.Visible = true // Toggle open all stable releases.
		stable = append(stable, *r)
	}
	for _, f := range fs {
		if r == nil || f.Version != r.Version {
			add()
			r = &Release{
				Version: f.Version,
				Stable:  isStable(f.Version),
			}
		}
		r.Files = append(r.Files, f)
	}
	add()
	return
}

// isStable reports whether the version string v is a stable version.
func isStable(v string) bool {
	return !strings.Contains(v, "beta") && !strings.Contains(v, "rc")
}

type fileOrder []File

func (s fileOrder) Len() int      { return len(s) }
func (s fileOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s fileOrder) Less(i, j int) bool {
	a, b := s[i], s[j]
	if av, bv := a.Version, b.Version; av != bv {
		// Put stable releases first.
		if isStable(av) != isStable(bv) {
			return isStable(av)
		}
		return versionLess(av, bv)
	}
	if a.OS != b.OS {
		return a.OS < b.OS
	}
	if a.Arch != b.Arch {
		return a.Arch < b.Arch
	}
	if a.Kind != b.Kind {
		return a.Kind < b.Kind
	}
	return a.Filename < b.Filename
}

func versionLess(a, b string) bool {
	maja, mina, ta := parseVersion(a)
	majb, minb, tb := parseVersion(b)
	if maja == majb {
		if mina == minb {
			if ta == "" {
				return true
			} else if tb == "" {
				return false
			}
			return ta >= tb
		}
		return mina >= minb
	}
	return maja >= majb
}

func parseVersion(v string) (maj, min int, tail string) {
	if i := strings.Index(v, "beta"); i > 0 {
		tail = v[i:]
		v = v[:i]
	}
	if i := strings.Index(v, "rc"); i > 0 {
		tail = v[i:]
		v = v[:i]
	}
	p := strings.Split(strings.TrimPrefix(v, "go1."), ".")
	maj, _ = strconv.Atoi(p[0])
	if len(p) < 2 {
		return
	}
	min, _ = strconv.Atoi(p[1])
	return
}

// validUser controls whether the named gomote user is allowed to upload
// Go release binaries via the /dl/upload endpoint.
func validUser(user string) bool {
	switch user {
	case "amedee", "cherryyz", "dmitshur", "drchase", "heschi", "mknyszek", "rakoczy", "thanm":
		return true
	case "relui":
		return true
	}
	return false
}

var (
	fileRe  = regexp.MustCompile(`^go[0-9a-z.]+\.[0-9a-z.-]+\.(tar\.gz|tar\.gz\.asc|pkg|msi|zip)$`)
	goGetRe = regexp.MustCompile(`^go[0-9a-z.]+\.[0-9a-z.-]+$`)
)

// pretty returns a human-readable version of the given OS, Arch, or Kind.
func pretty(s string) string {
	t, ok := prettyStrings[s]
	if !ok {
		return s
	}
	return t
}

var prettyStrings = map[string]string{
	"darwin":  "macOS",
	"freebsd": "FreeBSD",
	"linux":   "Linux",
	"windows": "Windows",

	"386":    "x86",
	"amd64":  "x86-64",
	"armv6l": "ARMv6",
	"arm64":  "ARM64",

	"archive":   "Archive",
	"installer": "Installer",
	"source":    "Source",
}
<commit_msg>internal/dl: update minimum Linux kernel to 2.6.32<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package dl implements a simple Go downloads frontend server.
//
// It accepts HTTP POST requests to create a new download metadata entity,
// and lists entities with sorting and filtering.
//
// The list of downloads, as well as individual files, are served at:
//
//	https://go.dev/dl/
//	https://go.dev/dl/{file}
//
// An optional query param, mode=json, serves the list of stable release
// downloads in JSON format:
//
//	https://go.dev/dl/?mode=json
//
// An additional query param, include=all, when used with the mode=json
// query param, will serve a full list of available downloads, including
// unstable, stable, and archived releases, in JSON format:
//
//	https://go.dev/dl/?mode=json&include=all
//
// Releases returned in JSON modes are sorted by version, newest to oldest.
package dl

import (
	"fmt"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
)

const (
	cacheKey      = "download_list_5" // increment if listTemplateData changes
	cacheDuration = time.Hour
)

// File represents a file on the go.dev downloads page.
// It should be kept in sync with the upload code in x/build/internal/relui.
type File struct {
	Filename       string    `json:"filename"`
	OS             string    `json:"os"`
	Arch           string    `json:"arch"`
	Version        string    `json:"version"`
	Checksum       string    `json:"-" datastore:",noindex"` // SHA1; deprecated
	ChecksumSHA256 string    `json:"sha256" datastore:",noindex"`
	Size           int64     `json:"size" datastore:",noindex"`
	Kind           string    `json:"kind"` // "archive", "installer", "source"
	Uploaded       time.Time `json:"-"`
}

func (f File) ChecksumType() string {
	if f.ChecksumSHA256 != "" {
		return "SHA256"
	}
	return "SHA1"
}

func (f File) PrettyArch() string { return pretty(f.Arch) }
func (f File) PrettyKind() string { return pretty(f.Kind) }

func (f File) PrettyChecksum() string {
	if f.ChecksumSHA256 != "" {
		return f.ChecksumSHA256
	}
	return f.Checksum
}

func (f File) PrettyOS() string {
	if f.OS == "darwin" {
		// Some older releases, like Go 1.4,
		// still contain "osx" in the filename.
		switch {
		case strings.Contains(f.Filename, "osx10.8"):
			return "OS X 10.8+"
		case strings.Contains(f.Filename, "osx10.6"):
			return "OS X 10.6+"
		}
	}
	return pretty(f.OS)
}

func (f File) PrettySize() string {
	const mb = 1 << 20
	if f.Size == 0 {
		return ""
	}
	if f.Size < mb {
		// All Go releases are >1mb, but handle this case anyway.
		return fmt.Sprintf("%v bytes", f.Size)
	}
	return fmt.Sprintf("%.0fMB", float64(f.Size)/mb)
}

var primaryPorts = map[string]bool{
	"darwin/amd64":  true,
	"darwin/arm64":  true,
	"linux/386":     true,
	"linux/amd64":   true,
	"linux/armv6l":  true,
	"linux/arm64":   true,
	"windows/386":   true,
	"windows/amd64": true,
}

func (f File) PrimaryPort() bool {
	if f.Kind == "source" {
		return true
	}
	return primaryPorts[f.OS+"/"+f.Arch]
}

func (f File) Highlight() bool {
	switch {
	case f.Kind == "source":
		return true
	case f.OS == "linux" && f.Arch == "amd64":
		return true
	case f.OS == "windows" && f.Kind == "installer" && (f.Arch == "amd64" || f.Arch == "arm64"):
		return true
	case f.OS == "darwin" && f.Kind == "installer" && !strings.Contains(f.Filename, "osx10.6"):
		return true
	}
	return false
}

// URL returns the canonical URL of the file.
func (f File) URL() string {
	// The download URL of a Go release file is /dl/{name}. It is handled by getHandler.
	// Use a relative URL so it works for any host like go.dev and golang.google.cn.
	// Don't shortcut to the redirect target here, we want canonical URLs to be visible. See issue 38713.
	return "/dl/" + f.Filename
}

type Release struct {
	Version        string `json:"version"`
	Stable         bool   `json:"stable"`
	Files          []File `json:"files"`
	Visible        bool   `json:"-"` // show files on page load
	SplitPortTable bool   `json:"-"` // whether files should be split by primary/other ports.
}

type Feature struct {
	// The File field will be filled in by the first stable File
	// whose name matches the given fileRE.
	File
	fileRE *regexp.Regexp

	Platform     string // "Microsoft Windows", "Apple macOS", "Linux"
	Requirements string // "Windows XP and above, 64-bit Intel Processor"
}

// featuredFiles lists the platforms and files to be featured
// at the top of the downloads page.
var featuredFiles = []Feature{
	{
		Platform:     "Microsoft Windows",
		Requirements: "Windows 7 or later, Intel 64-bit processor",
		fileRE:       regexp.MustCompile(`\.windows-amd64\.msi$`),
	},
	{
		Platform:     "Apple macOS (ARM64)",
		Requirements: "macOS 11 or later, Apple 64-bit processor",
		fileRE:       regexp.MustCompile(`\.darwin-arm64\.pkg$`),
	},
	{
		Platform:     "Apple macOS (x86-64)",
		Requirements: "macOS 10.13 or later, Intel 64-bit processor",
		fileRE:       regexp.MustCompile(`\.darwin-amd64\.pkg$`),
	},
	{
		Platform:     "Linux",
		Requirements: "Linux 2.6.32 or later, Intel 64-bit processor",
		fileRE:       regexp.MustCompile(`\.linux-amd64\.tar\.gz$`),
	},
	{
		Platform: "Source",
		fileRE:   regexp.MustCompile(`\.src\.tar\.gz$`),
	},
}

// data to send to the template; increment cacheKey if you change this.
type listTemplateData struct {
	Featured                  []Feature
	Stable, Unstable, Archive []Release
}

func filesToFeatured(fs []File) (featured []Feature) {
	for _, feature := range featuredFiles {
		for _, file := range fs {
			if feature.fileRE.MatchString(file.Filename) {
				feature.File = file
				featured = append(featured, feature)
				break
			}
		}
	}
	return
}

func filesToReleases(fs []File) (stable, unstable, archive []Release) {
	sort.Sort(fileOrder(fs))

	var r *Release
	var stableMaj, stableMin int
	add := func() {
		if r == nil {
			return
		}
		if !r.Stable {
			if len(unstable) != 0 {
				// Only show one (latest) unstable version,
				// consider the older ones to be archived.
				archive = append(archive, *r)
				return
			}
			maj, min, _ := parseVersion(r.Version)
			if maj < stableMaj || maj == stableMaj && min <= stableMin {
				// Display unstable version only if newer than the
				// latest stable release, otherwise consider it archived.
				archive = append(archive, *r)
				return
			}
			unstable = append(unstable, *r)
			return
		}

		// Reports whether the release is the most recent minor version of the
		// two most recent major versions.
		shouldAddStable := func() bool {
			if len(stable) >= 2 {
				// Show up to two stable versions.
				return false
			}
			if len(stable) == 0 {
				// Most recent stable version.
				stableMaj, stableMin, _ = parseVersion(r.Version)
				return true
			}
			if maj, _, _ := parseVersion(r.Version); maj == stableMaj {
				// Older minor version of most recent major version.
				return false
			}
			// Second most recent stable version.
			return true
		}
		if !shouldAddStable() {
			archive = append(archive, *r)
			return
		}

		// Split the file list into primary/other ports for the stable releases.
		// NOTE(cbro): This is only done for stable releases because maintaining the historical
		// nature of primary/other ports for older versions is infeasible.
		// If freebsd is considered primary some time in the future, we'd not want to
		// mark all of the older freebsd binaries as "primary".
		// It might be better if we set that as a flag when uploading.
		r.SplitPortTable = true
		r.Visible = true // Toggle open all stable releases.
		stable = append(stable, *r)
	}
	for _, f := range fs {
		if r == nil || f.Version != r.Version {
			add()
			r = &Release{
				Version: f.Version,
				Stable:  isStable(f.Version),
			}
		}
		r.Files = append(r.Files, f)
	}
	add()
	return
}

// isStable reports whether the version string v is a stable version.
func isStable(v string) bool {
	return !strings.Contains(v, "beta") && !strings.Contains(v, "rc")
}

type fileOrder []File

func (s fileOrder) Len() int      { return len(s) }
func (s fileOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s fileOrder) Less(i, j int) bool {
	a, b := s[i], s[j]
	if av, bv := a.Version, b.Version; av != bv {
		// Put stable releases first.
		if isStable(av) != isStable(bv) {
			return isStable(av)
		}
		return versionLess(av, bv)
	}
	if a.OS != b.OS {
		return a.OS < b.OS
	}
	if a.Arch != b.Arch {
		return a.Arch < b.Arch
	}
	if a.Kind != b.Kind {
		return a.Kind < b.Kind
	}
	return a.Filename < b.Filename
}

func versionLess(a, b string) bool {
	maja, mina, ta := parseVersion(a)
	majb, minb, tb := parseVersion(b)
	if maja == majb {
		if mina == minb {
			if ta == "" {
				return true
			} else if tb == "" {
				return false
			}
			return ta >= tb
		}
		return mina >= minb
	}
	return maja >= majb
}

func parseVersion(v string) (maj, min int, tail string) {
	if i := strings.Index(v, "beta"); i > 0 {
		tail = v[i:]
		v = v[:i]
	}
	if i := strings.Index(v, "rc"); i > 0 {
		tail = v[i:]
		v = v[:i]
	}
	p := strings.Split(strings.TrimPrefix(v, "go1."), ".")
	maj, _ = strconv.Atoi(p[0])
	if len(p) < 2 {
		return
	}
	min, _ = strconv.Atoi(p[1])
	return
}

// validUser controls whether the named gomote user is allowed to upload
// Go release binaries via the /dl/upload endpoint.
func validUser(user string) bool {
	switch user {
	case "amedee", "cherryyz", "dmitshur", "drchase", "heschi", "mknyszek", "rakoczy", "thanm":
		return true
	case "relui":
		return true
	}
	return false
}

var (
	fileRe  = regexp.MustCompile(`^go[0-9a-z.]+\.[0-9a-z.-]+\.(tar\.gz|tar\.gz\.asc|pkg|msi|zip)$`)
	goGetRe = regexp.MustCompile(`^go[0-9a-z.]+\.[0-9a-z.-]+$`)
)

// pretty returns a human-readable version of the given OS, Arch, or Kind.
func pretty(s string) string {
	t, ok := prettyStrings[s]
	if !ok {
		return s
	}
	return t
}

var prettyStrings = map[string]string{
	"darwin":  "macOS",
	"freebsd": "FreeBSD",
	"linux":   "Linux",
	"windows": "Windows",

	"386":    "x86",
	"amd64":  "x86-64",
	"armv6l": "ARMv6",
	"arm64":  "ARM64",

	"archive":   "Archive",
	"installer": "Installer",
	"source":    "Source",
}
<|endoftext|>


<commit_before>package arn

import (
	"errors"
	"fmt"
	"sync"

	"github.com/aerogo/nano"
)

// Group represents a group of users.
type Group struct {
	Name        string         `json:"name" editable:"true"`
	Tagline     string         `json:"tagline" editable:"true"`
	Image       GroupImage     `json:"image"`
	Description string         `json:"description" editable:"true" type:"textarea"`
	Rules       string         `json:"rules" editable:"true" type:"textarea"`
	Tags        []string       `json:"tags" editable:"true"`
	Members     []*GroupMember `json:"members"`
	Neighbors   []string       `json:"neighbors"`

	// Mixins
	HasID
	HasPosts
	HasCreator
	HasEditor
	HasDraft

	// Mutex
	membersMutex sync.Mutex
}

// Link returns the URI to the group page.
func (group *Group) Link() string {
	return "/group/" + group.ID
}

// TitleByUser returns the preferred title for the given user.
func (group *Group) TitleByUser(user *User) string {
	if group.Name == "" {
		return "untitled"
	}

	return group.Name
}

// String is the default text representation of the group.
func (group *Group) String() string {
	return group.TitleByUser(nil)
}

// FindMember returns the group member by user ID, if available.
func (group *Group) FindMember(userID string) *GroupMember {
	group.membersMutex.Lock()
	defer group.membersMutex.Unlock()

	for _, member := range group.Members {
		if member.UserID == userID {
			return member
		}
	}

	return nil
}

// TypeName returns the type name.
func (group *Group) TypeName() string {
	return "Group"
}

// Publish ...
func (group *Group) Publish() error {
	if len(group.Name) < 2 {
		return errors.New("Name too short: Should be at least 2 characters")
	}

	if len(group.Name) > 35 {
		return errors.New("Name too long: Should not be more than 35 characters")
	}

	if len(group.Tagline) < 4 {
		return errors.New("Tagline too short: Should be at least 4 characters")
	}

	if len(group.Tagline) > 60 {
		return errors.New("Tagline too long: Should not be more than 60 characters")
	}

	return publish(group)
}

// Unpublish ...
func (group *Group) Unpublish() error {
	return unpublish(group)
}

// Join makes the given user join the group.
func (group *Group) Join(user *User) error {
	group.membersMutex.Lock()
	defer group.membersMutex.Unlock()

	// Check if the user is already a member
	member := group.FindMember(user.ID)

	if member != nil {
		return errors.New("Already a member of this group")
	}

	// Add user to the members list
	group.Members = append(group.Members, &GroupMember{
		UserID: user.ID,
		Joined: DateTimeUTC(),
	})

	group.OnJoin(user)
	return nil
}

// Leave makes the given user leave the group.
func (group *Group) Leave(user *User) error {
	group.membersMutex.Lock()
	defer group.membersMutex.Unlock()

	for index, member := range group.Members {
		if member.UserID == user.ID {
			if member.UserID == group.CreatedBy {
				return errors.New("The founder can not leave the group, please contact a staff member")
			}

			group.Members = append(group.Members[:index], group.Members[index+1:]...)
			return nil
		}
	}

	return nil
}

// OnJoin sends notifications to the creator.
func (group *Group) OnJoin(user *User) {
	go func() {
		group.Creator().SendNotification(&PushNotification{
			Title:   "Someone joined your group!",
			Message: fmt.Sprintf(`%s has joined your group "%s"`, user.Nick, group.Name),
			Icon:    user.AvatarLink("medium"),
			Link:    "https://notify.moe" + group.Link() + "/members",
			Type:    NotificationTypeGroupJoin,
		})
	}()
}

// AverageColor returns the average color of the image.
func (group *Group) AverageColor() string {
	color := group.Image.AverageColor

	if color.Hue == 0 && color.Saturation == 0 && color.Lightness == 0 {
		return ""
	}

	return color.String()
}

// ImageLink returns a link to the group image.
func (group *Group) ImageLink(size string) string {
	if !group.HasImage() {
		return fmt.Sprintf("//%s/images/elements/no-group-image.svg", MediaHost)
	}

	extension := ".jpg"

	if size == "original" {
		extension = group.Image.Extension
	}

	return fmt.Sprintf("//%s/images/groups/%s/%s%s?%v", MediaHost, size, group.ID, extension, group.Image.LastModified)
}

// GetGroup ...
func GetGroup(id string) (*Group, error) {
	obj, err := DB.Get("Group", id)

	if err != nil {
		return nil, err
	}

	return obj.(*Group), nil
}

// StreamGroups returns a stream of all groups.
func StreamGroups() chan *Group {
	channel := make(chan *Group, nano.ChannelBufferSize)

	go func() {
		for obj := range DB.All("Group") {
			channel <- obj.(*Group)
		}

		close(channel)
	}()

	return channel
}

// AllGroups returns a slice of all groups.
func AllGroups() ([]*Group, error) {
	var all []*Group

	stream := StreamGroups()

	for obj := range stream {
		all = append(all, obj)
	}

	return all, nil
}

// FilterGroups filters all groups by a custom function.
func FilterGroups(filter func(*Group) bool) ([]*Group, error) {
	var filtered []*Group

	for obj := range DB.All("Group") {
		realObject := obj.(*Group)

		if filter(realObject) {
			filtered = append(filtered, realObject)
		}
	}

	return filtered, nil
}
<commit_msg>Removed old API signature<commit_after>package arn

import (
	"errors"
	"fmt"
	"sync"

	"github.com/aerogo/nano"
)

// Group represents a group of users.
type Group struct {
	Name        string         `json:"name" editable:"true"`
	Tagline     string         `json:"tagline" editable:"true"`
	Image       GroupImage     `json:"image"`
	Description string         `json:"description" editable:"true" type:"textarea"`
	Rules       string         `json:"rules" editable:"true" type:"textarea"`
	Tags        []string       `json:"tags" editable:"true"`
	Members     []*GroupMember `json:"members"`
	Neighbors   []string       `json:"neighbors"`

	// Mixins
	HasID
	HasPosts
	HasCreator
	HasEditor
	HasDraft

	// Mutex
	membersMutex sync.Mutex
}

// Link returns the URI to the group page.
func (group *Group) Link() string {
	return "/group/" + group.ID
}

// TitleByUser returns the preferred title for the given user.
func (group *Group) TitleByUser(user *User) string {
	if group.Name == "" {
		return "untitled"
	}

	return group.Name
}

// String is the default text representation of the group.
func (group *Group) String() string {
	return group.TitleByUser(nil)
}

// FindMember returns the group member by user ID, if available.
func (group *Group) FindMember(userID string) *GroupMember {
	group.membersMutex.Lock()
	defer group.membersMutex.Unlock()

	for _, member := range group.Members {
		if member.UserID == userID {
			return member
		}
	}

	return nil
}

// TypeName returns the type name.
func (group *Group) TypeName() string {
	return "Group"
}

// Publish ...
func (group *Group) Publish() error {
	if len(group.Name) < 2 {
		return errors.New("Name too short: Should be at least 2 characters")
	}

	if len(group.Name) > 35 {
		return errors.New("Name too long: Should not be more than 35 characters")
	}

	if len(group.Tagline) < 4 {
		return errors.New("Tagline too short: Should be at least 4 characters")
	}

	if len(group.Tagline) > 60 {
		return errors.New("Tagline too long: Should not be more than 60 characters")
	}

	return publish(group)
}

// Unpublish ...
func (group *Group) Unpublish() error {
	return unpublish(group)
}

// Join makes the given user join the group.
func (group *Group) Join(user *User) error {
	group.membersMutex.Lock()
	defer group.membersMutex.Unlock()

	// Check if the user is already a member
	member := group.FindMember(user.ID)

	if member != nil {
		return errors.New("Already a member of this group")
	}

	// Add user to the members list
	group.Members = append(group.Members, &GroupMember{
		UserID: user.ID,
		Joined: DateTimeUTC(),
	})

	group.OnJoin(user)
	return nil
}

// Leave makes the given user leave the group.
func (group *Group) Leave(user *User) error {
	group.membersMutex.Lock()
	defer group.membersMutex.Unlock()

	for index, member := range group.Members {
		if member.UserID == user.ID {
			if member.UserID == group.CreatedBy {
				return errors.New("The founder can not leave the group, please contact a staff member")
			}

			group.Members = append(group.Members[:index], group.Members[index+1:]...)
			return nil
		}
	}

	return nil
}

// OnJoin sends notifications to the creator.
func (group *Group) OnJoin(user *User) {
	go func() {
		group.Creator().SendNotification(&PushNotification{
			Title:   "Someone joined your group!",
			Message: fmt.Sprintf(`%s has joined your group "%s"`, user.Nick, group.Name),
			Icon:    user.AvatarLink("medium"),
			Link:    "https://notify.moe" + group.Link() + "/members",
			Type:    NotificationTypeGroupJoin,
		})
	}()
}

// AverageColor returns the average color of the image.
func (group *Group) AverageColor() string {
	color := group.Image.AverageColor

	if color.Hue == 0 && color.Saturation == 0 && color.Lightness == 0 {
		return ""
	}

	return color.String()
}

// ImageLink returns a link to the group image.
func (group *Group) ImageLink(size string) string {
	if !group.HasImage() {
		return fmt.Sprintf("//%s/images/elements/no-group-image.svg", MediaHost)
	}

	extension := ".jpg"

	if size == "original" {
		extension = group.Image.Extension
	}

	return fmt.Sprintf("//%s/images/groups/%s/%s%s?%v", MediaHost, size, group.ID, extension, group.Image.LastModified)
}

// GetGroup ...
func GetGroup(id string) (*Group, error) {
	obj, err := DB.Get("Group", id)

	if err != nil {
		return nil, err
	}

	return obj.(*Group), nil
}

// StreamGroups returns a stream of all groups.
func StreamGroups() chan *Group {
	channel := make(chan *Group, nano.ChannelBufferSize)

	go func() {
		for obj := range DB.All("Group") {
			channel <- obj.(*Group)
		}

		close(channel)
	}()

	return channel
}

// AllGroups returns a slice of all groups.
func AllGroups() []*Group {
	var all []*Group
	stream := StreamGroups()

	for obj := range stream {
		all = append(all, obj)
	}

	return all
}

// FilterGroups filters all groups by a custom function.
func FilterGroups(filter func(*Group) bool) []*Group {
	var filtered []*Group

	for obj := range DB.All("Group") {
		realObject := obj.(*Group)

		if filter(realObject) {
			filtered = append(filtered, realObject)
		}
	}

	return filtered
}
<|endoftext|>


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

// Package internal contains support packages for oauth2 package.
package internal

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/context"
)

// Token represents the crendentials used to authorize
// the requests to access protected resources on the OAuth 2.0
// provider's backend.
//
// This type is a mirror of oauth2.Token and exists to break
// an otherwise-circular dependency. Other internal packages
// should convert this Token into an oauth2.Token before use.
type Token struct {
	// AccessToken is the token that authorizes and authenticates
	// the requests.
	AccessToken string

	// TokenType is the type of token.
	// The Type method returns either this or "Bearer", the default.
	TokenType string

	// RefreshToken is a token that's used by the application
	// (as opposed to the user) to refresh the access token
	// if it expires.
	RefreshToken string

	// Expiry is the optional expiration time of the access token.
	//
	// If zero, TokenSource implementations will reuse the same
	// token forever and RefreshToken or equivalent
	// mechanisms for that TokenSource will not be used.
	Expiry time.Time

	// Raw optionally contains extra metadata from the server
	// when updating a token.
	Raw interface{}
}

// tokenJSON is the struct representing the HTTP response from OAuth2
// providers returning a token in JSON form.
type tokenJSON struct {
	AccessToken  string         `json:"access_token"`
	TokenType    string         `json:"token_type"`
	RefreshToken string         `json:"refresh_token"`
	ExpiresIn    expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
	Expires      expirationTime `json:"expires"`    // broken Facebook spelling of expires_in
}

func (e *tokenJSON) expiry() (t time.Time) {
	if v := e.ExpiresIn; v != 0 {
		return time.Now().Add(time.Duration(v) * time.Second)
	}
	if v := e.Expires; v != 0 {
		return time.Now().Add(time.Duration(v) * time.Second)
	}
	return
}

type expirationTime int32

func (e *expirationTime) UnmarshalJSON(b []byte) error {
	var n json.Number
	err := json.Unmarshal(b, &n)
	if err != nil {
		return err
	}
	i, err := n.Int64()
	if err != nil {
		return err
	}
	*e = expirationTime(i)
	return nil
}

var brokenAuthHeaderProviders = []string{
	"https://accounts.google.com/",
	"https://www.googleapis.com/",
	"https://github.com/",
	"https://api.instagram.com/",
	"https://www.douban.com/",
	"https://api.dropbox.com/",
	"https://api.soundcloud.com/",
	"https://www.linkedin.com/",
	"https://api.twitch.tv/",
	"https://oauth.vk.com/",
	"https://api.odnoklassniki.ru/",
	"https://connect.stripe.com/",
	"https://api.pushbullet.com/",
	"https://oauth.sandbox.trainingpeaks.com/",
	"https://oauth.trainingpeaks.com/",
	"https://www.strava.com/oauth/",
	"https://app.box.com/",
	"https://test-sandbox.auth.corp.google.com",
	"https://user.gini.net/",
	"https://api.netatmo.net/",
	"https://login.mailchimp.com/",
	"https://api.createsend.com/",
	"https://disqus.com/",
}

// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
// implements the OAuth2 spec correctly
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
// In summary:
// - Reddit only accepts client secret in the Authorization header
// - Dropbox accepts either it in URL param or Auth header, but not both.
// - Google only accepts URL param (not spec compliant?), not Auth header
// - Stripe only accepts client secret in Auth header with Bearer method, not Basic
func providerAuthHeaderWorks(tokenURL string) bool {
	for _, s := range brokenAuthHeaderProviders {
		if strings.HasPrefix(tokenURL, s) {
			// Some sites fail to implement the OAuth2 spec fully.
			return false
		}
	}

	// Assume the provider implements the spec properly
	// otherwise. We can add more exceptions as they're
	// discovered. We will _not_ be adding configurable hooks
	// to this package to let users select server bugs.
	return true
}

func RetrieveToken(ctx context.Context, ClientID, ClientSecret, TokenURL string, v url.Values) (*Token, error) {
	hc, err := ContextClient(ctx)
	if err != nil {
		return nil, err
	}
	v.Set("client_id", ClientID)
	bustedAuth := !providerAuthHeaderWorks(TokenURL)
	if bustedAuth && ClientSecret != "" {
		v.Set("client_secret", ClientSecret)
	}
	req, err := http.NewRequest("POST", TokenURL, strings.NewReader(v.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	if !bustedAuth {
		req.SetBasicAuth(ClientID, ClientSecret)
	}
	r, err := hc.Do(req)
	if err != nil {
		return nil, err
	}
	defer r.Body.Close()
	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
	if err != nil {
		return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
	}
	if code := r.StatusCode; code < 200 || code > 299 {
		return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body)
	}

	var token *Token
	content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
	switch content {
	case "application/x-www-form-urlencoded", "text/plain":
		vals, err := url.ParseQuery(string(body))
		if err != nil {
			return nil, err
		}
		token = &Token{
			AccessToken:  vals.Get("access_token"),
			TokenType:    vals.Get("token_type"),
			RefreshToken: vals.Get("refresh_token"),
			Raw:          vals,
		}
		e := vals.Get("expires_in")
		if e == "" {
			// TODO(jbd): Facebook's OAuth2 implementation is broken and
			// returns expires_in field in expires. Remove the fallback to expires,
			// when Facebook fixes their implementation.
			e = vals.Get("expires")
		}
		expires, _ := strconv.Atoi(e)
		if expires != 0 {
			token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
		}
	default:
		var tj tokenJSON
		if err = json.Unmarshal(body, &tj); err != nil {
			return nil, err
		}
		token = &Token{
			AccessToken:  tj.AccessToken,
			TokenType:    tj.TokenType,
			RefreshToken: tj.RefreshToken,
			Expiry:       tj.expiry(),
			Raw:          make(map[string]interface{}),
		}
		json.Unmarshal(body, &token.Raw) // no error checks for optional fields
	}
	// Don't overwrite `RefreshToken` with an empty value
	// if this was a token refreshing request.
	if token.RefreshToken == "" {
		token.RefreshToken = v.Get("refresh_token")
	}
	return token, nil
}
<commit_msg>Add Slack as a bad provider<commit_after>// Copyright 2014 The oauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package internal contains support packages for oauth2 package.
package internal

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/context"
)

// Token represents the crendentials used to authorize
// the requests to access protected resources on the OAuth 2.0
// provider's backend.
//
// This type is a mirror of oauth2.Token and exists to break
// an otherwise-circular dependency. Other internal packages
// should convert this Token into an oauth2.Token before use.
type Token struct {
	// AccessToken is the token that authorizes and authenticates
	// the requests.
	AccessToken string

	// TokenType is the type of token.
	// The Type method returns either this or "Bearer", the default.
	TokenType string

	// RefreshToken is a token that's used by the application
	// (as opposed to the user) to refresh the access token
	// if it expires.
	RefreshToken string

	// Expiry is the optional expiration time of the access token.
	//
	// If zero, TokenSource implementations will reuse the same
	// token forever and RefreshToken or equivalent
	// mechanisms for that TokenSource will not be used.
	Expiry time.Time

	// Raw optionally contains extra metadata from the server
	// when updating a token.
	Raw interface{}
}

// tokenJSON is the struct representing the HTTP response from OAuth2
// providers returning a token in JSON form.
type tokenJSON struct {
	AccessToken  string         `json:"access_token"`
	TokenType    string         `json:"token_type"`
	RefreshToken string         `json:"refresh_token"`
	ExpiresIn    expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
	Expires      expirationTime `json:"expires"`    // broken Facebook spelling of expires_in
}

func (e *tokenJSON) expiry() (t time.Time) {
	if v := e.ExpiresIn; v != 0 {
		return time.Now().Add(time.Duration(v) * time.Second)
	}
	if v := e.Expires; v != 0 {
		return time.Now().Add(time.Duration(v) * time.Second)
	}
	return
}

type expirationTime int32

func (e *expirationTime) UnmarshalJSON(b []byte) error {
	var n json.Number
	err := json.Unmarshal(b, &n)
	if err != nil {
		return err
	}
	i, err := n.Int64()
	if err != nil {
		return err
	}
	*e = expirationTime(i)
	return nil
}

var brokenAuthHeaderProviders = []string{
	"https://accounts.google.com/",
	"https://www.googleapis.com/",
	"https://github.com/",
	"https://api.instagram.com/",
	"https://www.douban.com/",
	"https://api.dropbox.com/",
	"https://api.soundcloud.com/",
	"https://www.linkedin.com/",
	"https://api.twitch.tv/",
	"https://oauth.vk.com/",
	"https://api.odnoklassniki.ru/",
	"https://connect.stripe.com/",
	"https://api.pushbullet.com/",
	"https://oauth.sandbox.trainingpeaks.com/",
	"https://oauth.trainingpeaks.com/",
	"https://www.strava.com/oauth/",
	"https://app.box.com/",
	"https://test-sandbox.auth.corp.google.com",
	"https://user.gini.net/",
	"https://api.netatmo.net/",
	"https://login.mailchimp.com/",
	"https://api.createsend.com/",
	"https://disqus.com/",
	"https://slack.com/",
}

// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
// implements the OAuth2 spec correctly
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
// In summary:
// - Reddit only accepts client secret in the Authorization header
// - Dropbox accepts either it in URL param or Auth header, but not both.
// - Google only accepts URL param (not spec compliant?), not Auth header
// - Stripe only accepts client secret in Auth header with Bearer method, not Basic
func providerAuthHeaderWorks(tokenURL string) bool {
	for _, s := range brokenAuthHeaderProviders {
		if strings.HasPrefix(tokenURL, s) {
			// Some sites fail to implement the OAuth2 spec fully.
			return false
		}
	}

	// Assume the provider implements the spec properly
	// otherwise. We can add more exceptions as they're
	// discovered. We will _not_ be adding configurable hooks
	// to this package to let users select server bugs.
	return true
}

func RetrieveToken(ctx context.Context, ClientID, ClientSecret, TokenURL string, v url.Values) (*Token, error) {
	hc, err := ContextClient(ctx)
	if err != nil {
		return nil, err
	}
	v.Set("client_id", ClientID)
	bustedAuth := !providerAuthHeaderWorks(TokenURL)
	if bustedAuth && ClientSecret != "" {
		v.Set("client_secret", ClientSecret)
	}
	req, err := http.NewRequest("POST", TokenURL, strings.NewReader(v.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	if !bustedAuth {
		req.SetBasicAuth(ClientID, ClientSecret)
	}
	r, err := hc.Do(req)
	if err != nil {
		return nil, err
	}
	defer r.Body.Close()
	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
	if err != nil {
		return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
	}
	if code := r.StatusCode; code < 200 || code > 299 {
		return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body)
	}

	var token *Token
	content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
	switch content {
	case "application/x-www-form-urlencoded", "text/plain":
		vals, err := url.ParseQuery(string(body))
		if err != nil {
			return nil, err
		}
		token = &Token{
			AccessToken:  vals.Get("access_token"),
			TokenType:    vals.Get("token_type"),
			RefreshToken: vals.Get("refresh_token"),
			Raw:          vals,
		}
		e := vals.Get("expires_in")
		if e == "" {
			// TODO(jbd): Facebook's OAuth2 implementation is broken and
			// returns expires_in field in expires. Remove the fallback to expires,
			// when Facebook fixes their implementation.
			e = vals.Get("expires")
		}
		expires, _ := strconv.Atoi(e)
		if expires != 0 {
			token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
		}
	default:
		var tj tokenJSON
		if err = json.Unmarshal(body, &tj); err != nil {
			return nil, err
		}
		token = &Token{
			AccessToken:  tj.AccessToken,
			TokenType:    tj.TokenType,
			RefreshToken: tj.RefreshToken,
			Expiry:       tj.expiry(),
			Raw:          make(map[string]interface{}),
		}
		json.Unmarshal(body, &token.Raw) // no error checks for optional fields
	}
	// Don't overwrite `RefreshToken` with an empty value
	// if this was a token refreshing request.
	if token.RefreshToken == "" {
		token.RefreshToken = v.Get("refresh_token")
	}
	return token, nil
}
<|endoftext|>


<commit_before>// Package internal contains utilities internal to the weasel packages.
package internal

import (
	"golang.org/x/oauth2/google"
	"golang.org/x/net/context"
	"golang.org/x/oauth2"
)

// AETokenSource returns App Engine OAuth2 token source
// given a context.Context and a slice of scopes.
// It is a stubbed static token source during testing.
func tmp(ctx context.Context, scope ...string) oauth2.TokenSource {
  ts, err := google.DefaultTokenSource(ctx, scope...)
  if (err != nil) {
    panic(err)
  }
  return ts
}
var AETokenSource = tmp
<commit_msg>fix review comments<commit_after>// Package internal contains utilities internal to the weasel packages.
package internal

import (
	"context"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
)

// AETokenSource returns App Engine OAuth2 token source
// given a context.Context and a slice of scopes.
// It is a stubbed static token source during testing.
var AETokenSource = func(ctx context.Context, scope ...string) oauth2.TokenSource {
  ts, err := google.DefaultTokenSource(ctx, scope...)
  if (err != nil) {
    panic(err)
  }
  return ts
}
<|endoftext|>


<commit_before>// UDPN (Universal Plug and Play) package provides an implementation of the UDPN
// specification.
package udpn

import (
	"bufio"
	"bytes"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
	"io"
)

const (
	// The port for UDPN discovery
	Port = 1900

	// The IP for UDPN broadcast
	BroadcastIP = "239.255.255.250"
)

// The search response from a device implementing UDPN.
type SearchResponse struct {
	Control      string
	Server       string
	ST           string
	Ext          string
	USN          string
	Location     *url.URL
	Date         time.Time
	ResponseAddr *net.UDPAddr
}

// The search reader interface to read UDP packets on the wire with a timeout
// period specified.
type searchReader interface {
	ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
	SetReadDeadline(t time.Time) error
}

// Search the network for UDPN devices using the given search string and duration
// to discover new devices. This function will return an array of SearchReponses
// discovered.
func Search(st string, mx time.Duration) ([]*SearchResponse, error) {
	conn, err := listenForSearchResponses()
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	searchBytes, broadcastAddr := buildSearchRequest(st, mx)
	// Write search bytes on the wire so all devices can respond
	_, err = conn.WriteTo(searchBytes, broadcastAddr)
	if err != nil {
		return nil, err
	}

	return readSearchResponses(conn, mx)
}

func listenForSearchResponses() (*net.UDPConn, error) {
	serverAddr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:"+strconv.Itoa(Port))
	return net.ListenUDP("udp", serverAddr)
}

func buildSearchRequest(st string, mx time.Duration) ([]byte, *net.UDPAddr) {
	// Placeholder to replace with * later on
	replaceMePlaceHolder := "/replacemewithstar"

	broadcastAddr, _ := net.ResolveUDPAddr("udp", BroadcastIP+":"+strconv.Itoa(Port))
	request, _ := http.NewRequest("M-SEARCH",
		"http://"+broadcastAddr.String()+replaceMePlaceHolder, strings.NewReader(""))

	headers := request.Header
	headers.Set("User-Agent", "")
	headers.Set("st", st)
	headers.Set("man", `"ssdp:discover"`)
	headers.Set("mx", strconv.Itoa(int(mx/time.Second)))

	searchBytes := make([]byte, 0, 1024)
	buffer := bytes.NewBuffer(searchBytes)
	err := request.Write(buffer)
	if err != nil {
		panic("Fatal error writing to buffer. This should never happen (in theory).")
	}
	searchBytes = buffer.Bytes()

	// Replace placeholder with *. Needed because request always escapes * when it shouldn't
	searchBytes = bytes.Replace(searchBytes, []byte(replaceMePlaceHolder), []byte("*"), 1)

	return searchBytes, broadcastAddr
}

func readSearchResponses(reader searchReader, duration time.Duration) ([]*SearchResponse, error) {
	responses := make([]*SearchResponse, 0, 10)
	// Only listen for responses for duration amount of time.
	reader.SetReadDeadline(time.Now().Add(duration))

	buf := make([]byte, 1024)
	for {
		rlen, addr, err := reader.ReadFromUDP(buf)
		if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
			break // duration reached, return what we've found
		}
		if err != nil {
			return nil, err
		}

		response, err := parseSearchResponse(bytes.NewReader(buf[:rlen]), addr)
		if err != nil {
			return nil, err
		}
		responses = append(responses, response)
	}

	return responses, nil
}

func parseSearchResponse(httpResponse io.Reader, responseAddr *net.UDPAddr) (*SearchResponse, error) {
	reader := bufio.NewReader(httpResponse)
	request := &http.Request{} // Needed for ReadResponse but doesn't have to be real
	response, err := http.ReadResponse(reader, request)
	if err != nil {
		return nil, err
	}
	headers := response.Header

	res := &SearchResponse{}

	res.Control = headers.Get("cache-control")
	res.Server = headers.Get("server")
	res.ST = headers.Get("st")
	res.Ext = headers.Get("ext")
	res.USN = headers.Get("usn")
	res.ResponseAddr = responseAddr

	if headers.Get("location") != "" {
		res.Location, err = response.Location()
		if err != nil {
			return nil, err
		}
	}

	date := headers.Get("date")
	if date != "" {
		res.Date, err = http.ParseTime(date)
		if err != nil {
			return nil, err
		}
	}

	return res, nil
}
<commit_msg>return []SearchRequest<commit_after>// UDPN (Universal Plug and Play) package provides an implementation of the UDPN
// specification.
package udpn

import (
	"bufio"
	"bytes"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
	"io"
)

const (
	// The port for UDPN discovery
	Port = 1900

	// The IP for UDPN broadcast
	BroadcastIP = "239.255.255.250"
)

// The search response from a device implementing UDPN.
type SearchResponse struct {
	Control      string
	Server       string
	ST           string
	Ext          string
	USN          string
	Location     *url.URL
	Date         time.Time
	ResponseAddr *net.UDPAddr
}

// The search reader interface to read UDP packets on the wire with a timeout
// period specified.
type searchReader interface {
	ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
	SetReadDeadline(t time.Time) error
}

// Search the network for UDPN devices using the given search string and duration
// to discover new devices. This function will return an array of SearchReponses
// discovered.
func Search(st string, mx time.Duration) ([]SearchResponse, error) {
	conn, err := listenForSearchResponses()
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	searchBytes, broadcastAddr := buildSearchRequest(st, mx)
	// Write search bytes on the wire so all devices can respond
	_, err = conn.WriteTo(searchBytes, broadcastAddr)
	if err != nil {
		return nil, err
	}

	return readSearchResponses(conn, mx)
}

func listenForSearchResponses() (*net.UDPConn, error) {
	serverAddr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:"+strconv.Itoa(Port))
	return net.ListenUDP("udp", serverAddr)
}

func buildSearchRequest(st string, mx time.Duration) ([]byte, *net.UDPAddr) {
	// Placeholder to replace with * later on
	replaceMePlaceHolder := "/replacemewithstar"

	broadcastAddr, _ := net.ResolveUDPAddr("udp", BroadcastIP+":"+strconv.Itoa(Port))
	request, _ := http.NewRequest("M-SEARCH",
		"http://"+broadcastAddr.String()+replaceMePlaceHolder, strings.NewReader(""))

	headers := request.Header
	headers.Set("User-Agent", "")
	headers.Set("st", st)
	headers.Set("man", `"ssdp:discover"`)
	headers.Set("mx", strconv.Itoa(int(mx/time.Second)))

	searchBytes := make([]byte, 0, 1024)
	buffer := bytes.NewBuffer(searchBytes)
	err := request.Write(buffer)
	if err != nil {
		panic("Fatal error writing to buffer. This should never happen (in theory).")
	}
	searchBytes = buffer.Bytes()

	// Replace placeholder with *. Needed because request always escapes * when it shouldn't
	searchBytes = bytes.Replace(searchBytes, []byte(replaceMePlaceHolder), []byte("*"), 1)

	return searchBytes, broadcastAddr
}

func readSearchResponses(reader searchReader, duration time.Duration) ([]SearchResponse, error) {
	responses := make([]SearchResponse, 0, 10)
	// Only listen for responses for duration amount of time.
	reader.SetReadDeadline(time.Now().Add(duration))

	buf := make([]byte, 1024)
	for {
		rlen, addr, err := reader.ReadFromUDP(buf)
		if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
			break // duration reached, return what we've found
		}
		if err != nil {
			return nil, err
		}

		response, err := parseSearchResponse(bytes.NewReader(buf[:rlen]), addr)
		if err != nil {
			return nil, err
		}
		responses = append(responses, *response)
	}

	return responses, nil
}

func parseSearchResponse(httpResponse io.Reader, responseAddr *net.UDPAddr) (*SearchResponse, error) {
	reader := bufio.NewReader(httpResponse)
	request := &http.Request{} // Needed for ReadResponse but doesn't have to be real
	response, err := http.ReadResponse(reader, request)
	if err != nil {
		return nil, err
	}
	headers := response.Header

	res := &SearchResponse{}

	res.Control = headers.Get("cache-control")
	res.Server = headers.Get("server")
	res.ST = headers.Get("st")
	res.Ext = headers.Get("ext")
	res.USN = headers.Get("usn")
	res.ResponseAddr = responseAddr

	if headers.Get("location") != "" {
		res.Location, err = response.Location()
		if err != nil {
			return nil, err
		}
	}

	date := headers.Get("date")
	if date != "" {
		res.Date, err = http.ParseTime(date)
		if err != nil {
			return nil, err
		}
	}

	return res, nil
}
<|endoftext|>