<commit_before>package client

import (
	"fmt"
	"html/template"
	"net/url"

	"sourcegraph.com/sourcegraph/api_router"
	"sourcegraph.com/sourcegraph/srcgraph/authorship"
	"sourcegraph.com/sourcegraph/srcgraph/graph"
	"sourcegraph.com/sourcegraph/srcgraph/person"
	"sourcegraph.com/sourcegraph/srcgraph/repo"
)

type SymbolsService interface {
	Get(symbol SymbolSpec, opt *GetSymbolOptions) (*Symbol, *Response, error)
	List(opt *SymbolListOptions) ([]*Symbol, *Response, error)
	ListExamples(symbol SymbolSpec, opt *SymbolExampleListOptions) ([]*Example, *Response, error)
	ListAuthors(symbol SymbolSpec, opt *SymbolAuthorListOptions) ([]*AugmentedSymbolAuthor, *Response, error)
	ListClients(symbol SymbolSpec, opt *SymbolClientListOptions) ([]*AugmentedSymbolClient, *Response, error)
	ListDependentRepositories(symbol SymbolSpec, opt *SymbolDependentRepositoryListOptions) ([]*AugmentedRepoRef, *Response, error)
	ListImplementations(symbol SymbolSpec, opt *SymbolListImplementationsOptions) ([]*Symbol, *Response, error)
	ListInterfaces(symbol SymbolSpec, opt *SymbolListInterfacesOptions) ([]*Symbol, *Response, error)
	CountByRepository(repo RepositorySpec) (*graph.SymbolCounts, *Response, error)
}

type SymbolSpec struct {
	SID int64

	Repo     string
	UnitType string
	Unit     string
	Path     string
}

func (s *SymbolSpec) SymbolKey() graph.SymbolKey {
	return graph.SymbolKey{
		Repo:     repo.URI(s.Repo),
		UnitType: s.UnitType,
		Unit:     s.Unit,
		Path:     graph.SymbolPath(s.Path),
	}
}

func NewSymbolSpecFromSymbolKey(key graph.SymbolKey) SymbolSpec {
	return SymbolSpec{
		Repo:     string(key.Repo),
		UnitType: key.UnitType,
		Unit:     key.Unit,
		Path:     string(key.Path),
	}
}

type symbolsService struct {
	client *Client
}

var _ SymbolsService = &symbolsService{}

type Symbol struct {
	graph.Symbol

	Stat map[graph.StatType]int `json:",omitempty"`

	Doc      string           `json:",omitempty"`
	DefHTML  template.HTML    `json:",omitempty"`
	DocPages []*graph.DocPage `json:",omitempty"`
}

func (s *Symbol) XRefs() int     { return s.Stat["xrefs"] }
func (s *Symbol) RRefs() int     { return s.Stat["rrefs"] }
func (s *Symbol) URefs() int     { return s.Stat["urefs"] }
func (s *Symbol) TotalRefs() int { return s.XRefs() + s.RRefs() + s.URefs() }

type GetSymbolOptions struct {
	Annotate bool `url:",omitempty"`
	DocPages bool `url:",omitempty"`
}

func (s *symbolsService) Get(symbol SymbolSpec, opt *GetSymbolOptions) (*Symbol, *Response, error) {
	var url *url.URL
	var err error
	if symbol.SID != 0 {
		url, err = s.client.url(api_router.SymbolBySID, map[string]string{"SID": fmt.Sprintf("%d", symbol.SID)}, opt)
	} else {
		url, err = s.client.url(api_router.Symbol, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	}
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var symbol_ *Symbol
	resp, err := s.client.Do(req, &symbol_)
	if err != nil {
		return nil, resp, err
	}

	return symbol_, resp, nil
}

type SymbolListOptions struct {
	RepositoryURI string `url:",omitempty"`
	Query         string `url:",omitempty"`

	Sort      string `url:",omitempty"`
	Direction string `url:",omitempty"`

	Kinds        []string `url:",omitempty,comma"`
	SpecificKind string   `url:",omitempty"`

	Scope     string `url:",omitempty"`
	Recursive bool   `url:",omitempty"`
	Exported  bool   `url:",omitempty"`
	Doc       bool   `url:",omitempty"`

	ListOptions
}

func (s *symbolsService) List(opt *SymbolListOptions) ([]*Symbol, *Response, error) {
	url, err := s.client.url(api_router.Symbols, nil, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var symbols []*Symbol
	resp, err := s.client.Do(req, &symbols)
	if err != nil {
		return nil, resp, err
	}

	return symbols, resp, nil
}

type Example struct {
	graph.Ref
	SrcHTML template.HTML
}

type Examples []*Example

func (r *Example) sortKey() string     { return fmt.Sprintf("%+v", r) }
func (vs Examples) Len() int           { return len(vs) }
func (vs Examples) Swap(i, j int)      { vs[i], vs[j] = vs[j], vs[i] }
func (vs Examples) Less(i, j int) bool { return vs[i].sortKey() < vs[j].sortKey() }

type SymbolExampleListOptions struct {
	Annotate bool

	ListOptions
}

func (s *symbolsService) ListExamples(symbol SymbolSpec, opt *SymbolExampleListOptions) ([]*Example, *Response, error) {
	url, err := s.client.url(api_router.SymbolExamples, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var examples []*Example
	resp, err := s.client.Do(req, &examples)
	if err != nil {
		return nil, resp, err
	}

	return examples, resp, nil
}

type AugmentedSymbolAuthor struct {
	User *person.User
	*authorship.SymbolAuthor
}

type SymbolAuthorListOptions struct {
	ListOptions
}

func (s *symbolsService) ListAuthors(symbol SymbolSpec, opt *SymbolAuthorListOptions) ([]*AugmentedSymbolAuthor, *Response, error) {
	url, err := s.client.url(api_router.SymbolAuthors, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var authors []*AugmentedSymbolAuthor
	resp, err := s.client.Do(req, &authors)
	if err != nil {
		return nil, resp, err
	}

	return authors, resp, nil
}

type AugmentedSymbolClient struct {
	User *person.User
	*authorship.SymbolClient
}

type SymbolClientListOptions struct {
	ListOptions
}

func (s *symbolsService) ListClients(symbol SymbolSpec, opt *SymbolClientListOptions) ([]*AugmentedSymbolClient, *Response, error) {
	url, err := s.client.url(api_router.SymbolClients, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var clients []*AugmentedSymbolClient
	resp, err := s.client.Do(req, &clients)
	if err != nil {
		return nil, resp, err
	}

	return clients, resp, nil
}

type AugmentedRepoRef struct {
	Repo  *repo.Repository
	Count int
}

type SymbolDependentRepositoryListOptions struct {
	ListOptions
}

func (s *symbolsService) ListDependentRepositories(symbol SymbolSpec, opt *SymbolDependentRepositoryListOptions) ([]*AugmentedRepoRef, *Response, error) {
	url, err := s.client.url(api_router.SymbolDependents, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var dependents []*AugmentedRepoRef
	resp, err := s.client.Do(req, &dependents)
	if err != nil {
		return nil, resp, err
	}

	return dependents, resp, nil
}

type SymbolListImplementationsOptions struct {
	ListOptions
}

func (s *symbolsService) ListImplementations(symbol SymbolSpec, opt *SymbolListImplementationsOptions) ([]*Symbol, *Response, error) {
	url, err := s.client.url(api_router.SymbolImplementations, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var symbols []*Symbol
	resp, err := s.client.Do(req, &symbols)
	if err != nil {
		return nil, resp, err
	}

	return symbols, resp, nil
}

type SymbolListInterfacesOptions struct {
	ListOptions
}

func (s *symbolsService) ListInterfaces(symbol SymbolSpec, opt *SymbolListInterfacesOptions) ([]*Symbol, *Response, error) {
	url, err := s.client.url(api_router.SymbolInterfaces, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var symbols []*Symbol
	resp, err := s.client.Do(req, &symbols)
	if err != nil {
		return nil, resp, err
	}

	return symbols, resp, nil
}

func (s *symbolsService) CountByRepository(repo RepositorySpec) (*graph.SymbolCounts, *Response, error) {
	url, err := s.client.url(api_router.RepositorySymbolCounts, map[string]string{"RepoURI": repo.URI}, nil)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var counts *graph.SymbolCounts
	resp, err := s.client.Do(req, &counts)
	if err != nil {
		return nil, resp, err
	}

	return counts, resp, nil
}

type MockSymbolsService struct {
	Get_                       func(symbol SymbolSpec, opt *GetSymbolOptions) (*Symbol, *Response, error)
	List_                      func(opt *SymbolListOptions) ([]*Symbol, *Response, error)
	ListExamples_              func(symbol SymbolSpec, opt *SymbolExampleListOptions) ([]*Example, *Response, error)
	ListAuthors_               func(symbol SymbolSpec, opt *SymbolAuthorListOptions) ([]*AugmentedSymbolAuthor, *Response, error)
	ListClients_               func(symbol SymbolSpec, opt *SymbolClientListOptions) ([]*AugmentedSymbolClient, *Response, error)
	ListDependentRepositories_ func(symbol SymbolSpec, opt *SymbolDependentRepositoryListOptions) ([]*AugmentedRepoRef, *Response, error)
	ListImplementations_       func(symbol SymbolSpec, opt *SymbolListImplementationsOptions) ([]*Symbol, *Response, error)
	ListInterfaces_            func(symbol SymbolSpec, opt *SymbolListInterfacesOptions) ([]*Symbol, *Response, error)
	CountByRepository_         func(repo RepositorySpec) (*graph.SymbolCounts, *Response, error)
}

var _ SymbolsService = MockSymbolsService{}

func (s MockSymbolsService) Get(symbol SymbolSpec, opt *GetSymbolOptions) (*Symbol, *Response, error) {
	if s.Get_ == nil {
		return nil, &Response{}, nil
	}
	return s.Get_(symbol, opt)
}

func (s MockSymbolsService) List(opt *SymbolListOptions) ([]*Symbol, *Response, error) {
	if s.List_ == nil {
		return nil, &Response{}, nil
	}
	return s.List_(opt)
}

func (s MockSymbolsService) ListExamples(symbol SymbolSpec, opt *SymbolExampleListOptions) ([]*Example, *Response, error) {
	if s.ListExamples_ == nil {
		return nil, &Response{}, nil
	}
	return s.ListExamples_(symbol, opt)
}

func (s MockSymbolsService) ListAuthors(symbol SymbolSpec, opt *SymbolAuthorListOptions) ([]*AugmentedSymbolAuthor, *Response, error) {
	if s.ListAuthors_ == nil {
		return nil, &Response{}, nil
	}
	return s.ListAuthors_(symbol, opt)
}

func (s MockSymbolsService) ListClients(symbol SymbolSpec, opt *SymbolClientListOptions) ([]*AugmentedSymbolClient, *Response, error) {
	if s.ListClients_ == nil {
		return nil, &Response{}, nil
	}
	return s.ListClients_(symbol, opt)
}

func (s MockSymbolsService) ListDependentRepositories(symbol SymbolSpec, opt *SymbolDependentRepositoryListOptions) ([]*AugmentedRepoRef, *Response, error) {
	if s.ListDependentRepositories_ == nil {
		return nil, &Response{}, nil
	}
	return s.ListDependentRepositories_(symbol, opt)
}

func (s MockSymbolsService) ListImplementations(symbol SymbolSpec, opt *SymbolListImplementationsOptions) ([]*Symbol, *Response, error) {
	if s.ListImplementations_ == nil {
		return nil, &Response{}, nil
	}
	return s.ListImplementations_(symbol, opt)
}

func (s MockSymbolsService) ListInterfaces(symbol SymbolSpec, opt *SymbolListInterfacesOptions) ([]*Symbol, *Response, error) {
	if s.ListInterfaces_ == nil {
		return nil, &Response{}, nil
	}
	return s.ListInterfaces_(symbol, opt)
}

func (s MockSymbolsService) CountByRepository(repo RepositorySpec) (*graph.SymbolCounts, *Response, error) {
	if s.CountByRepository_ == nil {
		return &graph.SymbolCounts{}, &Response{}, nil
	}
	return s.CountByRepository_(repo)
}
<commit_msg>SymbolSpec method<commit_after>package client

import (
	"fmt"
	"html/template"
	"net/url"

	"sourcegraph.com/sourcegraph/api_router"
	"sourcegraph.com/sourcegraph/srcgraph/authorship"
	"sourcegraph.com/sourcegraph/srcgraph/graph"
	"sourcegraph.com/sourcegraph/srcgraph/person"
	"sourcegraph.com/sourcegraph/srcgraph/repo"
)

type SymbolsService interface {
	Get(symbol SymbolSpec, opt *GetSymbolOptions) (*Symbol, *Response, error)
	List(opt *SymbolListOptions) ([]*Symbol, *Response, error)
	ListExamples(symbol SymbolSpec, opt *SymbolExampleListOptions) ([]*Example, *Response, error)
	ListAuthors(symbol SymbolSpec, opt *SymbolAuthorListOptions) ([]*AugmentedSymbolAuthor, *Response, error)
	ListClients(symbol SymbolSpec, opt *SymbolClientListOptions) ([]*AugmentedSymbolClient, *Response, error)
	ListDependentRepositories(symbol SymbolSpec, opt *SymbolDependentRepositoryListOptions) ([]*AugmentedRepoRef, *Response, error)
	ListImplementations(symbol SymbolSpec, opt *SymbolListImplementationsOptions) ([]*Symbol, *Response, error)
	ListInterfaces(symbol SymbolSpec, opt *SymbolListInterfacesOptions) ([]*Symbol, *Response, error)
	CountByRepository(repo RepositorySpec) (*graph.SymbolCounts, *Response, error)
}

type SymbolSpec struct {
	SID int64

	Repo     string
	UnitType string
	Unit     string
	Path     string
}

func (s *SymbolSpec) SymbolKey() graph.SymbolKey {
	return graph.SymbolKey{
		Repo:     repo.URI(s.Repo),
		UnitType: s.UnitType,
		Unit:     s.Unit,
		Path:     graph.SymbolPath(s.Path),
	}
}

func NewSymbolSpecFromSymbolKey(key graph.SymbolKey) SymbolSpec {
	return SymbolSpec{
		Repo:     string(key.Repo),
		UnitType: key.UnitType,
		Unit:     key.Unit,
		Path:     string(key.Path),
	}
}

type symbolsService struct {
	client *Client
}

var _ SymbolsService = &symbolsService{}

type Symbol struct {
	graph.Symbol

	Stat map[graph.StatType]int `json:",omitempty"`

	Doc      string           `json:",omitempty"`
	DefHTML  template.HTML    `json:",omitempty"`
	DocPages []*graph.DocPage `json:",omitempty"`
}

func (s *Symbol) SymbolSpec() SymbolSpec {
	spec := NewSymbolSpecFromSymbolKey(s.Symbol.SymbolKey)
	spec.SID = int64(s.Symbol.SID)
	return spec
}

func (s *Symbol) XRefs() int     { return s.Stat["xrefs"] }
func (s *Symbol) RRefs() int     { return s.Stat["rrefs"] }
func (s *Symbol) URefs() int     { return s.Stat["urefs"] }
func (s *Symbol) TotalRefs() int { return s.XRefs() + s.RRefs() + s.URefs() }

type GetSymbolOptions struct {
	Annotate bool `url:",omitempty"`
	DocPages bool `url:",omitempty"`
}

func (s *symbolsService) Get(symbol SymbolSpec, opt *GetSymbolOptions) (*Symbol, *Response, error) {
	var url *url.URL
	var err error
	if symbol.SID != 0 {
		url, err = s.client.url(api_router.SymbolBySID, map[string]string{"SID": fmt.Sprintf("%d", symbol.SID)}, opt)
	} else {
		url, err = s.client.url(api_router.Symbol, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	}
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var symbol_ *Symbol
	resp, err := s.client.Do(req, &symbol_)
	if err != nil {
		return nil, resp, err
	}

	return symbol_, resp, nil
}

type SymbolListOptions struct {
	RepositoryURI string `url:",omitempty"`
	Query         string `url:",omitempty"`

	Sort      string `url:",omitempty"`
	Direction string `url:",omitempty"`

	Kinds        []string `url:",omitempty,comma"`
	SpecificKind string   `url:",omitempty"`

	Scope     string `url:",omitempty"`
	Recursive bool   `url:",omitempty"`
	Exported  bool   `url:",omitempty"`
	Doc       bool   `url:",omitempty"`

	ListOptions
}

func (s *symbolsService) List(opt *SymbolListOptions) ([]*Symbol, *Response, error) {
	url, err := s.client.url(api_router.Symbols, nil, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var symbols []*Symbol
	resp, err := s.client.Do(req, &symbols)
	if err != nil {
		return nil, resp, err
	}

	return symbols, resp, nil
}

type Example struct {
	graph.Ref
	SrcHTML template.HTML
}

type Examples []*Example

func (r *Example) sortKey() string     { return fmt.Sprintf("%+v", r) }
func (vs Examples) Len() int           { return len(vs) }
func (vs Examples) Swap(i, j int)      { vs[i], vs[j] = vs[j], vs[i] }
func (vs Examples) Less(i, j int) bool { return vs[i].sortKey() < vs[j].sortKey() }

type SymbolExampleListOptions struct {
	Annotate bool

	ListOptions
}

func (s *symbolsService) ListExamples(symbol SymbolSpec, opt *SymbolExampleListOptions) ([]*Example, *Response, error) {
	url, err := s.client.url(api_router.SymbolExamples, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var examples []*Example
	resp, err := s.client.Do(req, &examples)
	if err != nil {
		return nil, resp, err
	}

	return examples, resp, nil
}

type AugmentedSymbolAuthor struct {
	User *person.User
	*authorship.SymbolAuthor
}

type SymbolAuthorListOptions struct {
	ListOptions
}

func (s *symbolsService) ListAuthors(symbol SymbolSpec, opt *SymbolAuthorListOptions) ([]*AugmentedSymbolAuthor, *Response, error) {
	url, err := s.client.url(api_router.SymbolAuthors, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var authors []*AugmentedSymbolAuthor
	resp, err := s.client.Do(req, &authors)
	if err != nil {
		return nil, resp, err
	}

	return authors, resp, nil
}

type AugmentedSymbolClient struct {
	User *person.User
	*authorship.SymbolClient
}

type SymbolClientListOptions struct {
	ListOptions
}

func (s *symbolsService) ListClients(symbol SymbolSpec, opt *SymbolClientListOptions) ([]*AugmentedSymbolClient, *Response, error) {
	url, err := s.client.url(api_router.SymbolClients, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var clients []*AugmentedSymbolClient
	resp, err := s.client.Do(req, &clients)
	if err != nil {
		return nil, resp, err
	}

	return clients, resp, nil
}

type AugmentedRepoRef struct {
	Repo  *repo.Repository
	Count int
}

type SymbolDependentRepositoryListOptions struct {
	ListOptions
}

func (s *symbolsService) ListDependentRepositories(symbol SymbolSpec, opt *SymbolDependentRepositoryListOptions) ([]*AugmentedRepoRef, *Response, error) {
	url, err := s.client.url(api_router.SymbolDependents, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var dependents []*AugmentedRepoRef
	resp, err := s.client.Do(req, &dependents)
	if err != nil {
		return nil, resp, err
	}

	return dependents, resp, nil
}

type SymbolListImplementationsOptions struct {
	ListOptions
}

func (s *symbolsService) ListImplementations(symbol SymbolSpec, opt *SymbolListImplementationsOptions) ([]*Symbol, *Response, error) {
	url, err := s.client.url(api_router.SymbolImplementations, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var symbols []*Symbol
	resp, err := s.client.Do(req, &symbols)
	if err != nil {
		return nil, resp, err
	}

	return symbols, resp, nil
}

type SymbolListInterfacesOptions struct {
	ListOptions
}

func (s *symbolsService) ListInterfaces(symbol SymbolSpec, opt *SymbolListInterfacesOptions) ([]*Symbol, *Response, error) {
	url, err := s.client.url(api_router.SymbolInterfaces, map[string]string{"RepoURI": symbol.Repo, "UnitType": symbol.UnitType, "Unit": symbol.Unit, "Path": symbol.Path}, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var symbols []*Symbol
	resp, err := s.client.Do(req, &symbols)
	if err != nil {
		return nil, resp, err
	}

	return symbols, resp, nil
}

func (s *symbolsService) CountByRepository(repo RepositorySpec) (*graph.SymbolCounts, *Response, error) {
	url, err := s.client.url(api_router.RepositorySymbolCounts, map[string]string{"RepoURI": repo.URI}, nil)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", url.String(), nil)
	if err != nil {
		return nil, nil, err
	}

	var counts *graph.SymbolCounts
	resp, err := s.client.Do(req, &counts)
	if err != nil {
		return nil, resp, err
	}

	return counts, resp, nil
}

type MockSymbolsService struct {
	Get_                       func(symbol SymbolSpec, opt *GetSymbolOptions) (*Symbol, *Response, error)
	List_                      func(opt *SymbolListOptions) ([]*Symbol, *Response, error)
	ListExamples_              func(symbol SymbolSpec, opt *SymbolExampleListOptions) ([]*Example, *Response, error)
	ListAuthors_               func(symbol SymbolSpec, opt *SymbolAuthorListOptions) ([]*AugmentedSymbolAuthor, *Response, error)
	ListClients_               func(symbol SymbolSpec, opt *SymbolClientListOptions) ([]*AugmentedSymbolClient, *Response, error)
	ListDependentRepositories_ func(symbol SymbolSpec, opt *SymbolDependentRepositoryListOptions) ([]*AugmentedRepoRef, *Response, error)
	ListImplementations_       func(symbol SymbolSpec, opt *SymbolListImplementationsOptions) ([]*Symbol, *Response, error)
	ListInterfaces_            func(symbol SymbolSpec, opt *SymbolListInterfacesOptions) ([]*Symbol, *Response, error)
	CountByRepository_         func(repo RepositorySpec) (*graph.SymbolCounts, *Response, error)
}

var _ SymbolsService = MockSymbolsService{}

func (s MockSymbolsService) Get(symbol SymbolSpec, opt *GetSymbolOptions) (*Symbol, *Response, error) {
	if s.Get_ == nil {
		return nil, &Response{}, nil
	}
	return s.Get_(symbol, opt)
}

func (s MockSymbolsService) List(opt *SymbolListOptions) ([]*Symbol, *Response, error) {
	if s.List_ == nil {
		return nil, &Response{}, nil
	}
	return s.List_(opt)
}

func (s MockSymbolsService) ListExamples(symbol SymbolSpec, opt *SymbolExampleListOptions) ([]*Example, *Response, error) {
	if s.ListExamples_ == nil {
		return nil, &Response{}, nil
	}
	return s.ListExamples_(symbol, opt)
}

func (s MockSymbolsService) ListAuthors(symbol SymbolSpec, opt *SymbolAuthorListOptions) ([]*AugmentedSymbolAuthor, *Response, error) {
	if s.ListAuthors_ == nil {
		return nil, &Response{}, nil
	}
	return s.ListAuthors_(symbol, opt)
}

func (s MockSymbolsService) ListClients(symbol SymbolSpec, opt *SymbolClientListOptions) ([]*AugmentedSymbolClient, *Response, error) {
	if s.ListClients_ == nil {
		return nil, &Response{}, nil
	}
	return s.ListClients_(symbol, opt)
}

func (s MockSymbolsService) ListDependentRepositories(symbol SymbolSpec, opt *SymbolDependentRepositoryListOptions) ([]*AugmentedRepoRef, *Response, error) {
	if s.ListDependentRepositories_ == nil {
		return nil, &Response{}, nil
	}
	return s.ListDependentRepositories_(symbol, opt)
}

func (s MockSymbolsService) ListImplementations(symbol SymbolSpec, opt *SymbolListImplementationsOptions) ([]*Symbol, *Response, error) {
	if s.ListImplementations_ == nil {
		return nil, &Response{}, nil
	}
	return s.ListImplementations_(symbol, opt)
}

func (s MockSymbolsService) ListInterfaces(symbol SymbolSpec, opt *SymbolListInterfacesOptions) ([]*Symbol, *Response, error) {
	if s.ListInterfaces_ == nil {
		return nil, &Response{}, nil
	}
	return s.ListInterfaces_(symbol, opt)
}

func (s MockSymbolsService) CountByRepository(repo RepositorySpec) (*graph.SymbolCounts, *Response, error) {
	if s.CountByRepository_ == nil {
		return &graph.SymbolCounts{}, &Response{}, nil
	}
	return s.CountByRepository_(repo)
}
<|endoftext|>


<commit_before>// Package igc implements an IGC parser.
package igc

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"regexp"
	"strings"
	"time"

	"github.com/twpayne/go-geom"
)

var (
	// ErrInvalidCharacter is returned when an invalid character is encountered.
	ErrInvalidCharacter = errors.New("invalid character")
	// ErrInvalidCharactersBeforeARecord is returned when invalid characters are encountered before the A record.
	ErrInvalidCharactersBeforeARecord = errors.New("invalid characters before A record")
	// ErrInvalidBRecord is returned when an invalid B record is encountered.
	ErrInvalidBRecord = errors.New("invalid B record")
	// ErrInvalidHRecord is returned when an invalid H record is encountered.
	ErrInvalidHRecord = errors.New("invalid H record")
	// ErrInvalidIRecord is returned when an invalid I record is encountered.
	ErrInvalidIRecord = errors.New("invalid I record")
	// ErrEmptyLine is returned when an empty line is encountered.
	ErrEmptyLine = errors.New("empty line")
	// ErrMissingARecord is returned when no A record is found.
	ErrMissingARecord = errors.New("missing A record")
	// ErrOutOfRange is returned when a value is out of range.
	ErrOutOfRange = errors.New("out of range")

	hRegexp = regexp.MustCompile(`H(.)([A-Z0-9]{3})(.*?:)?(.*?)\s*\z`)
)

// An Errors is a map of errors encountered at each line.
type Errors map[int]error

// A Header is an IGC header.
type Header struct {
	Source   string
	Key      string
	KeyExtra string
	Value    string
}

// A T represents a parsed IGC file.
type T struct {
	Headers    []Header
	LineString *geom.LineString
}

func (es Errors) Error() string {
	var ss []string
	for lineno, e := range es {
		ss = append(ss, fmt.Sprintf("%d: %s", lineno, e.Error()))
	}
	return strings.Join(ss, "\n")
}

// parseDec parses a decimal value in s[start:stop].
func parseDec(s string, start, stop int) (int, error) {
	result := 0
	neg := false
	if s[start] == '-' {
		neg = true
		start++
	}
	for i := start; i < stop; i++ {
		if c := s[i]; '0' <= c && c <= '9' {
			result = 10*result + int(c) - '0'
		} else {
			return 0, ErrInvalidCharacter
		}
	}
	if neg {
		result = -result
	}
	return result, nil
}

// parseDecInRange parsers a decimal value in s[start:stop], and returns an
// error if it is outside the range [min, max).
func parseDecInRange(s string, start, stop, min, max int) (int, error) {
	if result, err := parseDec(s, start, stop); err != nil {
		return result, err
	} else if result < min || max <= result {
		return result, ErrOutOfRange
	} else {
		return result, nil
	}
}

// parser contains the state of a parser.
type parser struct {
	headers           []Header
	coords            []float64
	year, month, day  int
	startAt           time.Time
	lastDate          time.Time
	ladStart, ladStop int
	lodStart, lodStop int
	tdsStart, tdsStop int
	bRecordLen        int
}

// newParser creates a new parser.
func newParser() *parser {
	return &parser{bRecordLen: 35}
}

// parseB parses a B record from line and updates the state of p.
func (p *parser) parseB(line string) error {

	if len(line) != p.bRecordLen {
		return ErrInvalidBRecord
	}

	var err error

	var hour, minute, second, nsec int
	if hour, err = parseDecInRange(line, 1, 3, 0, 24); err != nil {
		return err
	}
	if minute, err = parseDecInRange(line, 3, 5, 0, 60); err != nil {
		return err
	}
	if second, err = parseDecInRange(line, 5, 7, 0, 60); err != nil {
		return err
	}
	if p.tdsStart != 0 {
		var decisecond int
		decisecond, err = parseDecInRange(line, p.tdsStart, p.tdsStop, 0, 10)
		if err != nil {
			return err
		}
		nsec = decisecond * 1e8
	}
	date := time.Date(p.year, time.Month(p.month), p.day, hour, minute, second, nsec, time.UTC)
	if date.Before(p.lastDate) {
		p.day++
		date = time.Date(p.year, time.Month(p.month), p.day, hour, minute, second, nsec, time.UTC)
	}

	if p.startAt.IsZero() {
		p.startAt = date
	}

	var latDeg, latMilliMin int
	if latDeg, err = parseDecInRange(line, 7, 9, 0, 90); err != nil {
		return err
	}
	// special case: latMilliMin should be in the range [0, 60000) but a number of flight recorders generate latMilliMins of 60000
	// FIXME check what happens in negative (S, W) hemispheres
	if latMilliMin, err = parseDecInRange(line, 9, 14, 0, 60000+1); err != nil {
		return err
	}
	lat := float64(60000*latDeg+latMilliMin) / 60000.
	if p.ladStart != 0 {
		var lad int
		if lad, err = parseDec(line, p.ladStart, p.ladStop); err == nil {
			lat += float64(lad) / 6000000.
		} else {
			return err
		}
	}
	switch c := line[14]; c {
	case 'N':
	case 'S':
		lat = -lat
	default:
		return ErrInvalidCharacter
	}

	var lngDeg, lngMilliMin int
	if lngDeg, err = parseDecInRange(line, 15, 18, 0, 180); err != nil {
		return err
	}
	if lngMilliMin, err = parseDecInRange(line, 18, 23, 0, 60000+1); err != nil {
		return err
	}
	lng := float64(60000*lngDeg+lngMilliMin) / 60000.
	if p.lodStart != 0 {
		var lod int
		if lod, err = parseDec(line, p.lodStart, p.lodStop); err == nil {
			lng += float64(lod) / 6000000.
		} else {
			return err
		}
	}
	switch c := line[23]; c {
	case 'E':
	case 'W':
		lng = -lng
	default:
		return ErrInvalidCharacter
	}

	var pressureAlt, ellipsoidAlt int
	if pressureAlt, err = parseDec(line, 25, 30); err != nil {
		return err
	}
	if ellipsoidAlt, err = parseDec(line, 30, 35); err != nil {
		return err
	}

	p.coords = append(p.coords, lng, lat, float64(ellipsoidAlt), float64(date.UnixNano())/1e9, float64(pressureAlt))
	p.lastDate = date

	return nil

}

// parseB parses an H record from line and updates the state of p.
func (p *parser) parseH(line string) error {
	m := hRegexp.FindStringSubmatch(line)
	if m == nil {
		return ErrInvalidHRecord
	}
	header := Header{
		Source:   m[1],
		Key:      m[2],
		KeyExtra: strings.TrimSuffix(m[3], ":"),
		Value:    m[4],
	}
	p.headers = append(p.headers, header)
	if header.Key == "DTE" {
		if len(header.Value) < 6 {
			return ErrInvalidHRecord
		}
		day, err := parseDecInRange(header.Value, 0, 2, 1, 31+1)
		if err != nil {
			return err
		}
		month, err := parseDecInRange(header.Value, 2, 4, 1, 12+1)
		if err != nil {
			return err
		}
		year, err := parseDec(header.Value, 4, 6)
		if err != nil {
			return err
		}
		p.day = day
		p.month = month
		if year < 70 {
			p.year = 2000 + year
		} else {
			p.year = 1970 + year
		}
	}
	return nil
}

// parseB parses an I record from line and updates the state of p.
func (p *parser) parseI(line string) error {
	var err error
	var n int
	if len(line) < 3 {
		return ErrInvalidIRecord
	}
	if n, err = parseDec(line, 1, 3); err != nil {
		return err
	}
	if len(line) < 7*n+3 {
		return ErrInvalidIRecord
	}
	for i := 0; i < n; i++ {
		var start, stop int
		if start, err = parseDec(line, 7*i+3, 7*i+5); err != nil {
			return err
		}
		if stop, err = parseDec(line, 7*i+5, 7*i+7); err != nil {
			return err
		}
		if start != p.bRecordLen+1 || stop < start {
			return ErrInvalidIRecord
		}
		p.bRecordLen = stop
		switch line[7*i+7 : 7*i+10] {
		case "LAD":
			p.ladStart, p.ladStop = start-1, stop
		case "LOD":
			p.lodStart, p.lodStop = start-1, stop
		case "TDS":
			p.tdsStart, p.tdsStop = start-1, stop
		}
	}
	return nil
}

// parseLine parses a single record from line and updates the state of p.
func (p *parser) parseLine(line string) error {
	switch line[0] {
	case 'B':
		return p.parseB(line)
	case 'H':
		return p.parseH(line)
	case 'I':
		return p.parseI(line)
	default:
		return nil
	}
}

// doParse reads r, parsers all the records it finds, updating the state of p.
func doParse(r io.Reader) (*parser, Errors) {
	errors := make(Errors)
	p := newParser()
	s := bufio.NewScanner(r)
	foundA := false
	leadingNoise := false
	for lineno := 1; s.Scan(); lineno++ {
		line := s.Text()
		if len(line) == 0 {
			// errors[lineno] = ErrEmptyLine
		} else if foundA {
			if err := p.parseLine(line); err != nil {
				errors[lineno] = err
			}
		} else {
			if c := line[0]; c == 'A' {
				foundA = true
			} else if 'A' <= c && c <= 'Z' {
				// All records that start with an uppercase character must be valid.
				leadingNoise = true
				continue
			} else if i := strings.IndexRune(line, 'A'); i != -1 {
				// Strip any leading noise.
				// The noise must include at least one unprintable character (like XOFF or a fragment of a Unicode BOM).
				for _, c := range line[:i] {
					if !(c == ' ' || ('A' <= c && c <= 'Z')) {
						foundA = true
						leadingNoise = true
						break
					}
				}
			}
		}
	}
	if !foundA {
		errors[1] = ErrMissingARecord
	} else if leadingNoise {
		errors[1] = ErrInvalidCharactersBeforeARecord
	}
	return p, errors
}

// Read reads a igc.T from r, which should contain IGC records.
func Read(r io.Reader) (*T, error) {
	p, errors := doParse(r)
	if len(errors) != 0 {
		return nil, errors
	}
	return &T{
		Headers:    p.headers,
		LineString: geom.NewLineStringFlat(geom.Layout(5), p.coords),
	}, nil
}
<commit_msg>Ignore trailing carriage returns<commit_after>// Package igc implements an IGC parser.
package igc

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"regexp"
	"strings"
	"time"

	"github.com/twpayne/go-geom"
)

var (
	// ErrInvalidCharacter is returned when an invalid character is encountered.
	ErrInvalidCharacter = errors.New("invalid character")
	// ErrInvalidCharactersBeforeARecord is returned when invalid characters are encountered before the A record.
	ErrInvalidCharactersBeforeARecord = errors.New("invalid characters before A record")
	// ErrInvalidBRecord is returned when an invalid B record is encountered.
	ErrInvalidBRecord = errors.New("invalid B record")
	// ErrInvalidHRecord is returned when an invalid H record is encountered.
	ErrInvalidHRecord = errors.New("invalid H record")
	// ErrInvalidIRecord is returned when an invalid I record is encountered.
	ErrInvalidIRecord = errors.New("invalid I record")
	// ErrEmptyLine is returned when an empty line is encountered.
	ErrEmptyLine = errors.New("empty line")
	// ErrMissingARecord is returned when no A record is found.
	ErrMissingARecord = errors.New("missing A record")
	// ErrOutOfRange is returned when a value is out of range.
	ErrOutOfRange = errors.New("out of range")

	hRegexp = regexp.MustCompile(`H(.)([A-Z0-9]{3})(.*?:)?(.*?)\s*\z`)
)

// An Errors is a map of errors encountered at each line.
type Errors map[int]error

// A Header is an IGC header.
type Header struct {
	Source   string
	Key      string
	KeyExtra string
	Value    string
}

// A T represents a parsed IGC file.
type T struct {
	Headers    []Header
	LineString *geom.LineString
}

func (es Errors) Error() string {
	var ss []string
	for lineno, e := range es {
		ss = append(ss, fmt.Sprintf("%d: %s", lineno, e.Error()))
	}
	return strings.Join(ss, "\n")
}

// parseDec parses a decimal value in s[start:stop].
func parseDec(s string, start, stop int) (int, error) {
	result := 0
	neg := false
	if s[start] == '-' {
		neg = true
		start++
	}
	for i := start; i < stop; i++ {
		if c := s[i]; '0' <= c && c <= '9' {
			result = 10*result + int(c) - '0'
		} else {
			return 0, ErrInvalidCharacter
		}
	}
	if neg {
		result = -result
	}
	return result, nil
}

// parseDecInRange parsers a decimal value in s[start:stop], and returns an
// error if it is outside the range [min, max).
func parseDecInRange(s string, start, stop, min, max int) (int, error) {
	if result, err := parseDec(s, start, stop); err != nil {
		return result, err
	} else if result < min || max <= result {
		return result, ErrOutOfRange
	} else {
		return result, nil
	}
}

// parser contains the state of a parser.
type parser struct {
	headers           []Header
	coords            []float64
	year, month, day  int
	startAt           time.Time
	lastDate          time.Time
	ladStart, ladStop int
	lodStart, lodStop int
	tdsStart, tdsStop int
	bRecordLen        int
}

// newParser creates a new parser.
func newParser() *parser {
	return &parser{bRecordLen: 35}
}

// parseB parses a B record from line and updates the state of p.
func (p *parser) parseB(line string) error {

	if len(line) != p.bRecordLen {
		return ErrInvalidBRecord
	}

	var err error

	var hour, minute, second, nsec int
	if hour, err = parseDecInRange(line, 1, 3, 0, 24); err != nil {
		return err
	}
	if minute, err = parseDecInRange(line, 3, 5, 0, 60); err != nil {
		return err
	}
	if second, err = parseDecInRange(line, 5, 7, 0, 60); err != nil {
		return err
	}
	if p.tdsStart != 0 {
		var decisecond int
		decisecond, err = parseDecInRange(line, p.tdsStart, p.tdsStop, 0, 10)
		if err != nil {
			return err
		}
		nsec = decisecond * 1e8
	}
	date := time.Date(p.year, time.Month(p.month), p.day, hour, minute, second, nsec, time.UTC)
	if date.Before(p.lastDate) {
		p.day++
		date = time.Date(p.year, time.Month(p.month), p.day, hour, minute, second, nsec, time.UTC)
	}

	if p.startAt.IsZero() {
		p.startAt = date
	}

	var latDeg, latMilliMin int
	if latDeg, err = parseDecInRange(line, 7, 9, 0, 90); err != nil {
		return err
	}
	// special case: latMilliMin should be in the range [0, 60000) but a number of flight recorders generate latMilliMins of 60000
	// FIXME check what happens in negative (S, W) hemispheres
	if latMilliMin, err = parseDecInRange(line, 9, 14, 0, 60000+1); err != nil {
		return err
	}
	lat := float64(60000*latDeg+latMilliMin) / 60000.
	if p.ladStart != 0 {
		var lad int
		if lad, err = parseDec(line, p.ladStart, p.ladStop); err == nil {
			lat += float64(lad) / 6000000.
		} else {
			return err
		}
	}
	switch c := line[14]; c {
	case 'N':
	case 'S':
		lat = -lat
	default:
		return ErrInvalidCharacter
	}

	var lngDeg, lngMilliMin int
	if lngDeg, err = parseDecInRange(line, 15, 18, 0, 180); err != nil {
		return err
	}
	if lngMilliMin, err = parseDecInRange(line, 18, 23, 0, 60000+1); err != nil {
		return err
	}
	lng := float64(60000*lngDeg+lngMilliMin) / 60000.
	if p.lodStart != 0 {
		var lod int
		if lod, err = parseDec(line, p.lodStart, p.lodStop); err == nil {
			lng += float64(lod) / 6000000.
		} else {
			return err
		}
	}
	switch c := line[23]; c {
	case 'E':
	case 'W':
		lng = -lng
	default:
		return ErrInvalidCharacter
	}

	var pressureAlt, ellipsoidAlt int
	if pressureAlt, err = parseDec(line, 25, 30); err != nil {
		return err
	}
	if ellipsoidAlt, err = parseDec(line, 30, 35); err != nil {
		return err
	}

	p.coords = append(p.coords, lng, lat, float64(ellipsoidAlt), float64(date.UnixNano())/1e9, float64(pressureAlt))
	p.lastDate = date

	return nil

}

// parseB parses an H record from line and updates the state of p.
func (p *parser) parseH(line string) error {
	m := hRegexp.FindStringSubmatch(line)
	if m == nil {
		return ErrInvalidHRecord
	}
	header := Header{
		Source:   m[1],
		Key:      m[2],
		KeyExtra: strings.TrimSuffix(m[3], ":"),
		Value:    m[4],
	}
	p.headers = append(p.headers, header)
	if header.Key == "DTE" {
		if len(header.Value) < 6 {
			return ErrInvalidHRecord
		}
		day, err := parseDecInRange(header.Value, 0, 2, 1, 31+1)
		if err != nil {
			return err
		}
		month, err := parseDecInRange(header.Value, 2, 4, 1, 12+1)
		if err != nil {
			return err
		}
		year, err := parseDec(header.Value, 4, 6)
		if err != nil {
			return err
		}
		p.day = day
		p.month = month
		if year < 70 {
			p.year = 2000 + year
		} else {
			p.year = 1970 + year
		}
	}
	return nil
}

// parseB parses an I record from line and updates the state of p.
func (p *parser) parseI(line string) error {
	var err error
	var n int
	if len(line) < 3 {
		return ErrInvalidIRecord
	}
	if n, err = parseDec(line, 1, 3); err != nil {
		return err
	}
	if len(line) < 7*n+3 {
		return ErrInvalidIRecord
	}
	for i := 0; i < n; i++ {
		var start, stop int
		if start, err = parseDec(line, 7*i+3, 7*i+5); err != nil {
			return err
		}
		if stop, err = parseDec(line, 7*i+5, 7*i+7); err != nil {
			return err
		}
		if start != p.bRecordLen+1 || stop < start {
			return ErrInvalidIRecord
		}
		p.bRecordLen = stop
		switch line[7*i+7 : 7*i+10] {
		case "LAD":
			p.ladStart, p.ladStop = start-1, stop
		case "LOD":
			p.lodStart, p.lodStop = start-1, stop
		case "TDS":
			p.tdsStart, p.tdsStop = start-1, stop
		}
	}
	return nil
}

// parseLine parses a single record from line and updates the state of p.
func (p *parser) parseLine(line string) error {
	switch line[0] {
	case 'B':
		return p.parseB(line)
	case 'H':
		return p.parseH(line)
	case 'I':
		return p.parseI(line)
	default:
		return nil
	}
}

// doParse reads r, parsers all the records it finds, updating the state of p.
func doParse(r io.Reader) (*parser, Errors) {
	errors := make(Errors)
	p := newParser()
	s := bufio.NewScanner(r)
	foundA := false
	leadingNoise := false
	for lineno := 1; s.Scan(); lineno++ {
		line := strings.TrimSuffix(s.Text(), "\r")
		if len(line) == 0 {
			// errors[lineno] = ErrEmptyLine
		} else if foundA {
			if err := p.parseLine(line); err != nil {
				errors[lineno] = err
			}
		} else {
			if c := line[0]; c == 'A' {
				foundA = true
			} else if 'A' <= c && c <= 'Z' {
				// All records that start with an uppercase character must be valid.
				leadingNoise = true
				continue
			} else if i := strings.IndexRune(line, 'A'); i != -1 {
				// Strip any leading noise.
				// The noise must include at least one unprintable character (like XOFF or a fragment of a Unicode BOM).
				for _, c := range line[:i] {
					if !(c == ' ' || ('A' <= c && c <= 'Z')) {
						foundA = true
						leadingNoise = true
						break
					}
				}
			}
		}
	}
	if !foundA {
		errors[1] = ErrMissingARecord
	} else if leadingNoise {
		errors[1] = ErrInvalidCharactersBeforeARecord
	}
	return p, errors
}

// Read reads a igc.T from r, which should contain IGC records.
func Read(r io.Reader) (*T, error) {
	p, errors := doParse(r)
	if len(errors) != 0 {
		return nil, errors
	}
	return &T{
		Headers:    p.headers,
		LineString: geom.NewLineStringFlat(geom.Layout(5), p.coords),
	}, nil
}
<|endoftext|>


<commit_before>package api

import (
	"bytes"
	"context"
	"fmt"
	"net/http"

	"github.com/fsamin/go-dump"
	"github.com/go-gorp/gorp"
	"github.com/gorilla/mux"

	"github.com/ovh/cds/engine/api/application"
	"github.com/ovh/cds/engine/api/environment"
	"github.com/ovh/cds/engine/api/permission"
	"github.com/ovh/cds/engine/api/pipeline"
	"github.com/ovh/cds/engine/api/project"
	"github.com/ovh/cds/engine/api/services"
	"github.com/ovh/cds/engine/api/workflow"
	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/log"
)

// getWorkflowsHandler returns ID and name of workflows for a given project/user
func (api *API) getWorkflowsHandler() Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars["permProjectKey"]

		ws, err := workflow.LoadAll(api.mustDB(), key)
		if err != nil {
			return err
		}

		return WriteJSON(w, ws, http.StatusOK)
	}
}

// getWorkflowHandler returns a full workflow
func (api *API) getWorkflowHandler() Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars["key"]
		name := vars["permWorkflowName"]
		withUsage := FormBool(r, "withUsage")

		w1, err := workflow.Load(api.mustDB(), api.Cache, key, name, getUser(ctx), workflow.LoadOptions{})
		if err != nil {
			return sdk.WrapError(err, "getWorkflowHandler> Cannot load workflow %s", name)
		}

		if withUsage {
			usage, errU := loadWorkflowUsage(api.mustDB(), w1.ID)
			if errU != nil {
				return sdk.WrapError(errU, "getWorkflowHandler> Cannot load usage for workflow %s", name)
			}
			w1.Usage = &usage
		}

		w1.Permission = permission.WorkflowPermission(key, w1.Name, getUser(ctx))

		//We filter project and workflow configurtaion key, because they are always set on insertHooks
		w1.FilterHooksConfig(sdk.HookConfigProject, sdk.HookConfigWorkflow)

		return WriteJSON(w, w1, http.StatusOK)
	}
}

func loadWorkflowUsage(db gorp.SqlExecutor, workflowID int64) (sdk.Usage, error) {
	usage := sdk.Usage{}
	pips, errP := pipeline.LoadByWorkflowID(db, workflowID)
	if errP != nil {
		return usage, sdk.WrapError(errP, "loadWorkflowUsage> Cannot load pipelines linked to a workflow id %d", workflowID)
	}
	usage.Pipelines = pips

	envs, errE := environment.LoadByWorkflowID(db, workflowID)
	if errE != nil {
		return usage, sdk.WrapError(errE, "loadWorkflowUsage> Cannot load environments linked to a workflow id %d", workflowID)
	}
	usage.Environments = envs

	apps, errA := application.LoadByWorkflowID(db, workflowID)
	if errA != nil {
		return usage, sdk.WrapError(errA, "loadWorkflowUsage> Cannot load applications linked to a workflow id %d", workflowID)
	}
	usage.Applications = apps

	return usage, nil
}

// postWorkflowHandler creates a new workflow
func (api *API) postWorkflowHandler() Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars["permProjectKey"]

		p, errP := project.Load(api.mustDB(), api.Cache, key, getUser(ctx), project.LoadOptions.WithApplications, project.LoadOptions.WithPipelines, project.LoadOptions.WithEnvironments, project.LoadOptions.WithGroups)
		if errP != nil {
			return sdk.WrapError(errP, "Cannot load Project %s", key)
		}
		var wf sdk.Workflow
		if err := UnmarshalBody(r, &wf); err != nil {
			return sdk.WrapError(err, "Cannot read body")
		}
		wf.ProjectID = p.ID
		wf.ProjectKey = key

		tx, errT := api.mustDB().Begin()
		if errT != nil {
			return sdk.WrapError(errT, "Cannot start transaction")
		}
		defer tx.Rollback()

		defaultPayload, errHr := workflow.HookRegistration(tx, api.Cache, nil, wf, p)
		if errHr != nil {
			return sdk.WrapError(errHr, "postWorkflowHandler")
		}
		if defaultPayload != nil && isDefaultPayloadEmpty(wf) {
			wf.Root.Context.DefaultPayload = *defaultPayload
		}

		if err := workflow.Insert(tx, api.Cache, &wf, p, getUser(ctx)); err != nil {
			return sdk.WrapError(err, "Cannot insert workflow")
		}

		// Add group
		for _, gp := range p.ProjectGroups {
			if gp.Permission == permission.PermissionReadWriteExecute {
				if err := workflow.AddGroup(tx, &wf, gp); err != nil {
					return sdk.WrapError(err, "Cannot add group %s", gp.Group.Name)
				}
			}
		}

		if err := project.UpdateLastModified(tx, api.Cache, getUser(ctx), p, sdk.ProjectWorkflowLastModificationType); err != nil {
			return sdk.WrapError(err, "Cannot update project last modified date")
		}

		if err := project.UpdateLastModified(tx, api.Cache, getUser(ctx), p, sdk.ProjectWorkflowLastModificationType); err != nil {
			return sdk.WrapError(err, "postWorkflowHandler> Cannot update project workflows last modified")
		}

		if err := tx.Commit(); err != nil {
			return sdk.WrapError(err, "Cannot commit transaction")
		}

		wf1, errl := workflow.LoadByID(api.mustDB(), api.Cache, wf.ID, getUser(ctx), workflow.LoadOptions{})
		if errl != nil {
			return sdk.WrapError(errl, "Cannot load workflow")
		}

		//We filter project and workflow configurtaion key, because they are always set on insertHooks
		wf1.FilterHooksConfig(sdk.HookConfigProject, sdk.HookConfigWorkflow)

		return WriteJSON(w, wf1, http.StatusCreated)
	}
}

// putWorkflowHandler updates a workflow
func (api *API) putWorkflowHandler() Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars["key"]
		name := vars["permWorkflowName"]

		p, errP := project.Load(api.mustDB(), api.Cache, key, getUser(ctx), project.LoadOptions.WithApplications, project.LoadOptions.WithPipelines, project.LoadOptions.WithEnvironments)
		if errP != nil {
			return sdk.WrapError(errP, "putWorkflowHandler> Cannot load Project %s", key)
		}

		oldW, errW := workflow.Load(api.mustDB(), api.Cache, key, name, getUser(ctx), workflow.LoadOptions{})
		if errW != nil {
			return sdk.WrapError(errW, "putWorkflowHandler> Cannot load Workflow %s", key)
		}

		var wf sdk.Workflow
		if err := UnmarshalBody(r, &wf); err != nil {
			return sdk.WrapError(err, "Cannot read body")
		}
		wf.ID = oldW.ID
		wf.RootID = oldW.RootID
		wf.Root.ID = oldW.RootID
		wf.ProjectID = p.ID
		wf.ProjectKey = key

		tx, errT := api.mustDB().Begin()
		if errT != nil {
			return sdk.WrapError(errT, "putWorkflowHandler> Cannot start transaction")
		}
		defer tx.Rollback()

		if err := workflow.Update(tx, api.Cache, &wf, oldW, p, getUser(ctx)); err != nil {
			return sdk.WrapError(err, "putWorkflowHandler> Cannot update workflow")
		}

		// HookRegistration after workflow.Update.  It needs hooks to be created on DB
		defaultPayload, errHr := workflow.HookRegistration(tx, api.Cache, oldW, wf, p)
		if errHr != nil {
			return sdk.WrapError(errHr, "putWorkflowHandler")
		}

		if defaultPayload != nil && isDefaultPayloadEmpty(wf) {
			wf.Root.Context.DefaultPayload = *defaultPayload
			if err := workflow.UpdateNodeContext(tx, wf.Root.Context); err != nil {
				return sdk.WrapError(err, "putWorkflowHandler> updateNodeContext")
			}
		}

		if err := workflow.UpdateLastModifiedDate(tx, api.Cache, getUser(ctx), p.Key, oldW); err != nil {
			return sdk.WrapError(err, "putWorkflowHandler> Cannot update last modified date for workflow")
		}

		if oldW.Name != wf.Name {
			if err := project.UpdateLastModified(tx, api.Cache, getUser(ctx), p, sdk.ProjectWorkflowLastModificationType); err != nil {
				return sdk.WrapError(err, "putWorkflowHandler> Cannot update project last modified date")
			}
		}

		if err := tx.Commit(); err != nil {
			return sdk.WrapError(err, "putWorkflowHandler> Cannot commit transaction")
		}

		wf1, errl := workflow.LoadByID(api.mustDB(), api.Cache, wf.ID, getUser(ctx), workflow.LoadOptions{})
		if errl != nil {
			return sdk.WrapError(errl, "putWorkflowHandler> Cannot load workflow")
		}

		usage, errU := loadWorkflowUsage(api.mustDB(), wf1.ID)
		if errU != nil {
			return sdk.WrapError(errU, "Cannot load usage")
		}
		wf1.Usage = &usage

		//We filter project and workflow configuration key, because they are always set on insertHooks
		wf1.FilterHooksConfig(sdk.HookConfigProject, sdk.HookConfigWorkflow)

		return WriteJSON(w, wf1, http.StatusOK)
	}
}

func isDefaultPayloadEmpty(wf sdk.Workflow) bool {
	e := dump.NewDefaultEncoder(new(bytes.Buffer))
	e.Formatters = []dump.KeyFormatterFunc{dump.WithDefaultLowerCaseFormatter()}
	e.ExtraFields.DetailedMap = false
	e.ExtraFields.DetailedStruct = false
	e.ExtraFields.Len = false
	e.ExtraFields.Type = false
	m, err := e.ToStringMap(wf.Root.Context.DefaultPayload)
	if err != nil {
		log.Warning("isDefaultPayloadEmpty>error while dump wf.Root.Context.DefaultPayload")
	}
	return len(m) == 0 // if empty, return true
}

// putWorkflowHandler deletes a workflow
func (api *API) deleteWorkflowHandler() Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars["key"]
		name := vars["permWorkflowName"]

		p, errP := project.Load(api.mustDB(), api.Cache, key, getUser(ctx))
		if errP != nil {
			return sdk.WrapError(errP, "Cannot load Project %s", key)
		}

		oldW, errW := workflow.Load(api.mustDB(), api.Cache, key, name, getUser(ctx), workflow.LoadOptions{})
		if errW != nil {
			return sdk.WrapError(errW, "Cannot load Workflow %s", key)
		}

		tx, errT := api.mustDB().Begin()
		if errT != nil {
			return sdk.WrapError(errT, "Cannot start transaction")
		}
		defer tx.Rollback()

		if err := workflow.Delete(tx, api.Cache, p, oldW, getUser(ctx)); err != nil {
			return sdk.WrapError(err, "Cannot delete workflow")
		}

		if err := project.UpdateLastModified(tx, api.Cache, getUser(ctx), p, sdk.ProjectWorkflowLastModificationType); err != nil {
			return sdk.WrapError(err, "Cannot update project last modified date")
		}

		if err := tx.Commit(); err != nil {
			return sdk.WrapError(errT, "Cannot commit transaction")
		}
		return WriteJSON(w, nil, http.StatusOK)
	}
}

func (api *API) getWorkflowHookHandler() Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars["key"]
		name := vars["permWorkflowName"]
		uuid := vars["uuid"]

		wf, errW := workflow.Load(api.mustDB(), api.Cache, key, name, getUser(ctx), workflow.LoadOptions{})
		if errW != nil {
			return sdk.WrapError(errW, "getWorkflowHookHandler> Cannot load Workflow %s/%s", key, name)
		}

		whooks := wf.GetHooks()
		_, has := whooks[uuid]
		if !has {
			return sdk.WrapError(sdk.ErrNotFound, "getWorkflowHookHandler> Cannot load Workflow %s/%s hook %s", key, name, uuid)
		}

		//Push the hook to hooks µService
		dao := services.Querier(api.mustDB(), api.Cache)
		//Load service "hooks"
		srvs, errS := dao.FindByType("hooks")
		if errS != nil {
			return sdk.WrapError(errS, "getWorkflowHookHandler> Unable to load hooks services")
		}

		path := fmt.Sprintf("/task/%s/execution", uuid)
		task := sdk.Task{}
		if _, err := services.DoJSONRequest(srvs, "GET", path, nil, &task); err != nil {
			return sdk.WrapError(err, "getWorkflowHookHandler> Unable to get hook %s task and executions", uuid)
		}

		return WriteJSON(w, task, http.StatusOK)
	}
}
<commit_msg>fix (engine): default rights creation (#2376)<commit_after>package api

import (
	"bytes"
	"context"
	"fmt"
	"net/http"

	"github.com/fsamin/go-dump"
	"github.com/go-gorp/gorp"
	"github.com/gorilla/mux"

	"github.com/ovh/cds/engine/api/application"
	"github.com/ovh/cds/engine/api/environment"
	"github.com/ovh/cds/engine/api/permission"
	"github.com/ovh/cds/engine/api/pipeline"
	"github.com/ovh/cds/engine/api/project"
	"github.com/ovh/cds/engine/api/services"
	"github.com/ovh/cds/engine/api/workflow"
	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/log"
)

// getWorkflowsHandler returns ID and name of workflows for a given project/user
func (api *API) getWorkflowsHandler() Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars["permProjectKey"]

		ws, err := workflow.LoadAll(api.mustDB(), key)
		if err != nil {
			return err
		}

		return WriteJSON(w, ws, http.StatusOK)
	}
}

// getWorkflowHandler returns a full workflow
func (api *API) getWorkflowHandler() Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars["key"]
		name := vars["permWorkflowName"]
		withUsage := FormBool(r, "withUsage")

		w1, err := workflow.Load(api.mustDB(), api.Cache, key, name, getUser(ctx), workflow.LoadOptions{})
		if err != nil {
			return sdk.WrapError(err, "getWorkflowHandler> Cannot load workflow %s", name)
		}

		if withUsage {
			usage, errU := loadWorkflowUsage(api.mustDB(), w1.ID)
			if errU != nil {
				return sdk.WrapError(errU, "getWorkflowHandler> Cannot load usage for workflow %s", name)
			}
			w1.Usage = &usage
		}

		w1.Permission = permission.WorkflowPermission(key, w1.Name, getUser(ctx))

		//We filter project and workflow configurtaion key, because they are always set on insertHooks
		w1.FilterHooksConfig(sdk.HookConfigProject, sdk.HookConfigWorkflow)

		return WriteJSON(w, w1, http.StatusOK)
	}
}

func loadWorkflowUsage(db gorp.SqlExecutor, workflowID int64) (sdk.Usage, error) {
	usage := sdk.Usage{}
	pips, errP := pipeline.LoadByWorkflowID(db, workflowID)
	if errP != nil {
		return usage, sdk.WrapError(errP, "loadWorkflowUsage> Cannot load pipelines linked to a workflow id %d", workflowID)
	}
	usage.Pipelines = pips

	envs, errE := environment.LoadByWorkflowID(db, workflowID)
	if errE != nil {
		return usage, sdk.WrapError(errE, "loadWorkflowUsage> Cannot load environments linked to a workflow id %d", workflowID)
	}
	usage.Environments = envs

	apps, errA := application.LoadByWorkflowID(db, workflowID)
	if errA != nil {
		return usage, sdk.WrapError(errA, "loadWorkflowUsage> Cannot load applications linked to a workflow id %d", workflowID)
	}
	usage.Applications = apps

	return usage, nil
}

// postWorkflowHandler creates a new workflow
func (api *API) postWorkflowHandler() Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars["permProjectKey"]

		p, errP := project.Load(api.mustDB(), api.Cache, key, getUser(ctx), project.LoadOptions.WithApplications, project.LoadOptions.WithPipelines, project.LoadOptions.WithEnvironments, project.LoadOptions.WithGroups)
		if errP != nil {
			return sdk.WrapError(errP, "Cannot load Project %s", key)
		}
		var wf sdk.Workflow
		if err := UnmarshalBody(r, &wf); err != nil {
			return sdk.WrapError(err, "Cannot read body")
		}
		wf.ProjectID = p.ID
		wf.ProjectKey = key

		tx, errT := api.mustDB().Begin()
		if errT != nil {
			return sdk.WrapError(errT, "Cannot start transaction")
		}
		defer tx.Rollback()

		defaultPayload, errHr := workflow.HookRegistration(tx, api.Cache, nil, wf, p)
		if errHr != nil {
			return sdk.WrapError(errHr, "postWorkflowHandler")
		}
		if defaultPayload != nil && isDefaultPayloadEmpty(wf) {
			wf.Root.Context.DefaultPayload = *defaultPayload
		}

		if err := workflow.Insert(tx, api.Cache, &wf, p, getUser(ctx)); err != nil {
			return sdk.WrapError(err, "Cannot insert workflow")
		}

		// Add group
		for _, gp := range p.ProjectGroups {
			if gp.Permission >= permission.PermissionReadExecute {
				if err := workflow.AddGroup(tx, &wf, gp); err != nil {
					return sdk.WrapError(err, "Cannot add group %s", gp.Group.Name)
				}
			}
		}

		if err := project.UpdateLastModified(tx, api.Cache, getUser(ctx), p, sdk.ProjectWorkflowLastModificationType); err != nil {
			return sdk.WrapError(err, "Cannot update project last modified date")
		}

		if err := project.UpdateLastModified(tx, api.Cache, getUser(ctx), p, sdk.ProjectWorkflowLastModificationType); err != nil {
			return sdk.WrapError(err, "postWorkflowHandler> Cannot update project workflows last modified")
		}

		if err := tx.Commit(); err != nil {
			return sdk.WrapError(err, "Cannot commit transaction")
		}

		wf1, errl := workflow.LoadByID(api.mustDB(), api.Cache, wf.ID, getUser(ctx), workflow.LoadOptions{})
		if errl != nil {
			return sdk.WrapError(errl, "Cannot load workflow")
		}

		//We filter project and workflow configurtaion key, because they are always set on insertHooks
		wf1.FilterHooksConfig(sdk.HookConfigProject, sdk.HookConfigWorkflow)

		return WriteJSON(w, wf1, http.StatusCreated)
	}
}

// putWorkflowHandler updates a workflow
func (api *API) putWorkflowHandler() Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars["key"]
		name := vars["permWorkflowName"]

		p, errP := project.Load(api.mustDB(), api.Cache, key, getUser(ctx), project.LoadOptions.WithApplications, project.LoadOptions.WithPipelines, project.LoadOptions.WithEnvironments)
		if errP != nil {
			return sdk.WrapError(errP, "putWorkflowHandler> Cannot load Project %s", key)
		}

		oldW, errW := workflow.Load(api.mustDB(), api.Cache, key, name, getUser(ctx), workflow.LoadOptions{})
		if errW != nil {
			return sdk.WrapError(errW, "putWorkflowHandler> Cannot load Workflow %s", key)
		}

		var wf sdk.Workflow
		if err := UnmarshalBody(r, &wf); err != nil {
			return sdk.WrapError(err, "Cannot read body")
		}
		wf.ID = oldW.ID
		wf.RootID = oldW.RootID
		wf.Root.ID = oldW.RootID
		wf.ProjectID = p.ID
		wf.ProjectKey = key

		tx, errT := api.mustDB().Begin()
		if errT != nil {
			return sdk.WrapError(errT, "putWorkflowHandler> Cannot start transaction")
		}
		defer tx.Rollback()

		if err := workflow.Update(tx, api.Cache, &wf, oldW, p, getUser(ctx)); err != nil {
			return sdk.WrapError(err, "putWorkflowHandler> Cannot update workflow")
		}

		// HookRegistration after workflow.Update.  It needs hooks to be created on DB
		defaultPayload, errHr := workflow.HookRegistration(tx, api.Cache, oldW, wf, p)
		if errHr != nil {
			return sdk.WrapError(errHr, "putWorkflowHandler")
		}

		if defaultPayload != nil && isDefaultPayloadEmpty(wf) {
			wf.Root.Context.DefaultPayload = *defaultPayload
			if err := workflow.UpdateNodeContext(tx, wf.Root.Context); err != nil {
				return sdk.WrapError(err, "putWorkflowHandler> updateNodeContext")
			}
		}

		if err := workflow.UpdateLastModifiedDate(tx, api.Cache, getUser(ctx), p.Key, oldW); err != nil {
			return sdk.WrapError(err, "putWorkflowHandler> Cannot update last modified date for workflow")
		}

		if oldW.Name != wf.Name {
			if err := project.UpdateLastModified(tx, api.Cache, getUser(ctx), p, sdk.ProjectWorkflowLastModificationType); err != nil {
				return sdk.WrapError(err, "putWorkflowHandler> Cannot update project last modified date")
			}
		}

		if err := tx.Commit(); err != nil {
			return sdk.WrapError(err, "putWorkflowHandler> Cannot commit transaction")
		}

		wf1, errl := workflow.LoadByID(api.mustDB(), api.Cache, wf.ID, getUser(ctx), workflow.LoadOptions{})
		if errl != nil {
			return sdk.WrapError(errl, "putWorkflowHandler> Cannot load workflow")
		}

		usage, errU := loadWorkflowUsage(api.mustDB(), wf1.ID)
		if errU != nil {
			return sdk.WrapError(errU, "Cannot load usage")
		}
		wf1.Usage = &usage

		//We filter project and workflow configuration key, because they are always set on insertHooks
		wf1.FilterHooksConfig(sdk.HookConfigProject, sdk.HookConfigWorkflow)

		return WriteJSON(w, wf1, http.StatusOK)
	}
}

func isDefaultPayloadEmpty(wf sdk.Workflow) bool {
	e := dump.NewDefaultEncoder(new(bytes.Buffer))
	e.Formatters = []dump.KeyFormatterFunc{dump.WithDefaultLowerCaseFormatter()}
	e.ExtraFields.DetailedMap = false
	e.ExtraFields.DetailedStruct = false
	e.ExtraFields.Len = false
	e.ExtraFields.Type = false
	m, err := e.ToStringMap(wf.Root.Context.DefaultPayload)
	if err != nil {
		log.Warning("isDefaultPayloadEmpty>error while dump wf.Root.Context.DefaultPayload")
	}
	return len(m) == 0 // if empty, return true
}

// putWorkflowHandler deletes a workflow
func (api *API) deleteWorkflowHandler() Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars["key"]
		name := vars["permWorkflowName"]

		p, errP := project.Load(api.mustDB(), api.Cache, key, getUser(ctx))
		if errP != nil {
			return sdk.WrapError(errP, "Cannot load Project %s", key)
		}

		oldW, errW := workflow.Load(api.mustDB(), api.Cache, key, name, getUser(ctx), workflow.LoadOptions{})
		if errW != nil {
			return sdk.WrapError(errW, "Cannot load Workflow %s", key)
		}

		tx, errT := api.mustDB().Begin()
		if errT != nil {
			return sdk.WrapError(errT, "Cannot start transaction")
		}
		defer tx.Rollback()

		if err := workflow.Delete(tx, api.Cache, p, oldW, getUser(ctx)); err != nil {
			return sdk.WrapError(err, "Cannot delete workflow")
		}

		if err := project.UpdateLastModified(tx, api.Cache, getUser(ctx), p, sdk.ProjectWorkflowLastModificationType); err != nil {
			return sdk.WrapError(err, "Cannot update project last modified date")
		}

		if err := tx.Commit(); err != nil {
			return sdk.WrapError(errT, "Cannot commit transaction")
		}
		return WriteJSON(w, nil, http.StatusOK)
	}
}

func (api *API) getWorkflowHookHandler() Handler {
	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
		vars := mux.Vars(r)
		key := vars["key"]
		name := vars["permWorkflowName"]
		uuid := vars["uuid"]

		wf, errW := workflow.Load(api.mustDB(), api.Cache, key, name, getUser(ctx), workflow.LoadOptions{})
		if errW != nil {
			return sdk.WrapError(errW, "getWorkflowHookHandler> Cannot load Workflow %s/%s", key, name)
		}

		whooks := wf.GetHooks()
		_, has := whooks[uuid]
		if !has {
			return sdk.WrapError(sdk.ErrNotFound, "getWorkflowHookHandler> Cannot load Workflow %s/%s hook %s", key, name, uuid)
		}

		//Push the hook to hooks µService
		dao := services.Querier(api.mustDB(), api.Cache)
		//Load service "hooks"
		srvs, errS := dao.FindByType("hooks")
		if errS != nil {
			return sdk.WrapError(errS, "getWorkflowHookHandler> Unable to load hooks services")
		}

		path := fmt.Sprintf("/task/%s/execution", uuid)
		task := sdk.Task{}
		if _, err := services.DoJSONRequest(srvs, "GET", path, nil, &task); err != nil {
			return sdk.WrapError(err, "getWorkflowHookHandler> Unable to get hook %s task and executions", uuid)
		}

		return WriteJSON(w, task, http.StatusOK)
	}
}
<|endoftext|>


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

// Package mutation implements the monitor service. This package contains the
// core functionality.
package mutation

import (
	"fmt"
	"log"
	"strconv"

	"github.com/google/keytransparency/core/mutator"
	"github.com/google/keytransparency/core/transaction"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"

	tpb "github.com/google/keytransparency/core/proto/keytransparency_v1_types"
	"github.com/google/trillian"
)

// Server holds internal state for the monitor server core functionality.
type Server struct {
	logID     int64
	mapID     int64
	tlog      trillian.TrillianLogClient
	tmap      trillian.TrillianMapClient
	mutations mutator.Mutation
	factory   transaction.Factory
}

// New creates a new instance of the monitor server.
func New(logID int64,
	mapID int64,
	tlog trillian.TrillianLogClient,
	tmap trillian.TrillianMapClient,
	mutations mutator.Mutation,
	factory transaction.Factory) *Server {
	return &Server{
		logID:     logID,
		mapID:     mapID,
		tlog:      tlog,
		tmap:      tmap,
		mutations: mutations,
		factory:   factory,
	}
}

// GetMutations returns a list of mutations paged by epoch number.
func (s *Server) GetMutations(ctx context.Context, in *tpb.GetMutationsRequest) (*tpb.GetMutationsResponse, error) {
	// Get signed map root by revision.
	resp, err := s.tmap.GetSignedMapRootByRevision(ctx, &trillian.GetSignedMapRootByRevisionRequest{
		MapId:    s.mapID,
		Revision: in.Epoch,
	})
	if err != nil {
		log.Printf("GetSignedMapRootByRevision(%v, %v): %v", s.mapID, in.Epoch, err)
		return nil, grpc.Errorf(codes.Internal, "Get signed map root failed")
	}

	// Get highest and lowest sequence number.
	highestSeq := uint64(resp.GetMapRoot().GetMetadata().HighestFullyCompletedSeq)
	lowestSeq, err := s.lowestSequenceNumber(ctx, in.PageToken, in.Epoch-1)
	if err != nil {
		return nil, err
	}

	// Read mutations from the database.
	txn, err := s.factory.NewTxn(ctx)
	if err != nil {
		return nil, fmt.Errorf("NewDBTxn(): %v", err)
	}
	maxSequence, mRange, err := s.mutations.ReadRange(txn, lowestSeq, highestSeq, in.PageSize)
	if err != nil {
		log.Printf("mutations.ReadRange(%v, %v, %v): %v", lowestSeq, highestSeq, in.PageSize, err)
		if err := txn.Rollback(); err != nil {
			log.Printf("Cannot rollback the transaction: %v", err)
		}
		return nil, grpc.Errorf(codes.Internal, "Reading mutations range failed")
	}
	if err := txn.Commit(); err != nil {
		return nil, fmt.Errorf("txn.Commit(): %v", err)
	}
	indexes := make([][]byte, 0, len(mRange))
	mutations := make([]*tpb.Mutation, 0, len(mRange))
	for _, m := range mRange {
		mutations = append(mutations, &tpb.Mutation{Update: m})
		indexes = append(indexes, m.GetKeyValue().GetKey())
	}
	// Get leaf proofs.
	proofs, err := s.inclusionProofs(ctx, indexes, in.Epoch)
	if err != nil {
		return nil, err
	}
	for i, p := range proofs {
		mutations[i].Proof = p
	}

	// Fetch log proofs.
	logRoot, logConsistency, logInclusion, err := s.logProofs(ctx, in.FirstTreeSize, resp.GetMapRoot().MapRevision)
	if err != nil {
		return nil, err
	}

	nextPageToken := ""
	if len(mutations) == int(in.PageSize) && maxSequence != highestSeq {
		nextPageToken = fmt.Sprintf("%d", maxSequence)
	}
	return &tpb.GetMutationsResponse{
		Epoch:          in.Epoch,
		Smr:            resp.GetMapRoot(),
		LogRoot:        logRoot.GetSignedLogRoot(),
		LogConsistency: logConsistency.GetProof().GetHashes(),
		LogInclusion:   logInclusion.GetProof().GetHashes(),
		Mutations:      mutations,
		NextPageToken:  nextPageToken,
	}, nil
}

func (s *Server) logProofs(ctx context.Context, firstTreeSize int64, revision int64) (*trillian.GetLatestSignedLogRootResponse, *trillian.GetConsistencyProofResponse, *trillian.GetInclusionProofResponse, error) {
	logRoot, err := s.tlog.GetLatestSignedLogRoot(ctx,
		&trillian.GetLatestSignedLogRootRequest{
			LogId: s.logID,
		})
	if err != nil {
		log.Printf("tlog.GetLatestSignedLogRoot(%v): %v", s.logID, err)
		return nil, nil, nil, grpc.Errorf(codes.Internal, "Cannot fetch SignedLogRoot")
	}
	secondTreeSize := logRoot.GetSignedLogRoot().GetTreeSize()
	// Consistency proof.
	var logConsistency *trillian.GetConsistencyProofResponse
	if firstTreeSize != 0 {
		logConsistency, err = s.tlog.GetConsistencyProof(ctx,
			&trillian.GetConsistencyProofRequest{
				LogId:          s.logID,
				FirstTreeSize:  firstTreeSize,
				SecondTreeSize: secondTreeSize,
			})
		if err != nil {
			log.Printf("tlog.GetConsistency(%v, %v, %v): %v", s.logID, firstTreeSize, secondTreeSize, err)
			return nil, nil, nil, grpc.Errorf(codes.Internal, "Cannot fetch log consistency proof")
		}
	}
	// Inclusion proof.
	logInclusion, err := s.tlog.GetInclusionProof(ctx,
		&trillian.GetInclusionProofRequest{
			LogId: s.logID,
			// SignedMapRoot must be in the log at MapRevision.
			LeafIndex: revision,
			TreeSize:  secondTreeSize,
		})
	if err != nil {
		log.Printf("tlog.GetInclusionProof(%v, %v, %v): %v", s.logID, revision, secondTreeSize, err)
		return nil, nil, nil, grpc.Errorf(codes.Internal, "Cannot fetch log inclusion proof")
	}
	return logRoot, logConsistency, logInclusion, nil
}

func (s *Server) lowestSequenceNumber(ctx context.Context, token string, epoch int64) (uint64, error) {
	lowestSeq := int64(0)
	if token != "" {
		// A simple cast will panic if the underlying string is not a
		// string. To avoid this, strconv is used.
		var err error
		if lowestSeq, err = strconv.ParseInt(token, 10, 64); err != nil {
			log.Printf("strconv.ParseInt(%v, 10, 64): %v", token, err)
			return 0, grpc.Errorf(codes.InvalidArgument, "%v is not a valid sequence number", token)
		}
	} else if epoch != 0 {
		resp, err := s.tmap.GetSignedMapRootByRevision(ctx, &trillian.GetSignedMapRootByRevisionRequest{
			MapId:    s.mapID,
			Revision: epoch,
		})
		if err != nil {
			log.Printf("GetSignedMapRootByRevision(%v, %v): %v", s.mapID, epoch, err)
			return 0, grpc.Errorf(codes.Internal, "Get previous signed map root failed")
		}
		lowestSeq = resp.GetMapRoot().GetMetadata().HighestFullyCompletedSeq
	}
	return uint64(lowestSeq), nil
}

func (s *Server) inclusionProofs(ctx context.Context, indexes [][]byte, epoch int64) ([]*trillian.MapLeafInclusion, error) {
	getResp, err := s.tmap.GetLeaves(ctx, &trillian.GetMapLeavesRequest{
		MapId:    s.mapID,
		Index:    indexes,
		Revision: epoch,
	})
	if err != nil {
		log.Printf("GetLeaves(): %v", err)
		return nil, grpc.Errorf(codes.Internal, "Failed fetching map leaf")
	}
	if got, want := len(getResp.MapLeafInclusion), len(indexes); got != want {
		log.Printf("GetLeaves() len: %v, want %v", got, want)
		return nil, grpc.Errorf(codes.Internal, "Failed fetching map leaf")
	}
	return getResp.MapLeafInclusion, nil
}
<commit_msg>Use Get when accessing proto message fields<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package mutation implements the monitor service. This package contains the
// core functionality.
package mutation

import (
	"fmt"
	"log"
	"strconv"

	"github.com/google/keytransparency/core/mutator"
	"github.com/google/keytransparency/core/transaction"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"

	tpb "github.com/google/keytransparency/core/proto/keytransparency_v1_types"
	"github.com/google/trillian"
)

// Server holds internal state for the monitor server core functionality.
type Server struct {
	logID     int64
	mapID     int64
	tlog      trillian.TrillianLogClient
	tmap      trillian.TrillianMapClient
	mutations mutator.Mutation
	factory   transaction.Factory
}

// New creates a new instance of the monitor server.
func New(logID int64,
	mapID int64,
	tlog trillian.TrillianLogClient,
	tmap trillian.TrillianMapClient,
	mutations mutator.Mutation,
	factory transaction.Factory) *Server {
	return &Server{
		logID:     logID,
		mapID:     mapID,
		tlog:      tlog,
		tmap:      tmap,
		mutations: mutations,
		factory:   factory,
	}
}

// GetMutations returns a list of mutations paged by epoch number.
func (s *Server) GetMutations(ctx context.Context, in *tpb.GetMutationsRequest) (*tpb.GetMutationsResponse, error) {
	// Get signed map root by revision.
	resp, err := s.tmap.GetSignedMapRootByRevision(ctx, &trillian.GetSignedMapRootByRevisionRequest{
		MapId:    s.mapID,
		Revision: in.Epoch,
	})
	if err != nil {
		log.Printf("GetSignedMapRootByRevision(%v, %v): %v", s.mapID, in.Epoch, err)
		return nil, grpc.Errorf(codes.Internal, "Get signed map root failed")
	}

	// Get highest and lowest sequence number.
	highestSeq := uint64(resp.GetMapRoot().GetMetadata().GetHighestFullyCompletedSeq())
	lowestSeq, err := s.lowestSequenceNumber(ctx, in.PageToken, in.Epoch-1)
	if err != nil {
		return nil, err
	}

	// Read mutations from the database.
	txn, err := s.factory.NewTxn(ctx)
	if err != nil {
		return nil, fmt.Errorf("NewDBTxn(): %v", err)
	}
	maxSequence, mRange, err := s.mutations.ReadRange(txn, lowestSeq, highestSeq, in.PageSize)
	if err != nil {
		log.Printf("mutations.ReadRange(%v, %v, %v): %v", lowestSeq, highestSeq, in.PageSize, err)
		if err := txn.Rollback(); err != nil {
			log.Printf("Cannot rollback the transaction: %v", err)
		}
		return nil, grpc.Errorf(codes.Internal, "Reading mutations range failed")
	}
	if err := txn.Commit(); err != nil {
		return nil, fmt.Errorf("txn.Commit(): %v", err)
	}
	indexes := make([][]byte, 0, len(mRange))
	mutations := make([]*tpb.Mutation, 0, len(mRange))
	for _, m := range mRange {
		mutations = append(mutations, &tpb.Mutation{Update: m})
		indexes = append(indexes, m.GetKeyValue().GetKey())
	}
	// Get leaf proofs.
	// TODO: allow leaf proofs to be optional.
	proofs, err := s.inclusionProofs(ctx, indexes, in.Epoch)
	if err != nil {
		return nil, err
	}
	for i, p := range proofs {
		mutations[i].Proof = p
	}

	// Fetch log proofs.
	logRoot, logConsistency, logInclusion, err := s.logProofs(ctx, in.GetFirstTreeSize(), resp.GetMapRoot().GetMapRevision())
	if err != nil {
		return nil, err
	}

	nextPageToken := ""
	if len(mutations) == int(in.PageSize) && maxSequence != highestSeq {
		nextPageToken = fmt.Sprintf("%d", maxSequence)
	}
	return &tpb.GetMutationsResponse{
		Epoch:          in.Epoch,
		Smr:            resp.GetMapRoot(),
		LogRoot:        logRoot.GetSignedLogRoot(),
		LogConsistency: logConsistency.GetProof().GetHashes(),
		LogInclusion:   logInclusion.GetProof().GetHashes(),
		Mutations:      mutations,
		NextPageToken:  nextPageToken,
	}, nil
}

func (s *Server) logProofs(ctx context.Context, firstTreeSize int64, revision int64) (*trillian.GetLatestSignedLogRootResponse, *trillian.GetConsistencyProofResponse, *trillian.GetInclusionProofResponse, error) {
	logRoot, err := s.tlog.GetLatestSignedLogRoot(ctx,
		&trillian.GetLatestSignedLogRootRequest{
			LogId: s.logID,
		})
	if err != nil {
		log.Printf("tlog.GetLatestSignedLogRoot(%v): %v", s.logID, err)
		return nil, nil, nil, grpc.Errorf(codes.Internal, "Cannot fetch SignedLogRoot")
	}
	secondTreeSize := logRoot.GetSignedLogRoot().GetTreeSize()
	// Consistency proof.
	var logConsistency *trillian.GetConsistencyProofResponse
	if firstTreeSize != 0 {
		logConsistency, err = s.tlog.GetConsistencyProof(ctx,
			&trillian.GetConsistencyProofRequest{
				LogId:          s.logID,
				FirstTreeSize:  firstTreeSize,
				SecondTreeSize: secondTreeSize,
			})
		if err != nil {
			log.Printf("tlog.GetConsistency(%v, %v, %v): %v", s.logID, firstTreeSize, secondTreeSize, err)
			return nil, nil, nil, grpc.Errorf(codes.Internal, "Cannot fetch log consistency proof")
		}
	}
	// Inclusion proof.
	logInclusion, err := s.tlog.GetInclusionProof(ctx,
		&trillian.GetInclusionProofRequest{
			LogId: s.logID,
			// SignedMapRoot must be in the log at MapRevision.
			LeafIndex: revision,
			TreeSize:  secondTreeSize,
		})
	if err != nil {
		log.Printf("tlog.GetInclusionProof(%v, %v, %v): %v", s.logID, revision, secondTreeSize, err)
		return nil, nil, nil, grpc.Errorf(codes.Internal, "Cannot fetch log inclusion proof")
	}
	return logRoot, logConsistency, logInclusion, nil
}

func (s *Server) lowestSequenceNumber(ctx context.Context, token string, epoch int64) (uint64, error) {
	lowestSeq := int64(0)
	if token != "" {
		// A simple cast will panic if the underlying string is not a
		// string. To avoid this, strconv is used.
		var err error
		if lowestSeq, err = strconv.ParseInt(token, 10, 64); err != nil {
			log.Printf("strconv.ParseInt(%v, 10, 64): %v", token, err)
			return 0, grpc.Errorf(codes.InvalidArgument, "%v is not a valid sequence number", token)
		}
	} else if epoch != 0 {
		resp, err := s.tmap.GetSignedMapRootByRevision(ctx, &trillian.GetSignedMapRootByRevisionRequest{
			MapId:    s.mapID,
			Revision: epoch,
		})
		if err != nil {
			log.Printf("GetSignedMapRootByRevision(%v, %v): %v", s.mapID, epoch, err)
			return 0, grpc.Errorf(codes.Internal, "Get previous signed map root failed")
		}
		lowestSeq = resp.GetMapRoot().GetMetadata().HighestFullyCompletedSeq
	}
	return uint64(lowestSeq), nil
}

func (s *Server) inclusionProofs(ctx context.Context, indexes [][]byte, epoch int64) ([]*trillian.MapLeafInclusion, error) {
	getResp, err := s.tmap.GetLeaves(ctx, &trillian.GetMapLeavesRequest{
		MapId:    s.mapID,
		Index:    indexes,
		Revision: epoch,
	})
	if err != nil {
		log.Printf("GetLeaves(): %v", err)
		return nil, grpc.Errorf(codes.Internal, "Failed fetching map leaf")
	}
	if got, want := len(getResp.MapLeafInclusion), len(indexes); got != want {
		log.Printf("GetLeaves() len: %v, want %v", got, want)
		return nil, grpc.Errorf(codes.Internal, "Failed fetching map leaf")
	}
	return getResp.MapLeafInclusion, nil
}
<|endoftext|>


<commit_before>package router

import (
	"errors"
	"sync"

	pb_broker "github.com/TheThingsNetwork/ttn/api/broker"
	pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol"
	pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan"
	pb "github.com/TheThingsNetwork/ttn/api/router"
	"github.com/TheThingsNetwork/ttn/core/types"
	"github.com/apex/log"
)

func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) {
	ctx := r.Ctx.WithFields(log.Fields{
		"GatewayEUI": gatewayEUI,
		"AppEUI":     *activation.AppEui,
		"DevEUI":     *activation.DevEui,
	})
	var err error
	defer func() {
		if err != nil {
			ctx.WithError(err).Warn("Could not handle activation")
		}
	}()

	gateway := r.getGateway(gatewayEUI)

	uplink := &pb.UplinkMessage{
		Payload:          activation.Payload,
		ProtocolMetadata: activation.ProtocolMetadata,
		GatewayMetadata:  activation.GatewayMetadata,
	}

	// Only for LoRaWAN
	gateway.Utilization.AddRx(uplink)

	downlinkOptions := r.buildDownlinkOptions(uplink, true, gateway)

	// Find Broker
	brokers, err := r.brokerDiscovery.All()
	if err != nil {
		return nil, err
	}

	// Prepare request
	request := &pb_broker.DeviceActivationRequest{
		Payload:          activation.Payload,
		DevEui:           activation.DevEui,
		AppEui:           activation.AppEui,
		ProtocolMetadata: activation.ProtocolMetadata,
		GatewayMetadata:  activation.GatewayMetadata,
		ActivationMetadata: &pb_protocol.ActivationMetadata{
			Protocol: &pb_protocol.ActivationMetadata_Lorawan{
				Lorawan: &pb_lorawan.ActivationMetadata{
					AppEui: activation.AppEui,
					DevEui: activation.DevEui,
				},
			},
		},
		DownlinkOptions: downlinkOptions,
	}

	// Prepare LoRaWAN activation
	status, err := gateway.Status.Get()
	if err != nil {
		return nil, err
	}
	region := status.Region
	if region == "" {
		region = guessRegion(uplink.GatewayMetadata.Frequency)
	}
	band, err := getBand(region)
	if err != nil {
		return nil, err
	}
	lorawan := request.ActivationMetadata.GetLorawan()
	lorawan.Rx1DrOffset = 0
	lorawan.Rx2Dr = uint32(band.RX2DataRate)
	lorawan.RxDelay = uint32(band.ReceiveDelay1.Seconds())
	switch region {
	case "EU_863_870":
		lorawan.CfList = []uint64{867100000, 867300000, 867500000, 867700000, 867900000}
	}

	ctx = ctx.WithField("NumBrokers", len(brokers))
	ctx.Debug("Forward Activation")

	// Forward to all brokers and collect responses
	var wg sync.WaitGroup
	responses := make(chan *pb_broker.DeviceActivationResponse, len(brokers))
	for _, broker := range brokers {
		broker, err := r.getBroker(broker)
		if err != nil {
			continue
		}

		// Do async request
		wg.Add(1)
		go func() {
			res, err := broker.client.Activate(r.Component.GetContext(), request)
			if err == nil && res != nil {
				responses <- res
			}
			wg.Done()
		}()
	}

	// Make sure to close channel when all requests are done
	go func() {
		wg.Wait()
		close(responses)
	}()

	var gotFirst bool
	for res := range responses {
		if gotFirst {
			ctx.Warn("Duplicate Activation Response")
		} else {
			gotFirst = true
			downlink := &pb_broker.DownlinkMessage{
				Payload:        res.Payload,
				DownlinkOption: res.DownlinkOption,
			}
			err := r.HandleDownlink(downlink)
			if err != nil {
				ctx.Warn("Could not send downlink for Activation")
				gotFirst = false // try again
			}
		}
	}

	// Activation not accepted by any broker
	if !gotFirst {
		ctx.Debug("Activation not accepted at this gateway")
		return nil, errors.New("ttn/router: Activation not accepted at this Gateway")
	}

	// Activation accepted by (at least one) broker
	ctx.Debug("Activation accepted")
	return &pb.DeviceActivationResponse{}, nil
}
<commit_msg>Also sync gateway schedule on Activations<commit_after>package router

import (
	"errors"
	"sync"

	pb_broker "github.com/TheThingsNetwork/ttn/api/broker"
	pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol"
	pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan"
	pb "github.com/TheThingsNetwork/ttn/api/router"
	"github.com/TheThingsNetwork/ttn/core/types"
	"github.com/apex/log"
)

func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) {
	ctx := r.Ctx.WithFields(log.Fields{
		"GatewayEUI": gatewayEUI,
		"AppEUI":     *activation.AppEui,
		"DevEUI":     *activation.DevEui,
	})
	var err error
	defer func() {
		if err != nil {
			ctx.WithError(err).Warn("Could not handle activation")
		}
	}()

	gateway := r.getGateway(gatewayEUI)

	uplink := &pb.UplinkMessage{
		Payload:          activation.Payload,
		ProtocolMetadata: activation.ProtocolMetadata,
		GatewayMetadata:  activation.GatewayMetadata,
	}

	// Only for LoRaWAN
	gateway.Schedule.Sync(uplink.GatewayMetadata.Timestamp)
	gateway.Utilization.AddRx(uplink)
	downlinkOptions := r.buildDownlinkOptions(uplink, true, gateway)

	// Find Broker
	brokers, err := r.brokerDiscovery.All()
	if err != nil {
		return nil, err
	}

	// Prepare request
	request := &pb_broker.DeviceActivationRequest{
		Payload:          activation.Payload,
		DevEui:           activation.DevEui,
		AppEui:           activation.AppEui,
		ProtocolMetadata: activation.ProtocolMetadata,
		GatewayMetadata:  activation.GatewayMetadata,
		ActivationMetadata: &pb_protocol.ActivationMetadata{
			Protocol: &pb_protocol.ActivationMetadata_Lorawan{
				Lorawan: &pb_lorawan.ActivationMetadata{
					AppEui: activation.AppEui,
					DevEui: activation.DevEui,
				},
			},
		},
		DownlinkOptions: downlinkOptions,
	}

	// Prepare LoRaWAN activation
	status, err := gateway.Status.Get()
	if err != nil {
		return nil, err
	}
	region := status.Region
	if region == "" {
		region = guessRegion(uplink.GatewayMetadata.Frequency)
	}
	band, err := getBand(region)
	if err != nil {
		return nil, err
	}
	lorawan := request.ActivationMetadata.GetLorawan()
	lorawan.Rx1DrOffset = 0
	lorawan.Rx2Dr = uint32(band.RX2DataRate)
	lorawan.RxDelay = uint32(band.ReceiveDelay1.Seconds())
	switch region {
	case "EU_863_870":
		lorawan.CfList = []uint64{867100000, 867300000, 867500000, 867700000, 867900000}
	}

	ctx = ctx.WithField("NumBrokers", len(brokers))
	ctx.Debug("Forward Activation")

	// Forward to all brokers and collect responses
	var wg sync.WaitGroup
	responses := make(chan *pb_broker.DeviceActivationResponse, len(brokers))
	for _, broker := range brokers {
		broker, err := r.getBroker(broker)
		if err != nil {
			continue
		}

		// Do async request
		wg.Add(1)
		go func() {
			res, err := broker.client.Activate(r.Component.GetContext(), request)
			if err == nil && res != nil {
				responses <- res
			}
			wg.Done()
		}()
	}

	// Make sure to close channel when all requests are done
	go func() {
		wg.Wait()
		close(responses)
	}()

	var gotFirst bool
	for res := range responses {
		if gotFirst {
			ctx.Warn("Duplicate Activation Response")
		} else {
			gotFirst = true
			downlink := &pb_broker.DownlinkMessage{
				Payload:        res.Payload,
				DownlinkOption: res.DownlinkOption,
			}
			err := r.HandleDownlink(downlink)
			if err != nil {
				ctx.Warn("Could not send downlink for Activation")
				gotFirst = false // try again
			}
		}
	}

	// Activation not accepted by any broker
	if !gotFirst {
		ctx.Debug("Activation not accepted at this gateway")
		return nil, errors.New("ttn/router: Activation not accepted at this Gateway")
	}

	// Activation accepted by (at least one) broker
	ctx.Debug("Activation accepted")
	return &pb.DeviceActivationResponse{}, nil
}
<|endoftext|>


<commit_before>package app

import (
	"fmt"

	"github.com/moncho/dry/version"
)

//ShortHelp is a short description of dry
const ShortHelp = `
dry

Connects to a Docker daemon if environment variable DOCKER_HOST is present
then shows the list of containers and allows to execute Docker commands on them.

`

var help = `
<white>dry ` + fmt.Sprintf("version %s, build %s", version.VERSION, version.GITCOMMIT) + `</>` +
	`
Connects to a Docker daemon, shows the list of containers and allows to execute Docker commands on them.

Visit <blue>http://moncho.github.io/dry/</> for more information.

<yellow>Global keybinds</>
	<white>F9</>        Shows the last 10 events reported by Docker
	<white>F10</>       Inspects Docker
	<white>1</>         To container list
	<white>2</>         To image list
	<white>3</>         To network list
	<white>h</>         Shows this help screen
	<white>Crtl+c</>    Quits dry inmediately
	<white>q</>         Quits <white>dry</>.
	<white>esc</>       Goes back to the main screen

<yellow>Container list keybinds</>
	<white>F1</>        Cycles through containers sort modes (by Id | by Image | by Status | by Name)
	<white>F2</>        Toggles showing all containers (default shows just running)
	<white>F5</>        Refresh container list
	<white>e</>         Removes the selected container
	<white>Crtl+e</>    Removes all stopped containers
	<white>Crtl+k</>    Kills the selected container
	<white>l</>         Displays the logs of the selected container
	<white>Ctrl+r</>    Restarts selected container
	<white>s</>         Displays a live stream of the selected container resource usage statistics
	<white>Crtl+t</>    Stops selected container (noop if it is not running)
	<white>Enter</>     Returns low-level information of the selected container

<yellow>Image list keybinds</>
	<white>F1</>        Cycles through images sort modes (by Repo | by Id | by Creation date | by Size)
	<white>F5</>        Refresh the image list
	<white>Crtl+e</>    Removes the selected image
	<white>Crtl+f</>    Forces removal of the selected image
	<white>i</>         Shows image history
	<white>Enter</>     Returns low-level information of the selected image

	<yellow>Network list keybinds</>
	<white>Enter</>     Returns low-level information of the selected network

<yellow>Move around in container/image/network lists</>
	<white>ArrowUp</>   Moves the cursor one line up
	<white>ArrowDown</> Moves the cursor one line down

<yellow>Move around in logs/inspect buffers</>
	<white>g</>         Moves the cursor to the beginning
	<white>G</>         Moves the cursor until the end
	<white>n</>         After a search, it moves forwards to the next search hit
	<white>N</>         After a search, it moves backwards to the previous search hit
	<white>s</>         Searchs in the text being shown
	<white>pg up</>     Moves the cursor "screen size" lines up
	<white>pg down</>   Moves the cursor "screen size" lines down

<r> Press ESC to exit help. </r>
`

const (
	commonMappings = "<b>[H]:<darkgrey>Help</> <b>[Q]:<darkgrey>Quit</> <blue>|</> "
	inspectMapping = "<b>[Enter]:<darkgrey>Commands</></>"
	keyMappings    = commonMappings +
		"<b>[F1]:<darkgrey>Sort</> <b>[F2]:<darkgrey>Toggle Show Containers</> <b>[F5]:<darkgrey>Refresh</> <b>[F9]:<darkgrey>Docker Events</> <b>[F10]:<darkgrey>Docker Info</> <blue>|</> " +
		"<b>[2]:<darkgrey>Images</> <b>[3]:<darkgrey>Networks</><blue>|</>" +
		inspectMapping

	imagesKeyMappings = commonMappings +
		"<b>[F1]:<darkgrey>Sort</> <b>[F5]:<darkgrey>Refresh</> <b>[F9]:<darkgrey>Docker Events</> <b>[F10]:<darkgrey>Docker Info</> <blue>|</> " +
		"<b>[1]:<darkgrey>Containers</> <b>[3]:<darkgrey>Networks</> <blue>|</>" +
		"<b>[Crtl+D]:<darkgrey>Remove Dangling</> <b>[Crtl+E]:<darkgrey>Remove</> <b>[Crtl+F]:<darkgrey>Force Remove</> <b>[I]:<darkgrey>History</> <blue>|</>" +
		inspectMapping

	networkKeyMappings = commonMappings +
		"<b>[F1]:<darkgrey>Sort</> <b>[F5]:<darkgrey>Refresh</> <b>[F9]:<darkgrey>Docker Events</> <b>[F10]:<darkgrey>Docker Info</> <blue>|</> " +
		"<b>[1]:<darkgrey>Containers</> <b>[2]:<darkgrey>Images</><blue>|</>" +
		inspectMapping

	commandsMenuBar = "<b>[Esc]:<darkgrey>Back</> <b>[Up]:<darkgrey>Cursor Up</> <b>[Down]:<darkgrey>Cursor Down</> <b>[Intro]:<darkgrey>Execute Command</>"
)
<commit_msg>Minor<commit_after>package app

import (
	"fmt"

	"github.com/moncho/dry/version"
)

//ShortHelp is a short description of dry
const ShortHelp = `
dry

Connects to a Docker daemon if environment variable DOCKER_HOST is present
then shows the list of containers and allows to execute Docker commands on them.

`

var help = `
<white>dry ` + fmt.Sprintf("version %s, build %s", version.VERSION, version.GITCOMMIT) + `</>` +
	`
Connects to a Docker daemon, shows the list of containers and allows to execute Docker commands on them.

Visit <blue>http://moncho.github.io/dry/</> for more information.

<yellow>Global keybinds</>
	<white>F9</>        Shows the last 10 events reported by Docker
	<white>F10</>       Inspects Docker
	<white>1</>         To container list
	<white>2</>         To image list
	<white>3</>         To network list
	<white>h</>         Shows this help screen
	<white>Crtl+c</>    Quits dry inmediately
	<white>q</>         Quits <white>dry</>.
	<white>esc</>       Goes back to the main screen

<yellow>Container list keybinds</>
	<white>F1</>        Cycles through containers sort modes (by Id | by Image | by Status | by Name)
	<white>F2</>        Toggles showing all containers (default shows just running)
	<white>F5</>        Refresh container list
	<white>e</>         Removes the selected container
	<white>Crtl+e</>    Removes all stopped containers
	<white>Crtl+k</>    Kills the selected container
	<white>l</>         Displays the logs of the selected container
	<white>Ctrl+r</>    Restarts selected container
	<white>s</>         Displays a live stream of the selected container resource usage statistics
	<white>Crtl+t</>    Stops selected container (noop if it is not running)
	<white>Enter</>     Returns low-level information of the selected container

<yellow>Image list keybinds</>
	<white>F1</>        Cycles through images sort modes (by Repo | by Id | by Creation date | by Size)
	<white>F5</>        Refresh the image list
	<white>Crtl+e</>    Removes the selected image
	<white>Crtl+f</>    Forces removal of the selected image
	<white>i</>         Shows image history
	<white>Enter</>     Returns low-level information of the selected image

	<yellow>Network list keybinds</>
	<white>Enter</>     Returns low-level information of the selected network

<yellow>Move around in container/image/network lists</>
	<white>ArrowUp</>   Moves the cursor one line up
	<white>ArrowDown</> Moves the cursor one line down

<yellow>Move around in logs/inspect buffers</>
	<white>g</>         Moves the cursor to the beginning
	<white>G</>         Moves the cursor until the end
	<white>n</>         After a search, it moves forwards to the next search hit
	<white>N</>         After a search, it moves backwards to the previous search hit
	<white>s</>         Searchs in the text being shown
	<white>pg up</>     Moves the cursor "screen size" lines up
	<white>pg down</>   Moves the cursor "screen size" lines down

<r> Press ESC to exit help. </r>
`

const (
	commonMappings = "<b>[H]:<darkgrey>Help</> <b>[Q]:<darkgrey>Quit</> <blue>|</> "
	keyMappings    = commonMappings +
		"<b>[F1]:<darkgrey>Sort</> <b>[F2]:<darkgrey>Toggle Show Containers</> <b>[F5]:<darkgrey>Refresh</> <b>[F9]:<darkgrey>Docker Events</> <b>[F10]:<darkgrey>Docker Info</> <blue>|</> " +
		"<b>[2]:<darkgrey>Images</> <b>[3]:<darkgrey>Networks</><blue>|</> <b>[Enter]:<darkgrey>Commands</></>"

	imagesKeyMappings = commonMappings +
		"<b>[F1]:<darkgrey>Sort</> <b>[F5]:<darkgrey>Refresh</> <b>[F9]:<darkgrey>Docker Events</> <b>[F10]:<darkgrey>Docker Info</> <blue>|</> " +
		"<b>[1]:<darkgrey>Containers</> <b>[3]:<darkgrey>Networks</> <blue>|</>" +
		"<b>[Crtl+D]:<darkgrey>Remove Dangling</> <b>[Crtl+E]:<darkgrey>Remove</> <b>[Crtl+F]:<darkgrey>Force Remove</> <b>[I]:<darkgrey>History</> <blue>|</>"

	networkKeyMappings = commonMappings +
		"<b>[F1]:<darkgrey>Sort</> <b>[F5]:<darkgrey>Refresh</> <b>[F9]:<darkgrey>Docker Events</> <b>[F10]:<darkgrey>Docker Info</> <blue>|</> " +
		"<b>[1]:<darkgrey>Containers</> <b>[2]:<darkgrey>Images</><blue>|</>" +
		"<b>[Crtl+E]:<darkgrey>Remove</> <b>[Enter]:<darkgrey>Inspect</>"

	commandsMenuBar = "<b>[Esc]:<darkgrey>Back</> <b>[Up]:<darkgrey>Cursor Up</> <b>[Down]:<darkgrey>Cursor Down</> <b>[Intro]:<darkgrey>Execute Command</>"
)
<|endoftext|>