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

package storage

import (
	"container/heap"
	"strings"

	"github.com/prometheus/common/log"
	"github.com/prometheus/prometheus/pkg/labels"
)

type fanout struct {
	primary     Storage
	secondaries []Storage
}

// NewFanout returns a new fan-out Storage, which proxies reads and writes
// through to multiple underlying storages.
func NewFanout(primary Storage, secondaries ...Storage) Storage {
	return &fanout{
		primary:     primary,
		secondaries: secondaries,
	}
}

func (f *fanout) Querier(mint, maxt int64) (Querier, error) {
	queriers := mergeQuerier{
		queriers: make([]Querier, 0, 1+len(f.secondaries)),
	}

	// Add primary querier
	querier, err := f.primary.Querier(mint, maxt)
	if err != nil {
		return nil, err
	}
	queriers.queriers = append(queriers.queriers, querier)

	// Add secondary queriers
	for _, storage := range f.secondaries {
		querier, err := storage.Querier(mint, maxt)
		if err != nil {
			queriers.Close()
			return nil, err
		}
		queriers.queriers = append(queriers.queriers, querier)
	}
	return &queriers, nil
}

func (f *fanout) Appender() (Appender, error) {
	primary, err := f.primary.Appender()
	if err != nil {
		return nil, err
	}

	secondaries := make([]Appender, 0, len(f.secondaries))
	for _, storage := range f.secondaries {
		appender, err := storage.Appender()
		if err != nil {
			return nil, err
		}
		secondaries = append(secondaries, appender)
	}
	return &fanoutAppender{
		primary:     primary,
		secondaries: secondaries,
	}, nil
}

// Close closes the storage and all its underlying resources.
func (f *fanout) Close() error {
	if err := f.primary.Close(); err != nil {
		return err
	}

	// TODO return multiple errors?
	var lastErr error
	for _, storage := range f.secondaries {
		if err := storage.Close(); err != nil {
			lastErr = err
		}
	}
	return lastErr
}

// fanoutAppender implements Appender.
type fanoutAppender struct {
	primary     Appender
	secondaries []Appender
}

func (f *fanoutAppender) Add(l labels.Labels, t int64, v float64) (string, error) {
	ref, err := f.primary.Add(l, t, v)
	if err != nil {
		return ref, err
	}

	for _, appender := range f.secondaries {
		if _, err := appender.Add(l, t, v); err != nil {
			return "", err
		}
	}
	return ref, nil
}

func (f *fanoutAppender) AddFast(l labels.Labels, ref string, t int64, v float64) error {
	if err := f.primary.AddFast(l, ref, t, v); err != nil {
		return err
	}

	for _, appender := range f.secondaries {
		if _, err := appender.Add(l, t, v); err != nil {
			return err
		}
	}
	return nil
}

func (f *fanoutAppender) Commit() (err error) {
	err = f.primary.Commit()

	for _, appender := range f.secondaries {
		if err == nil {
			err = appender.Commit()
		} else {
			if rollbackErr := appender.Rollback(); rollbackErr != nil {
				log.Errorf("Squashed rollback error on commit: %v", rollbackErr)
			}
		}
	}
	return
}

func (f *fanoutAppender) Rollback() (err error) {
	err = f.primary.Rollback()

	for _, appender := range f.secondaries {
		rollbackErr := appender.Rollback()
		if err == nil {
			err = rollbackErr
		} else if rollbackErr != nil {
			log.Errorf("Squashed rollback error on rollback: %v", rollbackErr)
		}
	}
	return nil
}

// mergeQuerier implements Querier.
type mergeQuerier struct {
	queriers []Querier
}

// NewMergeQuerier returns a new Querier that merges results of input queriers.
func NewMergeQuerier(queriers []Querier) Querier {
	return &mergeQuerier{
		queriers: queriers,
	}
}

// Select returns a set of series that matches the given label matchers.
func (q *mergeQuerier) Select(matchers ...*labels.Matcher) SeriesSet {
	seriesSets := make([]SeriesSet, 0, len(q.queriers))
	for _, querier := range q.queriers {
		seriesSets = append(seriesSets, querier.Select(matchers...))
	}
	return newMergeSeriesSet(seriesSets)
}

// LabelValues returns all potential values for a label name.
func (q *mergeQuerier) LabelValues(name string) ([]string, error) {
	var results [][]string
	for _, querier := range q.queriers {
		values, err := querier.LabelValues(name)
		if err != nil {
			return nil, err
		}
		results = append(results, values)
	}
	return mergeStringSlices(results), nil
}

func mergeStringSlices(ss [][]string) []string {
	switch len(ss) {
	case 0:
		return nil
	case 1:
		return ss[0]
	case 2:
		return mergeTwoStringSlices(ss[0], ss[1])
	default:
		halfway := len(ss) / 2
		return mergeTwoStringSlices(
			mergeStringSlices(ss[:halfway]),
			mergeStringSlices(ss[halfway:]),
		)
	}
}

func mergeTwoStringSlices(a, b []string) []string {
	i, j := 0, 0
	result := make([]string, 0, len(a)+len(b))
	for i < len(a) && j < len(b) {
		switch strings.Compare(a[i], b[j]) {
		case 0:
			result = append(result, a[i])
			i++
			j++
		case -1:
			result = append(result, a[i])
			i++
		case 1:
			result = append(result, b[j])
			j++
		}
	}
	result = append(result, a[i:]...)
	result = append(result, b[j:]...)
	return result
}

// Close releases the resources of the Querier.
func (q *mergeQuerier) Close() error {
	// TODO return multiple errors?
	var lastErr error
	for _, querier := range q.queriers {
		if err := querier.Close(); err != nil {
			lastErr = err
		}
	}
	return lastErr
}

// mergeSeriesSet implements SeriesSet
type mergeSeriesSet struct {
	currentLabels labels.Labels
	currentSets   []SeriesSet
	sets          seriesSetHeap
}

func newMergeSeriesSet(sets []SeriesSet) SeriesSet {
	// Sets need to be pre-advanced, so we can introspect the label of the
	// series under the cursor.
	var h seriesSetHeap
	for _, set := range sets {
		if set.Next() {
			heap.Push(&h, set)
		}
	}
	return &mergeSeriesSet{
		sets: h,
	}
}

func (c *mergeSeriesSet) Next() bool {
	// Firstly advance all the current series sets.  If any of them have run out
	// we can drop them, otherwise they should be inserted back into the heap.
	for _, set := range c.currentSets {
		if set.Next() {
			heap.Push(&c.sets, set)
		}
	}
	if len(c.sets) == 0 {
		return false
	}

	// Now, pop items of the heap that have equal label sets.
	c.currentSets = nil
	c.currentLabels = c.sets[0].At().Labels()
	for len(c.sets) > 0 && labels.Equal(c.currentLabels, c.sets[0].At().Labels()) {
		set := heap.Pop(&c.sets).(SeriesSet)
		c.currentSets = append(c.currentSets, set)
	}
	return true
}

func (c *mergeSeriesSet) At() Series {
	series := []Series{}
	for _, seriesSet := range c.currentSets {
		series = append(series, seriesSet.At())
	}
	return &mergeSeries{
		labels: c.currentLabels,
		series: series,
	}
}

func (c *mergeSeriesSet) Err() error {
	for _, set := range c.sets {
		if err := set.Err(); err != nil {
			return err
		}
	}
	return nil
}

type seriesSetHeap []SeriesSet

func (h seriesSetHeap) Len() int      { return len(h) }
func (h seriesSetHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

func (h seriesSetHeap) Less(i, j int) bool {
	a, b := h[i].At().Labels(), h[j].At().Labels()
	return labels.Compare(a, b) < 0
}

func (h *seriesSetHeap) Push(x interface{}) {
	*h = append(*h, x.(SeriesSet))
}

func (h *seriesSetHeap) Pop() interface{} {
	old := *h
	n := len(old)
	x := old[n-1]
	*h = old[0 : n-1]
	return x
}

type mergeSeries struct {
	labels labels.Labels
	series []Series
}

func (m *mergeSeries) Labels() labels.Labels {
	return m.labels
}

func (m *mergeSeries) Iterator() SeriesIterator {
	iterators := make([]SeriesIterator, 0, len(m.series))
	for _, s := range m.series {
		iterators = append(iterators, s.Iterator())
	}
	return &mergeIterator{
		iterators: iterators,
	}
}

type mergeIterator struct {
	iterators []SeriesIterator
	h         seriesIteratorHeap
}

func newMergeIterator(iterators []SeriesIterator) SeriesIterator {
	return &mergeIterator{
		iterators: iterators,
		h:         nil,
	}
}

func (c *mergeIterator) Seek(t int64) bool {
	c.h = seriesIteratorHeap{}
	for _, iter := range c.iterators {
		if iter.Seek(t) {
			heap.Push(&c.h, iter)
		}
	}
	return len(c.h) > 0
}

func (c *mergeIterator) At() (t int64, v float64) {
	// TODO do I need to dedupe or just merge?
	return c.h[0].At()
}

func (c *mergeIterator) Next() bool {
	if c.h == nil {
		for _, iter := range c.iterators {
			if iter.Next() {
				heap.Push(&c.h, iter)
			}
		}
		return len(c.h) > 0
	}

	if len(c.h) == 0 {
		return false
	}

	iter := heap.Pop(&c.h).(SeriesIterator)
	if iter.Next() {
		heap.Push(&c.h, iter)
	}

	return len(c.h) > 0
}

func (c *mergeIterator) Err() error {
	for _, iter := range c.iterators {
		if err := iter.Err(); err != nil {
			return err
		}
	}
	return nil
}

type seriesIteratorHeap []SeriesIterator

func (h seriesIteratorHeap) Len() int      { return len(h) }
func (h seriesIteratorHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

func (h seriesIteratorHeap) Less(i, j int) bool {
	at, _ := h[i].At()
	bt, _ := h[j].At()
	return at < bt
}

func (h *seriesIteratorHeap) Push(x interface{}) {
	*h = append(*h, x.(SeriesIterator))
}

func (h *seriesIteratorHeap) Pop() interface{} {
	old := *h
	n := len(old)
	x := old[n-1]
	*h = old[0 : n-1]
	return x
}
<commit_msg>Return zeros if At() is called after Next() returns false.<commit_after>// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.package remote

package storage

import (
	"container/heap"
	"strings"

	"github.com/prometheus/common/log"
	"github.com/prometheus/prometheus/pkg/labels"
)

type fanout struct {
	primary     Storage
	secondaries []Storage
}

// NewFanout returns a new fan-out Storage, which proxies reads and writes
// through to multiple underlying storages.
func NewFanout(primary Storage, secondaries ...Storage) Storage {
	return &fanout{
		primary:     primary,
		secondaries: secondaries,
	}
}

func (f *fanout) Querier(mint, maxt int64) (Querier, error) {
	queriers := mergeQuerier{
		queriers: make([]Querier, 0, 1+len(f.secondaries)),
	}

	// Add primary querier
	querier, err := f.primary.Querier(mint, maxt)
	if err != nil {
		return nil, err
	}
	queriers.queriers = append(queriers.queriers, querier)

	// Add secondary queriers
	for _, storage := range f.secondaries {
		querier, err := storage.Querier(mint, maxt)
		if err != nil {
			queriers.Close()
			return nil, err
		}
		queriers.queriers = append(queriers.queriers, querier)
	}
	return &queriers, nil
}

func (f *fanout) Appender() (Appender, error) {
	primary, err := f.primary.Appender()
	if err != nil {
		return nil, err
	}

	secondaries := make([]Appender, 0, len(f.secondaries))
	for _, storage := range f.secondaries {
		appender, err := storage.Appender()
		if err != nil {
			return nil, err
		}
		secondaries = append(secondaries, appender)
	}
	return &fanoutAppender{
		primary:     primary,
		secondaries: secondaries,
	}, nil
}

// Close closes the storage and all its underlying resources.
func (f *fanout) Close() error {
	if err := f.primary.Close(); err != nil {
		return err
	}

	// TODO return multiple errors?
	var lastErr error
	for _, storage := range f.secondaries {
		if err := storage.Close(); err != nil {
			lastErr = err
		}
	}
	return lastErr
}

// fanoutAppender implements Appender.
type fanoutAppender struct {
	primary     Appender
	secondaries []Appender
}

func (f *fanoutAppender) Add(l labels.Labels, t int64, v float64) (string, error) {
	ref, err := f.primary.Add(l, t, v)
	if err != nil {
		return ref, err
	}

	for _, appender := range f.secondaries {
		if _, err := appender.Add(l, t, v); err != nil {
			return "", err
		}
	}
	return ref, nil
}

func (f *fanoutAppender) AddFast(l labels.Labels, ref string, t int64, v float64) error {
	if err := f.primary.AddFast(l, ref, t, v); err != nil {
		return err
	}

	for _, appender := range f.secondaries {
		if _, err := appender.Add(l, t, v); err != nil {
			return err
		}
	}
	return nil
}

func (f *fanoutAppender) Commit() (err error) {
	err = f.primary.Commit()

	for _, appender := range f.secondaries {
		if err == nil {
			err = appender.Commit()
		} else {
			if rollbackErr := appender.Rollback(); rollbackErr != nil {
				log.Errorf("Squashed rollback error on commit: %v", rollbackErr)
			}
		}
	}
	return
}

func (f *fanoutAppender) Rollback() (err error) {
	err = f.primary.Rollback()

	for _, appender := range f.secondaries {
		rollbackErr := appender.Rollback()
		if err == nil {
			err = rollbackErr
		} else if rollbackErr != nil {
			log.Errorf("Squashed rollback error on rollback: %v", rollbackErr)
		}
	}
	return nil
}

// mergeQuerier implements Querier.
type mergeQuerier struct {
	queriers []Querier
}

// NewMergeQuerier returns a new Querier that merges results of input queriers.
func NewMergeQuerier(queriers []Querier) Querier {
	return &mergeQuerier{
		queriers: queriers,
	}
}

// Select returns a set of series that matches the given label matchers.
func (q *mergeQuerier) Select(matchers ...*labels.Matcher) SeriesSet {
	seriesSets := make([]SeriesSet, 0, len(q.queriers))
	for _, querier := range q.queriers {
		seriesSets = append(seriesSets, querier.Select(matchers...))
	}
	return newMergeSeriesSet(seriesSets)
}

// LabelValues returns all potential values for a label name.
func (q *mergeQuerier) LabelValues(name string) ([]string, error) {
	var results [][]string
	for _, querier := range q.queriers {
		values, err := querier.LabelValues(name)
		if err != nil {
			return nil, err
		}
		results = append(results, values)
	}
	return mergeStringSlices(results), nil
}

func mergeStringSlices(ss [][]string) []string {
	switch len(ss) {
	case 0:
		return nil
	case 1:
		return ss[0]
	case 2:
		return mergeTwoStringSlices(ss[0], ss[1])
	default:
		halfway := len(ss) / 2
		return mergeTwoStringSlices(
			mergeStringSlices(ss[:halfway]),
			mergeStringSlices(ss[halfway:]),
		)
	}
}

func mergeTwoStringSlices(a, b []string) []string {
	i, j := 0, 0
	result := make([]string, 0, len(a)+len(b))
	for i < len(a) && j < len(b) {
		switch strings.Compare(a[i], b[j]) {
		case 0:
			result = append(result, a[i])
			i++
			j++
		case -1:
			result = append(result, a[i])
			i++
		case 1:
			result = append(result, b[j])
			j++
		}
	}
	result = append(result, a[i:]...)
	result = append(result, b[j:]...)
	return result
}

// Close releases the resources of the Querier.
func (q *mergeQuerier) Close() error {
	// TODO return multiple errors?
	var lastErr error
	for _, querier := range q.queriers {
		if err := querier.Close(); err != nil {
			lastErr = err
		}
	}
	return lastErr
}

// mergeSeriesSet implements SeriesSet
type mergeSeriesSet struct {
	currentLabels labels.Labels
	currentSets   []SeriesSet
	sets          seriesSetHeap
}

func newMergeSeriesSet(sets []SeriesSet) SeriesSet {
	// Sets need to be pre-advanced, so we can introspect the label of the
	// series under the cursor.
	var h seriesSetHeap
	for _, set := range sets {
		if set.Next() {
			heap.Push(&h, set)
		}
	}
	return &mergeSeriesSet{
		sets: h,
	}
}

func (c *mergeSeriesSet) Next() bool {
	// Firstly advance all the current series sets.  If any of them have run out
	// we can drop them, otherwise they should be inserted back into the heap.
	for _, set := range c.currentSets {
		if set.Next() {
			heap.Push(&c.sets, set)
		}
	}
	if len(c.sets) == 0 {
		return false
	}

	// Now, pop items of the heap that have equal label sets.
	c.currentSets = nil
	c.currentLabels = c.sets[0].At().Labels()
	for len(c.sets) > 0 && labels.Equal(c.currentLabels, c.sets[0].At().Labels()) {
		set := heap.Pop(&c.sets).(SeriesSet)
		c.currentSets = append(c.currentSets, set)
	}
	return true
}

func (c *mergeSeriesSet) At() Series {
	series := []Series{}
	for _, seriesSet := range c.currentSets {
		series = append(series, seriesSet.At())
	}
	return &mergeSeries{
		labels: c.currentLabels,
		series: series,
	}
}

func (c *mergeSeriesSet) Err() error {
	for _, set := range c.sets {
		if err := set.Err(); err != nil {
			return err
		}
	}
	return nil
}

type seriesSetHeap []SeriesSet

func (h seriesSetHeap) Len() int      { return len(h) }
func (h seriesSetHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

func (h seriesSetHeap) Less(i, j int) bool {
	a, b := h[i].At().Labels(), h[j].At().Labels()
	return labels.Compare(a, b) < 0
}

func (h *seriesSetHeap) Push(x interface{}) {
	*h = append(*h, x.(SeriesSet))
}

func (h *seriesSetHeap) Pop() interface{} {
	old := *h
	n := len(old)
	x := old[n-1]
	*h = old[0 : n-1]
	return x
}

type mergeSeries struct {
	labels labels.Labels
	series []Series
}

func (m *mergeSeries) Labels() labels.Labels {
	return m.labels
}

func (m *mergeSeries) Iterator() SeriesIterator {
	iterators := make([]SeriesIterator, 0, len(m.series))
	for _, s := range m.series {
		iterators = append(iterators, s.Iterator())
	}
	return newMergeIterator(iterators)
}

type mergeIterator struct {
	iterators []SeriesIterator
	h         seriesIteratorHeap
}

func newMergeIterator(iterators []SeriesIterator) SeriesIterator {
	return &mergeIterator{
		iterators: iterators,
		h:         nil,
	}
}

func (c *mergeIterator) Seek(t int64) bool {
	c.h = seriesIteratorHeap{}
	for _, iter := range c.iterators {
		if iter.Seek(t) {
			heap.Push(&c.h, iter)
		}
	}
	return len(c.h) > 0
}

func (c *mergeIterator) At() (t int64, v float64) {
	if len(c.h) == 0 {
		log.Error("mergeIterator.At() called after .Next() returned false.")
		return 0, 0
	}

	// TODO do I need to dedupe or just merge?
	return c.h[0].At()
}

func (c *mergeIterator) Next() bool {
	if c.h == nil {
		for _, iter := range c.iterators {
			if iter.Next() {
				heap.Push(&c.h, iter)
			}
		}
		return len(c.h) > 0
	}

	if len(c.h) == 0 {
		return false
	}

	iter := heap.Pop(&c.h).(SeriesIterator)
	if iter.Next() {
		heap.Push(&c.h, iter)
	}

	return len(c.h) > 0
}

func (c *mergeIterator) Err() error {
	for _, iter := range c.iterators {
		if err := iter.Err(); err != nil {
			return err
		}
	}
	return nil
}

type seriesIteratorHeap []SeriesIterator

func (h seriesIteratorHeap) Len() int      { return len(h) }
func (h seriesIteratorHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

func (h seriesIteratorHeap) Less(i, j int) bool {
	at, _ := h[i].At()
	bt, _ := h[j].At()
	return at < bt
}

func (h *seriesIteratorHeap) Push(x interface{}) {
	*h = append(*h, x.(SeriesIterator))
}

func (h *seriesIteratorHeap) Pop() interface{} {
	old := *h
	n := len(old)
	x := old[n-1]
	*h = old[0 : n-1]
	return x
}
<|endoftext|>


<commit_before>package store

import (
	"fmt"
	"github.com/danjac/podbaby/models"
	"github.com/danjac/podbaby/store/Godeps/_workspace/src/github.com/jmoiron/sqlx"
	"strings"
)

const maxRecommendations = 20

type ChannelReader interface {
	SelectAll(DataHandler, *[]models.Channel) error
	SelectByCategoryID(DataHandler, *[]models.Channel, int) error
	SelectSubscribed(DataHandler, *[]models.Channel, int) error
	SelectRelated(DataHandler, *[]models.Channel, int) error
	SelectRecommended(DataHandler, *[]models.Channel) error
	SelectRecommendedByUserID(DataHandler, *[]models.Channel, int) error
	Search(DataHandler, *[]models.Channel, string) error
	GetByID(DataHandler, *models.Channel, int) error
	GetByURL(DataHandler, *models.Channel, string) error
}

type ChannelWriter interface {
	Create(DataHandler, *models.Channel) error
	AddCategories(DataHandler, *models.Channel) error
	AddPodcasts(DataHandler, *models.Channel) error
}

type ChannelStore interface {
	ChannelReader
	ChannelWriter
}

type channelSqlStore struct {
	ChannelReader
	ChannelWriter
}

func newChannelStore() ChannelStore {
	return &channelSqlStore{
		ChannelReader: &channelSqlReader{},
		ChannelWriter: &channelSqlWriter{},
	}
}

type channelSqlReader struct{}

func (r *channelSqlReader) SelectAll(dh DataHandler, channels *[]models.Channel) error {
	q := "SELECT id, title, description, url, image, website, num_podcasts FROM channels"
	return sqlx.Select(dh, channels, q)
}

func (r *channelSqlReader) SelectByCategoryID(dh DataHandler, channels *[]models.Channel, categoryID int) error {
	q := `
    SELECT c.id, c.title, c.image, c.description, c.website, c.url, c.num_podcasts
    FROM channels c
    JOIN channels_categories cc
    ON cc.channel_id = c.id
    WHERE cc.category_id=$1
    GROUP BY c.id
    ORDER BY c.title`
	return sqlx.Select(dh, channels, q, categoryID)
}

func (r *channelSqlReader) SelectRelated(dh DataHandler, channels *[]models.Channel, channelID int) error {
	q := `
    SELECT c.id, c.title, c.image, c.description, c.website, c.url, c.num_podcasts
    FROM channels c
    JOIN subscriptions s ON s.channel_id=c.id
    WHERE s.user_id in (
      SELECT user_id FROM subscriptions WHERE channel_id=$1
    ) AND s.channel_id != $1
    GROUP BY c.id
    ORDER BY RANDOM() DESC LIMIT 3`

	return sqlx.Select(dh, channels, q, channelID)
}

func (r *channelSqlReader) SelectRecommended(dh DataHandler, channels *[]models.Channel) error {
	q := `
    SELECT c.id, c.title, c.image, c.description, c.website, c.url, c.num_podcasts
    FROM channels c
    JOIN subscriptions s ON s.channel_id = c.id
    GROUP BY c.id
    ORDER BY COUNT(DISTINCT(s.id)) DESC LIMIT $1
    `
	return sqlx.Select(dh, channels, q, maxRecommendations)
}

func (r *channelSqlReader) SelectRecommendedByUserID(dh DataHandler, channels *[]models.Channel, userID int) error {
	q := `
    WITH user_subs AS (SELECT channel_id FROM subscriptions WHERE user_id=$1)
    SELECT c.id, c.title, c.description, c.image, c.url, c.website, c.num_podcasts
    FROM channels c
    JOIN channels_categories cc ON cc.channel_id=c.id
    WHERE (cc.category_id IN (
       SELECT cc.category_id FROM channels_categories cc
       WHERE cc.channel_id IN (SELECT channel_id FROM user_subs)
    ) AND c.id NOT IN (SELECT channel_id FROM user_subs)
    ) OR ((SELECT COUNT(channel_id) FROM user_subs) = 0)
    GROUP BY c.id
    ORDER BY RANDOM()
    LIMIT $2`
	return sqlx.Select(dh, channels, q, userID, maxRecommendations)
}

func (r *channelSqlReader) SelectSubscribed(dh DataHandler, channels *[]models.Channel, userID int) error {

	q := `
    SELECT c.id, c.title, c.description, c.image, c.url, c.website, c.num_podcasts
    FROM channels c
    JOIN subscriptions s ON s.channel_id = c.id
    WHERE s.user_id=$1 AND title IS NOT NULL AND title != ''
    GROUP BY c.id
    ORDER BY title`
	return sqlx.Select(dh, channels, q, userID)
}

func (r *channelSqlReader) Search(dh DataHandler, channels *[]models.Channel, query string) error {

	q := `
    SELECT c.id, c.title, c.description, c.url, c.image, c.website, c.num_podcasts
    FROM channels c, plainto_tsquery($1) as q
    WHERE (c.tsv @@ q)
    ORDER BY ts_rank_cd(c.tsv, plainto_tsquery($1)) DESC LIMIT 20`
	return sqlx.Select(dh, channels, q, query)
}

func (r *channelSqlReader) GetByURL(dh DataHandler, channel *models.Channel, url string) error {
	q := `
    SELECT id, title, description, url, image, website, num_podcasts
    FROM channels
    WHERE url=$1`
	return sqlx.Get(dh, channel, q, url)
}

func (r *channelSqlReader) GetByID(dh DataHandler, channel *models.Channel, id int) error {
	q := `
    SELECT c.id, c.title, c.description, c.url, c.image, c.website, c.num_podcasts
    FROM channels c
    WHERE id=$1`
	return sqlx.Get(dh, channel, q, id)
}

type channelSqlWriter struct{}

func (w *channelSqlWriter) Create(dh DataHandler, ch *models.Channel) error {

	q := `SELECT upsert_channel (
    :url,
    :title,
    :description,
    :image,
    :keywords,
    :website
    )`

	q, args, err := sqlx.Named(q, ch)
	if err != nil {
		return err
	}

	return dh.QueryRowx(dh.Rebind(q), args...).Scan(&ch.ID)
}

func (w *channelSqlWriter) AddCategories(dh DataHandler, channel *models.Channel) error {
	if len(channel.Categories) == 0 {
		return nil
	}
	args := []interface{}{
		channel.ID,
	}

	params := make([]string, 0, len(channel.Categories))
	for i, category := range channel.Categories {
		params = append(params, fmt.Sprintf("$%v", i+2))
		args = append(args, category)
	}

	q := fmt.Sprintf("SELECT add_categories ($1, ARRAY[%s])", strings.Join(params, ", "))
	_, err := dh.Exec(q, args...)
	return err
}

func (w *channelSqlWriter) AddPodcasts(dh DataHandler, channel *models.Channel) error {

	q := `SELECT insert_podcast (
        :channel_id,
        :guid,
        :title,
        :description,
        :enclosure_url,
        :source,
        :pub_date)`

	stmt, err := dh.PrepareNamed(dh.Rebind(q))
	if err != nil {
		return err
	}

	for _, pc := range channel.Podcasts {
		pc.ChannelID = channel.ID
		err = stmt.QueryRowx(&pc).Scan(&pc.ID)
		if err != nil {
			return err
		}
	}
	return nil

}
<commit_msg>Close stmt<commit_after>package store

import (
	"fmt"
	"github.com/danjac/podbaby/models"
	"github.com/danjac/podbaby/store/Godeps/_workspace/src/github.com/jmoiron/sqlx"
	"strings"
)

const maxRecommendations = 20

type ChannelReader interface {
	SelectAll(DataHandler, *[]models.Channel) error
	SelectByCategoryID(DataHandler, *[]models.Channel, int) error
	SelectSubscribed(DataHandler, *[]models.Channel, int) error
	SelectRelated(DataHandler, *[]models.Channel, int) error
	SelectRecommended(DataHandler, *[]models.Channel) error
	SelectRecommendedByUserID(DataHandler, *[]models.Channel, int) error
	Search(DataHandler, *[]models.Channel, string) error
	GetByID(DataHandler, *models.Channel, int) error
	GetByURL(DataHandler, *models.Channel, string) error
}

type ChannelWriter interface {
	Create(DataHandler, *models.Channel) error
	AddCategories(DataHandler, *models.Channel) error
	AddPodcasts(DataHandler, *models.Channel) error
}

type ChannelStore interface {
	ChannelReader
	ChannelWriter
}

type channelSqlStore struct {
	ChannelReader
	ChannelWriter
}

func newChannelStore() ChannelStore {
	return &channelSqlStore{
		ChannelReader: &channelSqlReader{},
		ChannelWriter: &channelSqlWriter{},
	}
}

type channelSqlReader struct{}

func (r *channelSqlReader) SelectAll(dh DataHandler, channels *[]models.Channel) error {
	q := "SELECT id, title, description, url, image, website, num_podcasts FROM channels"
	return sqlx.Select(dh, channels, q)
}

func (r *channelSqlReader) SelectByCategoryID(dh DataHandler, channels *[]models.Channel, categoryID int) error {
	q := `
    SELECT c.id, c.title, c.image, c.description, c.website, c.url, c.num_podcasts
    FROM channels c
    JOIN channels_categories cc
    ON cc.channel_id = c.id
    WHERE cc.category_id=$1
    GROUP BY c.id
    ORDER BY c.title`
	return sqlx.Select(dh, channels, q, categoryID)
}

func (r *channelSqlReader) SelectRelated(dh DataHandler, channels *[]models.Channel, channelID int) error {
	q := `
    SELECT c.id, c.title, c.image, c.description, c.website, c.url, c.num_podcasts
    FROM channels c
    JOIN subscriptions s ON s.channel_id=c.id
    WHERE s.user_id in (
      SELECT user_id FROM subscriptions WHERE channel_id=$1
    ) AND s.channel_id != $1
    GROUP BY c.id
    ORDER BY RANDOM() DESC LIMIT 3`

	return sqlx.Select(dh, channels, q, channelID)
}

func (r *channelSqlReader) SelectRecommended(dh DataHandler, channels *[]models.Channel) error {
	q := `
    SELECT c.id, c.title, c.image, c.description, c.website, c.url, c.num_podcasts
    FROM channels c
    JOIN subscriptions s ON s.channel_id = c.id
    GROUP BY c.id
    ORDER BY COUNT(DISTINCT(s.id)) DESC LIMIT $1
    `
	return sqlx.Select(dh, channels, q, maxRecommendations)
}

func (r *channelSqlReader) SelectRecommendedByUserID(dh DataHandler, channels *[]models.Channel, userID int) error {
	q := `
    WITH user_subs AS (SELECT channel_id FROM subscriptions WHERE user_id=$1)
    SELECT c.id, c.title, c.description, c.image, c.url, c.website, c.num_podcasts
    FROM channels c
    JOIN channels_categories cc ON cc.channel_id=c.id
    WHERE (cc.category_id IN (
       SELECT cc.category_id FROM channels_categories cc
       WHERE cc.channel_id IN (SELECT channel_id FROM user_subs)
    ) AND c.id NOT IN (SELECT channel_id FROM user_subs)
    ) OR ((SELECT COUNT(channel_id) FROM user_subs) = 0)
    GROUP BY c.id
    ORDER BY RANDOM()
    LIMIT $2`
	return sqlx.Select(dh, channels, q, userID, maxRecommendations)
}

func (r *channelSqlReader) SelectSubscribed(dh DataHandler, channels *[]models.Channel, userID int) error {

	q := `
    SELECT c.id, c.title, c.description, c.image, c.url, c.website, c.num_podcasts
    FROM channels c
    JOIN subscriptions s ON s.channel_id = c.id
    WHERE s.user_id=$1 AND title IS NOT NULL AND title != ''
    GROUP BY c.id
    ORDER BY title`
	return sqlx.Select(dh, channels, q, userID)
}

func (r *channelSqlReader) Search(dh DataHandler, channels *[]models.Channel, query string) error {

	q := `
    SELECT c.id, c.title, c.description, c.url, c.image, c.website, c.num_podcasts
    FROM channels c, plainto_tsquery($1) as q
    WHERE (c.tsv @@ q)
    ORDER BY ts_rank_cd(c.tsv, plainto_tsquery($1)) DESC LIMIT 20`
	return sqlx.Select(dh, channels, q, query)
}

func (r *channelSqlReader) GetByURL(dh DataHandler, channel *models.Channel, url string) error {
	q := `
    SELECT id, title, description, url, image, website, num_podcasts
    FROM channels
    WHERE url=$1`
	return sqlx.Get(dh, channel, q, url)
}

func (r *channelSqlReader) GetByID(dh DataHandler, channel *models.Channel, id int) error {
	q := `
    SELECT c.id, c.title, c.description, c.url, c.image, c.website, c.num_podcasts
    FROM channels c
    WHERE id=$1`
	return sqlx.Get(dh, channel, q, id)
}

type channelSqlWriter struct{}

func (w *channelSqlWriter) Create(dh DataHandler, ch *models.Channel) error {

	q := `SELECT upsert_channel (
    :url,
    :title,
    :description,
    :image,
    :keywords,
    :website
    )`

	q, args, err := sqlx.Named(q, ch)
	if err != nil {
		return err
	}

	return dh.QueryRowx(dh.Rebind(q), args...).Scan(&ch.ID)
}

func (w *channelSqlWriter) AddCategories(dh DataHandler, channel *models.Channel) error {
	if len(channel.Categories) == 0 {
		return nil
	}
	args := []interface{}{
		channel.ID,
	}

	params := make([]string, 0, len(channel.Categories))
	for i, category := range channel.Categories {
		params = append(params, fmt.Sprintf("$%v", i+2))
		args = append(args, category)
	}

	q := fmt.Sprintf("SELECT add_categories ($1, ARRAY[%s])", strings.Join(params, ", "))
	_, err := dh.Exec(q, args...)
	return err
}

func (w *channelSqlWriter) AddPodcasts(dh DataHandler, channel *models.Channel) error {

	q := `SELECT insert_podcast (
        :channel_id,
        :guid,
        :title,
        :description,
        :enclosure_url,
        :source,
        :pub_date)`

	stmt, err := dh.PrepareNamed(dh.Rebind(q))
	defer stmt.Close()

	if err != nil {
		return err
	}

	for _, pc := range channel.Podcasts {
		pc.ChannelID = channel.ID
		err = stmt.QueryRowx(&pc).Scan(&pc.ID)
		if err != nil {
			return err
		}
	}
	return nil

}
<|endoftext|>


<commit_before>package server

import (
	log "github.com/Sirupsen/logrus"
	"github.com/Syleron/Pulse/src/client"
	"github.com/Syleron/Pulse/src/plugins"
	hc "github.com/Syleron/Pulse/src/proto"
	"github.com/Syleron/Pulse/src/security"
	"github.com/Syleron/Pulse/src/structures"
	"github.com/Syleron/Pulse/src/utils"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/grpclog"
	"io/ioutil"
	oglog "log"
	"net"
	"os"
	"sync"
	"time"
)

var (
	Config        structures.Configuration
	Plugins		  []structures.PluginHC
	Role          string
	Lis           *net.Listener
	ServerIP      string
	ServerPort    string
	Last_response time.Time                            // Last time we got a health check from the master
	Status        hc.HealthCheckResponse_ServingStatus // The status of the cluster
)

type server struct {
	mu     sync.Mutex
	status hc.HealthCheckResponse_ServingStatus
}

func (s *server) Check(ctx context.Context, in *hc.HealthCheckRequest) (*hc.HealthCheckResponse, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	switch in.Request {
	case hc.HealthCheckRequest_SETUP:
		log.Info("Recieved setup request from master..")

		if configureCluster() {
			// Reset the last_response time
			Last_response = time.Now()
			// Successfully configured the cluster... now to monitor for health checks
			go monitorResponses()
			// We return unknown as the request was not successful.
			return &hc.HealthCheckResponse{
				Status: hc.HealthCheckResponse_CONFIGURED,
			}, nil
		} else {
			// We return unknown as the request was not successful.
			return nil, grpc.Errorf(codes.PermissionDenied, "Slave has already been configured.")
			//return &hc.HealthCheckResponse{
			//	Status: hc.HealthCheckResponse_UNKNOWN,
			//}, nil
		}
	case hc.HealthCheckRequest_STATUS:
		// Make sure we are configured
		if Config.Local.Configured {
			// Reset the last_response time
			Last_response = time.Now()

			return &hc.HealthCheckResponse{
				Status: hc.HealthCheckResponse_HEALTHY,
			}, nil
		} else {
			return nil, grpc.Errorf(codes.PermissionDenied, "A setup request must be made before the slave can respond to health checks.")
		}
	default:
		return nil, grpc.Errorf(codes.NotFound, "unknown request")
	}
}

/**
 * Function is used to configure a clustered pair
 */
func configureCluster() bool {
	// Check to see if we can configure this node
	// make sure we are a slave
	if Config.Local.Role == "slave" {
		// Are we in a configured state already?
		if Config.Local.Configured == false {
			// Set the local value to configured
			Config.Local.Configured = true

			// Save
			utils.SaveConfig(Config)

			log.Info("Successfully configured slave.")

			return true
		} else {
			return false
		}
	}
	return false
}

/*
 * Setup Function used to initialise the server
 */
func Setup() {
	// Load the config and validate
	Config = utils.LoadConfig()

	// Load plugins
	var err error
	Plugins, err = plugins.Load()

	if err != nil {
		log.Errorf("Failed to load plugins: %v", err)
		os.Exit(1)
	}

	// Setup local variables
	setupLocalVariables()

	lis, err := net.Listen("tcp", ":"+ServerPort)
	grpclog.SetLogger(oglog.New(ioutil.Discard, "", 0))

	if err != nil {
		log.Errorf("failed to listen: %v", err)
	}

	Lis = &lis

	var s *grpc.Server

	// Create folder and keys if we have to
	// Note: This should probably check if the key files exist as well.
	if utils.CreateFolder("./certs") {
		log.Warn("Missing TLS keys.")
		security.Generate()
	}

	// Specify GRPC credentials
	creds, err := credentials.NewServerTLSFromFile("./certs/server.crt", "./certs/server.key")

	if err != nil {
		log.Fatal("Could not load TLS keys: ", err)
		os.Exit(1)
	}

	// Start GRPC server
	s = grpc.NewServer(grpc.Creds(creds))

	// Log message
	log.Info(Role + " initialised on port " + ServerPort)

	hc.RegisterRequesterServer(s, &server{})

	// If we are a slave.. we need to set the starting time
	// and fail-over checker
	// Note: as go routine otherwise the server doesn't serve!
	go func() {
		if Config.Local.Role == "slave" && Config.Local.Configured {
			Last_response = time.Now()
			monitorResponses()
		}
	}()

	s.Serve(*Lis)
}

/**
 * Slave function - used to monitor when the last health check we received.
 */
func monitorResponses() {
	for _ = range time.Tick(time.Duration(Config.Local.FOCInterval) * time.Millisecond) {
		elapsed := int64(time.Since(Last_response)) / 1e9

		if int(elapsed) > 0 && int(elapsed)%4 == 0 {
			log.Warn("No health checks are being made.. Perhaps a failover is required?")
		}

		// If x seconds has gone by.. something is wrong.
		if int(elapsed) >= Config.Local.FOCLimit {
			var addHCSuccess bool = false

			for _, plugin := range Plugins {
			    success, enabled := plugin.Send()

			    if enabled && success {
					log.Warn(plugin.Name() + " health check successful! Assuming master is still available..")
			    	addHCSuccess = true
			    	break
			    }
			}

			if !addHCSuccess {
				// Nothing has worked.. assume the master has failed. Fail over.
				log.Info("Attempting a failover..")
				failover()
				break
			} else {
				Last_response = time.Now()
			}
		}
	}
}

/**
 * Slave Function - Used when the master is no longer around.
 */
func failover() {
	if Config.Local.Role == "slave" {
		// update local role
		Config.Local.Role = "master"

		// Update local status
		Status = hc.HealthCheckResponse_FAILVER

		// Update master and slave configuration
		Config.Cluster.Nodes.Slave.IP = Config.Cluster.Nodes.Master.IP
		Config.Cluster.Nodes.Slave.Port = Config.Cluster.Nodes.Master.Port

		Config.Cluster.Nodes.Master.IP = ServerIP
		Config.Cluster.Nodes.Master.Port = ServerPort

		// Save to file
		utils.SaveConfig(Config)

		log.Info("Completed. Local role has been re-assigned as master..")

		// Close server
		var serverListener net.Listener
		serverListener = *Lis
		serverListener.Close()

		time.Sleep(1000)

		// Re-setup the server
		go Setup()
		// Tell the client to reload the config
		go client.ForceConfigReload()
	}
}

/**
 * Function to setup the local variables for this client.
 */
func setupLocalVariables() {
	switch Config.Local.Role {
	case "master":
		ServerIP = Config.Cluster.Nodes.Master.IP
		ServerPort = Config.Cluster.Nodes.Master.Port
		Role = "master"
	case "slave":
		ServerIP = Config.Cluster.Nodes.Slave.IP
		ServerPort = Config.Cluster.Nodes.Slave.Port
		Role = "slave"
	default:
		panic("Unable to initiate due to invalid role set in configuration.")
	}

	// Local configuration status
	if Config.Local.Configured {
		Status = hc.HealthCheckResponse_CONFIGURED
	} else {
		Status = hc.HealthCheckResponse_UNCONFIGURED
	}
}
<commit_msg>Updated server struct<commit_after>package server

import (
	log "github.com/Sirupsen/logrus"
	"github.com/Syleron/Pulse/src/client"
	"github.com/Syleron/Pulse/src/plugins"
	hc "github.com/Syleron/Pulse/src/proto"
	"github.com/Syleron/Pulse/src/security"
	"github.com/Syleron/Pulse/src/structures"
	"github.com/Syleron/Pulse/src/utils"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/grpclog"
	"io/ioutil"
	oglog "log"
	"net"
	"os"
	"sync"
	"time"
)

var (
	Config        structures.Configuration
	Plugins		  []structures.PluginHC
	Role          string
	Lis           *net.Listener
	ServerIP      string
	ServerPort    string
	Last_response time.Time                            // Last time we got a health check from the master
	Status        hc.HealthCheckResponse_ServingStatus // The status of the cluster
)

type server struct {
	sync.Mutex
	status hc.HealthCheckResponse_ServingStatus
}

func (s *server) Check(ctx context.Context, in *hc.HealthCheckRequest) (*hc.HealthCheckResponse, error) {
	s.Lock()
	defer s.Unlock()

	switch in.Request {
	case hc.HealthCheckRequest_SETUP:
		log.Info("Recieved setup request from master..")

		if configureCluster() {
			// Reset the last_response time
			Last_response = time.Now()
			// Successfully configured the cluster... now to monitor for health checks
			go monitorResponses()
			// We return unknown as the request was not successful.
			return &hc.HealthCheckResponse{
				Status: hc.HealthCheckResponse_CONFIGURED,
			}, nil
		} else {
			// We return unknown as the request was not successful.
			return nil, grpc.Errorf(codes.PermissionDenied, "Slave has already been configured.")
			//return &hc.HealthCheckResponse{
			//	Status: hc.HealthCheckResponse_UNKNOWN,
			//}, nil
		}
	case hc.HealthCheckRequest_STATUS:
		// Make sure we are configured
		if Config.Local.Configured {
			// Reset the last_response time
			Last_response = time.Now()

			return &hc.HealthCheckResponse{
				Status: hc.HealthCheckResponse_HEALTHY,
			}, nil
		} else {
			return nil, grpc.Errorf(codes.PermissionDenied, "A setup request must be made before the slave can respond to health checks.")
		}
	default:
		return nil, grpc.Errorf(codes.NotFound, "unknown request")
	}
}

/**
 * Function is used to configure a clustered pair
 */
func configureCluster() bool {
	// Check to see if we can configure this node
	// make sure we are a slave
	if Config.Local.Role == "slave" {
		// Are we in a configured state already?
		if Config.Local.Configured == false {
			// Set the local value to configured
			Config.Local.Configured = true

			// Save
			utils.SaveConfig(Config)

			log.Info("Successfully configured slave.")

			return true
		} else {
			return false
		}
	}
	return false
}

/*
 * Setup Function used to initialise the server
 */
func Setup() {
	// Load the config and validate
	Config = utils.LoadConfig()

	// Load plugins
	var err error
	Plugins, err = plugins.Load()

	if err != nil {
		log.Errorf("Failed to load plugins: %v", err)
		os.Exit(1)
	}

	// Setup local variables
	setupLocalVariables()

	lis, err := net.Listen("tcp", ":"+ServerPort)
	grpclog.SetLogger(oglog.New(ioutil.Discard, "", 0))

	if err != nil {
		log.Errorf("failed to listen: %v", err)
	}

	Lis = &lis

	var s *grpc.Server

	// Create folder and keys if we have to
	// Note: This should probably check if the key files exist as well.
	if utils.CreateFolder("./certs") {
		log.Warn("Missing TLS keys.")
		security.Generate()
	}

	// Specify GRPC credentials
	creds, err := credentials.NewServerTLSFromFile("./certs/server.crt", "./certs/server.key")

	if err != nil {
		log.Fatal("Could not load TLS keys: ", err)
		os.Exit(1)
	}

	// Start GRPC server
	s = grpc.NewServer(grpc.Creds(creds))

	// Log message
	log.Info(Role + " initialised on port " + ServerPort)

	hc.RegisterRequesterServer(s, &server{})

	// If we are a slave.. we need to set the starting time
	// and fail-over checker
	// Note: as go routine otherwise the server doesn't serve!
	go func() {
		if Config.Local.Role == "slave" && Config.Local.Configured {
			Last_response = time.Now()
			monitorResponses()
		}
	}()

	s.Serve(*Lis)
}

/**
 * Slave function - used to monitor when the last health check we received.
 */
func monitorResponses() {
	for _ = range time.Tick(time.Duration(Config.Local.FOCInterval) * time.Millisecond) {
		elapsed := int64(time.Since(Last_response)) / 1e9

		if int(elapsed) > 0 && int(elapsed)%4 == 0 {
			log.Warn("No health checks are being made.. Perhaps a failover is required?")
		}

		// If x seconds has gone by.. something is wrong.
		if int(elapsed) >= Config.Local.FOCLimit {
			var addHCSuccess bool = false

			for _, plugin := range Plugins {
			    success, enabled := plugin.Send()

			    if enabled && success {
					log.Warn(plugin.Name() + " health check successful! Assuming master is still available..")
			    	addHCSuccess = true
			    	break
			    }
			}

			if !addHCSuccess {
				// Nothing has worked.. assume the master has failed. Fail over.
				log.Info("Attempting a failover..")
				failover()
				break
			} else {
				Last_response = time.Now()
			}
		}
	}
}

/**
 * Slave Function - Used when the master is no longer around.
 */
func failover() {
	if Config.Local.Role == "slave" {
		// update local role
		Config.Local.Role = "master"

		// Update local status
		Status = hc.HealthCheckResponse_FAILVER

		// Update master and slave configuration
		Config.Cluster.Nodes.Slave.IP = Config.Cluster.Nodes.Master.IP
		Config.Cluster.Nodes.Slave.Port = Config.Cluster.Nodes.Master.Port

		Config.Cluster.Nodes.Master.IP = ServerIP
		Config.Cluster.Nodes.Master.Port = ServerPort

		// Save to file
		utils.SaveConfig(Config)

		log.Info("Completed. Local role has been re-assigned as master..")

		// Close server
		var serverListener net.Listener
		serverListener = *Lis
		serverListener.Close()

		time.Sleep(1000)

		// Re-setup the server
		go Setup()
		// Tell the client to reload the config
		go client.ForceConfigReload()
	}
}

/**
 * Function to setup the local variables for this client.
 */
func setupLocalVariables() {
	switch Config.Local.Role {
	case "master":
		ServerIP = Config.Cluster.Nodes.Master.IP
		ServerPort = Config.Cluster.Nodes.Master.Port
		Role = "master"
	case "slave":
		ServerIP = Config.Cluster.Nodes.Slave.IP
		ServerPort = Config.Cluster.Nodes.Slave.Port
		Role = "slave"
	default:
		panic("Unable to initiate due to invalid role set in configuration.")
	}

	// Local configuration status
	if Config.Local.Configured {
		Status = hc.HealthCheckResponse_CONFIGURED
	} else {
		Status = hc.HealthCheckResponse_UNCONFIGURED
	}
}
<|endoftext|>


<commit_before>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

// Package semtech provides useful methods and types to handle communications with a gateway.
//
// This package relies on the SemTech Protocol 1.2 accessible on github: https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT
package semtech

import (
	"time"
)

type DeviceAddress [4]byte

// RXPK represents an uplink json message format sent by the gateway
type RXPK struct {
	Chan *uint      `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer)
	Codr *string    `json:"codr,omitempty"` // LoRa ECC coding rate identifier
	Data *string    `json:"data,omitempty"` // Base64 encoded RF packet payload, padded
	Datr *string    `json:"-"`              // FSK datarate (unsigned in bit per second) || LoRa datarate identifier
	Freq *float64   `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision)
	Lsnr *float64   `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision)
	Modu *string    `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK"
	Rfch *uint      `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer)
	Rssi *int       `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision)
	Size *uint      `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer)
	Stat *int       `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC
	Time *time.Time `json:"-"`              // UTC time of pkt RX, us precision, ISO 8601 'compact' format
	Tmst *uint      `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned)
}

// TXPK represents a downlink j,omitemptyson message format received by the gateway.
// Most field are optional.
type TXPK struct {
	Codr *string    `json:"codr,omitempty"`  // LoRa ECC coding rate identifier
	Data *string    `json:"data,omirtmepty"` // Base64 encoded RF packet payload, padding optional
	Datr *string    `json:"-"`               // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second)
	Fdev *uint      `json:"fdev,omitempty"`  // FSK frequency deviation (unsigned integer, in Hz)
	Freq *float64   `json:"freq,omitempty"`  // TX central frequency in MHz (unsigned float, Hz precision)
	Imme *bool      `json:"imme,omitempty"`  // Send packet immediately (will ignore tmst & time)
	Ipol *bool      `json:"ipol,omitempty"`  // Lora modulation polarization inversion
	Modu *string    `json:"modu,omitempty"`  // Modulation identifier "LORA" or "FSK"
	Ncrc *bool      `json:"ncrc,omitempty"`  // If true, disable the CRC of the physical layer (optional)
	Powe *uint      `json:"powe,omitempty"`  // TX output power in dBm (unsigned integer, dBm precision)
	Prea *uint      `json:"prea,omitempty"`  // RF preamble size (unsigned integer)
	Rfch *uint      `json:"rfch,omitempty"`  // Concentrator "RF chain" used for TX (unsigned integer)
	Size *uint      `json:"size,omitempty"`  // RF packet payload size in bytes (unsigned integer)
	Time *time.Time `json:"-"`               // Send packet at a certain time (GPS synchronization required)
	Tmst *uint      `json:"tmst,omitempty"`  // Send packet on a certain timestamp value (will ignore time)
}

// Stat represents a status json message format sent by the gateway
type Stat struct {
	Ackr *float64   `json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged
	Alti *int       `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer)
	Dwnb *uint      `json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer)
	Lati *float64   `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +)
	Long *float64   `json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +)
	Rxfw *uint      `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer)
	Rxnb *uint      `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer)
	Rxok *uint      `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC
	Time *time.Time `json:"-"`              // UTC 'system' time of the gateway, ISO 8601 'expanded' format
	Txnb *uint      `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer)
}

// Packet as seen by the gateway.
type Packet struct {
	Version    byte     // Protocol version, should always be 1 here
	Token      []byte   // Random number generated by the gateway on some request. 2-bytes long.
	Identifier byte     // Packet's command identifier
	GatewayId  []byte   // Source gateway's identifier (Only PULL_DATA and PUSH_DATA)
	Payload    *Payload // JSON payload transmitted if any, nil otherwise
}

// Payload refers to the JSON payload sent by a gateway or a server.
type Payload struct {
	Raw  []byte `json:"-"`              // The raw unparsed response
	RXPK []RXPK `json:"rxpk,omitempty"` // A list of RXPK messages transmitted if any
	Stat *Stat  `json:"stat,omitempty"` // A Stat message transmitted if any
	TXPK *TXPK  `json:"txpk,omitempty"` // A TXPK message transmitted if any
}

// Available packet commands
const (
	PUSH_DATA byte = iota // Sent by the gateway for an uplink message with data
	PUSH_ACK              // Sent by the gateway's recipient in response to a PUSH_DATA
	PULL_DATA             // Sent periodically by the gateway to keep a connection open
	PULL_RESP             // Sent by the gateway's recipient to transmit back data to the Gateway
	PULL_ACK              // Sent by the gateway's recipient in response to PULL_DATA
)

// Protocol version in use
const VERSION = 0x01
<commit_msg>[refactor.semtech] Fix typo in semtech package<commit_after>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

// Package semtech provides useful methods and types to handle communications with a gateway.
//
// This package relies on the SemTech Protocol 1.2 accessible on github: https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT
package semtech

import (
	"time"
)

type DeviceAddress [4]byte

// RXPK represents an uplink json message format sent by the gateway
type RXPK struct {
	Chan *uint      `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer)
	Codr *string    `json:"codr,omitempty"` // LoRa ECC coding rate identifier
	Data *string    `json:"data,omitempty"` // Base64 encoded RF packet payload, padded
	Datr *string    `json:"-"`              // FSK datarate (unsigned in bit per second) || LoRa datarate identifier
	Freq *float64   `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision)
	Lsnr *float64   `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision)
	Modu *string    `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK"
	Rfch *uint      `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer)
	Rssi *int       `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision)
	Size *uint      `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer)
	Stat *int       `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC
	Time *time.Time `json:"-"`              // UTC time of pkt RX, us precision, ISO 8601 'compact' format
	Tmst *uint      `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned)
}

// TXPK represents a downlink j,omitemptyson message format received by the gateway.
// Most field are optional.
type TXPK struct {
	Codr *string    `json:"codr,omitempty"` // LoRa ECC coding rate identifier
	Data *string    `json:"data,omitempty"` // Base64 encoded RF packet payload, padding optional
	Datr *string    `json:"-"`              // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second)
	Fdev *uint      `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz)
	Freq *float64   `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision)
	Imme *bool      `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time)
	Ipol *bool      `json:"ipol,omitempty"` // Lora modulation polarization inversion
	Modu *string    `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK"
	Ncrc *bool      `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional)
	Powe *uint      `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision)
	Prea *uint      `json:"prea,omitempty"` // RF preamble size (unsigned integer)
	Rfch *uint      `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer)
	Size *uint      `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer)
	Time *time.Time `json:"-"`              // Send packet at a certain time (GPS synchronization required)
	Tmst *uint      `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time)
}

// Stat represents a status json message format sent by the gateway
type Stat struct {
	Ackr *float64   `json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged
	Alti *int       `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer)
	Dwnb *uint      `json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer)
	Lati *float64   `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +)
	Long *float64   `json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +)
	Rxfw *uint      `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer)
	Rxnb *uint      `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer)
	Rxok *uint      `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC
	Time *time.Time `json:"-"`              // UTC 'system' time of the gateway, ISO 8601 'expanded' format
	Txnb *uint      `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer)
}

// Packet as seen by the gateway.
type Packet struct {
	Version    byte     // Protocol version, should always be 1 here
	Token      []byte   // Random number generated by the gateway on some request. 2-bytes long.
	Identifier byte     // Packet's command identifier
	GatewayId  []byte   // Source gateway's identifier (Only PULL_DATA and PUSH_DATA)
	Payload    *Payload // JSON payload transmitted if any, nil otherwise
}

// Payload refers to the JSON payload sent by a gateway or a server.
type Payload struct {
	Raw  []byte `json:"-"`              // The raw unparsed response
	RXPK []RXPK `json:"rxpk,omitempty"` // A list of RXPK messages transmitted if any
	Stat *Stat  `json:"stat,omitempty"` // A Stat message transmitted if any
	TXPK *TXPK  `json:"txpk,omitempty"` // A TXPK message transmitted if any
}

// Available packet commands
const (
	PUSH_DATA byte = iota // Sent by the gateway for an uplink message with data
	PUSH_ACK              // Sent by the gateway's recipient in response to a PUSH_DATA
	PULL_DATA             // Sent periodically by the gateway to keep a connection open
	PULL_RESP             // Sent by the gateway's recipient to transmit back data to the Gateway
	PULL_ACK              // Sent by the gateway's recipient in response to PULL_DATA
)

// Protocol version in use
const VERSION = 0x01
<|endoftext|>


<commit_before>// Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
//
// 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 server

import (
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/eapache/channels"
	"github.com/osrg/gobgp/config"
	"github.com/osrg/gobgp/packet/bgp"
	"github.com/osrg/gobgp/table"
	"github.com/stretchr/testify/assert"
	"net"
	"strconv"
	"testing"
	"time"
)

type MockConnection struct {
	net.Conn
	recvCh    chan chan byte
	sendBuf   [][]byte
	currentCh chan byte
	isClosed  bool
	wait      int
}

func NewMockConnection() *MockConnection {
	m := &MockConnection{
		recvCh:   make(chan chan byte, 128),
		sendBuf:  make([][]byte, 0),
		isClosed: false,
	}
	return m
}

func (m *MockConnection) SetWriteDeadline(t time.Time) error {
	return nil
}

func (m *MockConnection) setData(data []byte) int {
	dataChan := make(chan byte, 4096)
	for _, b := range data {
		dataChan <- b
	}
	m.recvCh <- dataChan
	return len(dataChan)
}

func (m *MockConnection) Read(buf []byte) (int, error) {

	if m.isClosed {
		return 0, fmt.Errorf("already closed")
	}

	if m.currentCh == nil {
		m.currentCh = <-m.recvCh
	}

	length := 0
	rest := len(buf)
	for i := 0; i < rest; i++ {
		if len(m.currentCh) > 0 {
			val := <-m.currentCh
			buf[i] = val
			length++
		} else {
			m.currentCh = nil
			break
		}
	}

	fmt.Printf("%d bytes read from peer\n", length)
	return length, nil
}

func (m *MockConnection) Write(buf []byte) (int, error) {
	time.Sleep(time.Duration(m.wait) * time.Millisecond)
	m.sendBuf = append(m.sendBuf, buf)
	msg, _ := bgp.ParseBGPMessage(buf)
	fmt.Printf("%d bytes written by gobgp  message type : %s\n", len(buf), showMessageType(msg.Header.Type))
	return len(buf), nil
}

func showMessageType(t uint8) string {
	switch t {
	case bgp.BGP_MSG_KEEPALIVE:
		return "BGP_MSG_KEEPALIVE"
	case bgp.BGP_MSG_NOTIFICATION:
		return "BGP_MSG_NOTIFICATION"
	case bgp.BGP_MSG_OPEN:
		return "BGP_MSG_OPEN"
	case bgp.BGP_MSG_UPDATE:
		return "BGP_MSG_UPDATE"
	case bgp.BGP_MSG_ROUTE_REFRESH:
		return "BGP_MSG_ROUTE_REFRESH"
	}
	return strconv.Itoa(int(t))
}

func (m *MockConnection) Close() error {
	fmt.Printf("close called\n")
	if !m.isClosed {
		close(m.recvCh)
		m.isClosed = true
	}
	return nil
}

func (m *MockConnection) LocalAddr() net.Addr {
	return &net.TCPAddr{
		IP:   net.ParseIP("10.10.10.10"),
		Port: bgp.BGP_PORT}
}

func TestReadAll(t *testing.T) {
	assert := assert.New(t)
	m := NewMockConnection()
	msg := open()
	expected1, _ := msg.Header.Serialize()
	expected2, _ := msg.Body.Serialize()

	pushBytes := func() {
		fmt.Println("push 5 bytes")
		m.setData(expected1[0:5])
		fmt.Println("push rest")
		m.setData(expected1[5:])
		fmt.Println("push bytes at once")
		m.setData(expected2)
	}

	go pushBytes()

	var actual1 []byte
	actual1, _ = readAll(m, bgp.BGP_HEADER_LENGTH)
	fmt.Println(actual1)
	assert.Equal(expected1, actual1)

	var actual2 []byte
	actual2, _ = readAll(m, len(expected2))
	fmt.Println(actual2)
	assert.Equal(expected2, actual2)
}

func TestFSMHandlerOpensent_HoldTimerExpired(t *testing.T) {
	assert := assert.New(t)
	m := NewMockConnection()

	p, h := makePeerAndHandler()

	// push mock connection
	p.fsm.conn = m
	p.fsm.h = h

	// set keepalive ticker
	p.fsm.pConf.Timers.State.NegotiatedHoldTime = 3

	// set holdtime
	p.fsm.opensentHoldTime = 2

	state, _ := h.opensent()

	assert.Equal(bgp.BGP_FSM_IDLE, state)
	lastMsg := m.sendBuf[len(m.sendBuf)-1]
	sent, _ := bgp.ParseBGPMessage(lastMsg)
	assert.Equal(uint8(bgp.BGP_MSG_NOTIFICATION), sent.Header.Type)
	assert.Equal(uint8(bgp.BGP_ERROR_HOLD_TIMER_EXPIRED), sent.Body.(*bgp.BGPNotification).ErrorCode)

}

func TestFSMHandlerOpenconfirm_HoldTimerExpired(t *testing.T) {
	assert := assert.New(t)
	m := NewMockConnection()

	p, h := makePeerAndHandler()

	// push mock connection
	p.fsm.conn = m
	p.fsm.h = h

	// set up keepalive ticker
	p.fsm.pConf.Timers.Config.KeepaliveInterval = 1

	// set holdtime
	p.fsm.pConf.Timers.State.NegotiatedHoldTime = 2
	state, _ := h.openconfirm()

	assert.Equal(bgp.BGP_FSM_IDLE, state)
	lastMsg := m.sendBuf[len(m.sendBuf)-1]
	sent, _ := bgp.ParseBGPMessage(lastMsg)
	assert.Equal(uint8(bgp.BGP_MSG_NOTIFICATION), sent.Header.Type)
	assert.Equal(uint8(bgp.BGP_ERROR_HOLD_TIMER_EXPIRED), sent.Body.(*bgp.BGPNotification).ErrorCode)

}

func TestFSMHandlerEstablish_HoldTimerExpired(t *testing.T) {
	assert := assert.New(t)
	m := NewMockConnection()

	p, h := makePeerAndHandler()

	// push mock connection
	p.fsm.conn = m
	p.fsm.h = h

	// set keepalive ticker
	p.fsm.pConf.Timers.State.NegotiatedHoldTime = 3

	msg := keepalive()
	header, _ := msg.Header.Serialize()
	body, _ := msg.Body.Serialize()

	pushPackets := func() {
		// first keepalive from peer
		m.setData(header)
		m.setData(body)
	}

	// set holdtime
	p.fsm.pConf.Timers.Config.HoldTime = 2
	p.fsm.pConf.Timers.State.NegotiatedHoldTime = 2

	go pushPackets()
	state, _ := h.established()
	time.Sleep(time.Second * 1)
	assert.Equal(bgp.BGP_FSM_IDLE, state)
	lastMsg := m.sendBuf[len(m.sendBuf)-1]
	sent, _ := bgp.ParseBGPMessage(lastMsg)
	assert.Equal(uint8(bgp.BGP_MSG_NOTIFICATION), sent.Header.Type)
	assert.Equal(uint8(bgp.BGP_ERROR_HOLD_TIMER_EXPIRED), sent.Body.(*bgp.BGPNotification).ErrorCode)
}

func TestFSMHandlerOpenconfirm_HoldtimeZero(t *testing.T) {
	log.SetLevel(log.DebugLevel)
	assert := assert.New(t)
	m := NewMockConnection()

	p, h := makePeerAndHandler()

	// push mock connection
	p.fsm.conn = m
	p.fsm.h = h

	// set up keepalive ticker
	p.fsm.pConf.Timers.Config.KeepaliveInterval = 1
	// set holdtime
	p.fsm.pConf.Timers.State.NegotiatedHoldTime = 0
	go h.openconfirm()

	time.Sleep(100 * time.Millisecond)

	assert.Equal(0, len(m.sendBuf))

}

func TestFSMHandlerEstablished_HoldtimeZero(t *testing.T) {
	log.SetLevel(log.DebugLevel)
	assert := assert.New(t)
	m := NewMockConnection()

	p, h := makePeerAndHandler()

	// push mock connection
	p.fsm.conn = m
	p.fsm.h = h

	// set holdtime
	p.fsm.pConf.Timers.State.NegotiatedHoldTime = 0

	go h.established()

	time.Sleep(100 * time.Millisecond)

	assert.Equal(0, len(m.sendBuf))
}

func TestCheckOwnASLoop(t *testing.T) {
	assert := assert.New(t)
	aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{65100})}
	aspath := bgp.NewPathAttributeAsPath(aspathParam)
	assert.False(hasOwnASLoop(65100, 10, aspath))
	assert.True(hasOwnASLoop(65100, 0, aspath))
	assert.False(hasOwnASLoop(65200, 0, aspath))
}

func makePeerAndHandler() (*Peer, *FSMHandler) {
	p := &Peer{
		fsm:      NewFSM(&config.Global{}, &config.Neighbor{}, table.NewRoutingPolicy()),
		outgoing: channels.NewInfiniteChannel(),
	}

	h := &FSMHandler{
		fsm:      p.fsm,
		errorCh:  make(chan FsmStateReason, 2),
		incoming: channels.NewInfiniteChannel(),
		outgoing: p.outgoing,
	}

	return p, h

}

func open() *bgp.BGPMessage {
	p1 := bgp.NewOptionParameterCapability(
		[]bgp.ParameterCapabilityInterface{bgp.NewCapRouteRefresh()})
	p2 := bgp.NewOptionParameterCapability(
		[]bgp.ParameterCapabilityInterface{bgp.NewCapMultiProtocol(bgp.RF_IPv4_UC)})
	g := &bgp.CapGracefulRestartTuple{AFI: 4, SAFI: 2, Flags: 3}
	p3 := bgp.NewOptionParameterCapability(
		[]bgp.ParameterCapabilityInterface{bgp.NewCapGracefulRestart(true, true, 100,
			[]*bgp.CapGracefulRestartTuple{g})})
	p4 := bgp.NewOptionParameterCapability(
		[]bgp.ParameterCapabilityInterface{bgp.NewCapFourOctetASNumber(100000)})
	return bgp.NewBGPOpenMessage(11033, 303, "100.4.10.3",
		[]bgp.OptionParameterInterface{p1, p2, p3, p4})
}

func keepalive() *bgp.BGPMessage {
	return bgp.NewBGPKeepAliveMessage()
}
<commit_msg>server/fsm_test.go: fix race condition during tests<commit_after>// Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
//
// 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 server

import (
	"errors"
	"net"
	"strconv"
	"sync"
	"testing"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/eapache/channels"
	"github.com/osrg/gobgp/config"
	"github.com/osrg/gobgp/packet/bgp"
	"github.com/osrg/gobgp/table"
	"github.com/stretchr/testify/assert"
)

type MockConnection struct {
	*testing.T
	net.Conn
	recvCh    chan chan byte
	sendBuf   [][]byte
	currentCh chan byte
	isClosed  bool
	wait      int
	mtx       sync.Mutex
}

func NewMockConnection(t *testing.T) *MockConnection {
	m := &MockConnection{
		T:        t,
		recvCh:   make(chan chan byte, 128),
		sendBuf:  make([][]byte, 0),
		isClosed: false,
	}
	return m
}

func (m *MockConnection) SetWriteDeadline(t time.Time) error {
	return nil
}

func (m *MockConnection) setData(data []byte) int {
	dataChan := make(chan byte, 4096)
	for _, b := range data {
		dataChan <- b
	}
	m.recvCh <- dataChan
	return len(dataChan)
}

func (m *MockConnection) Read(buf []byte) (int, error) {
	m.mtx.Lock()
	closed := m.isClosed
	m.mtx.Unlock()
	if closed {
		return 0, errors.New("already closed")
	}

	if m.currentCh == nil {
		m.currentCh = <-m.recvCh
	}

	length := 0
	rest := len(buf)
	for i := 0; i < rest; i++ {
		if len(m.currentCh) > 0 {
			val := <-m.currentCh
			buf[i] = val
			length++
		} else {
			m.currentCh = nil
			break
		}
	}

	m.Logf("%d bytes read from peer", length)
	return length, nil
}

func (m *MockConnection) Write(buf []byte) (int, error) {
	time.Sleep(time.Duration(m.wait) * time.Millisecond)
	m.sendBuf = append(m.sendBuf, buf)
	msg, _ := bgp.ParseBGPMessage(buf)
	m.Logf("%d bytes written by gobgp  message type : %s",
		len(buf), showMessageType(msg.Header.Type))
	return len(buf), nil
}

func showMessageType(t uint8) string {
	switch t {
	case bgp.BGP_MSG_KEEPALIVE:
		return "BGP_MSG_KEEPALIVE"
	case bgp.BGP_MSG_NOTIFICATION:
		return "BGP_MSG_NOTIFICATION"
	case bgp.BGP_MSG_OPEN:
		return "BGP_MSG_OPEN"
	case bgp.BGP_MSG_UPDATE:
		return "BGP_MSG_UPDATE"
	case bgp.BGP_MSG_ROUTE_REFRESH:
		return "BGP_MSG_ROUTE_REFRESH"
	}
	return strconv.Itoa(int(t))
}

func (m *MockConnection) Close() error {
	m.mtx.Lock()
	defer m.mtx.Unlock()
	if !m.isClosed {
		close(m.recvCh)
		m.isClosed = true
	}
	return nil
}

func (m *MockConnection) LocalAddr() net.Addr {
	return &net.TCPAddr{
		IP:   net.ParseIP("10.10.10.10"),
		Port: bgp.BGP_PORT}
}

func TestReadAll(t *testing.T) {
	assert := assert.New(t)
	m := NewMockConnection(t)
	msg := open()
	expected1, _ := msg.Header.Serialize()
	expected2, _ := msg.Body.Serialize()

	pushBytes := func() {
		m.Log("push 5 bytes")
		m.setData(expected1[0:5])
		m.Log("push rest")
		m.setData(expected1[5:])
		m.Log("push bytes at once")
		m.setData(expected2)
	}

	go pushBytes()

	var actual1 []byte
	actual1, _ = readAll(m, bgp.BGP_HEADER_LENGTH)
	m.Log(actual1)
	assert.Equal(expected1, actual1)

	var actual2 []byte
	actual2, _ = readAll(m, len(expected2))
	m.Log(actual2)
	assert.Equal(expected2, actual2)
}

func TestFSMHandlerOpensent_HoldTimerExpired(t *testing.T) {
	assert := assert.New(t)
	m := NewMockConnection(t)

	p, h := makePeerAndHandler()

	// push mock connection
	p.fsm.conn = m
	p.fsm.h = h

	// set keepalive ticker
	p.fsm.pConf.Timers.State.NegotiatedHoldTime = 3

	// set holdtime
	p.fsm.opensentHoldTime = 2

	state, _ := h.opensent()

	assert.Equal(bgp.BGP_FSM_IDLE, state)
	lastMsg := m.sendBuf[len(m.sendBuf)-1]
	sent, _ := bgp.ParseBGPMessage(lastMsg)
	assert.Equal(uint8(bgp.BGP_MSG_NOTIFICATION), sent.Header.Type)
	assert.Equal(uint8(bgp.BGP_ERROR_HOLD_TIMER_EXPIRED), sent.Body.(*bgp.BGPNotification).ErrorCode)

}

func TestFSMHandlerOpenconfirm_HoldTimerExpired(t *testing.T) {
	assert := assert.New(t)
	m := NewMockConnection(t)

	p, h := makePeerAndHandler()

	// push mock connection
	p.fsm.conn = m
	p.fsm.h = h

	// set up keepalive ticker
	p.fsm.pConf.Timers.Config.KeepaliveInterval = 1

	// set holdtime
	p.fsm.pConf.Timers.State.NegotiatedHoldTime = 2
	state, _ := h.openconfirm()

	assert.Equal(bgp.BGP_FSM_IDLE, state)
	lastMsg := m.sendBuf[len(m.sendBuf)-1]
	sent, _ := bgp.ParseBGPMessage(lastMsg)
	assert.Equal(uint8(bgp.BGP_MSG_NOTIFICATION), sent.Header.Type)
	assert.Equal(uint8(bgp.BGP_ERROR_HOLD_TIMER_EXPIRED), sent.Body.(*bgp.BGPNotification).ErrorCode)

}

func TestFSMHandlerEstablish_HoldTimerExpired(t *testing.T) {
	assert := assert.New(t)
	m := NewMockConnection(t)

	p, h := makePeerAndHandler()

	// push mock connection
	p.fsm.conn = m
	p.fsm.h = h

	// set keepalive ticker
	p.fsm.pConf.Timers.State.NegotiatedHoldTime = 3

	msg := keepalive()
	header, _ := msg.Header.Serialize()
	body, _ := msg.Body.Serialize()

	pushPackets := func() {
		// first keepalive from peer
		m.setData(header)
		m.setData(body)
	}

	// set holdtime
	p.fsm.pConf.Timers.Config.HoldTime = 2
	p.fsm.pConf.Timers.State.NegotiatedHoldTime = 2

	go pushPackets()
	state, _ := h.established()
	time.Sleep(time.Second * 1)
	assert.Equal(bgp.BGP_FSM_IDLE, state)
	m.mtx.Lock()
	lastMsg := m.sendBuf[len(m.sendBuf)-1]
	m.mtx.Unlock()
	sent, _ := bgp.ParseBGPMessage(lastMsg)
	assert.Equal(uint8(bgp.BGP_MSG_NOTIFICATION), sent.Header.Type)
	assert.Equal(uint8(bgp.BGP_ERROR_HOLD_TIMER_EXPIRED), sent.Body.(*bgp.BGPNotification).ErrorCode)
}

func TestFSMHandlerOpenconfirm_HoldtimeZero(t *testing.T) {
	log.SetLevel(log.DebugLevel)
	assert := assert.New(t)
	m := NewMockConnection(t)

	p, h := makePeerAndHandler()

	// push mock connection
	p.fsm.conn = m
	p.fsm.h = h

	// set up keepalive ticker
	p.fsm.pConf.Timers.Config.KeepaliveInterval = 1
	// set holdtime
	p.fsm.pConf.Timers.State.NegotiatedHoldTime = 0
	go h.openconfirm()

	time.Sleep(100 * time.Millisecond)

	assert.Equal(0, len(m.sendBuf))

}

func TestFSMHandlerEstablished_HoldtimeZero(t *testing.T) {
	log.SetLevel(log.DebugLevel)
	assert := assert.New(t)
	m := NewMockConnection(t)

	p, h := makePeerAndHandler()

	// push mock connection
	p.fsm.conn = m
	p.fsm.h = h

	// set holdtime
	p.fsm.pConf.Timers.State.NegotiatedHoldTime = 0

	go h.established()

	time.Sleep(100 * time.Millisecond)

	assert.Equal(0, len(m.sendBuf))
}

func TestCheckOwnASLoop(t *testing.T) {
	assert := assert.New(t)
	aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{65100})}
	aspath := bgp.NewPathAttributeAsPath(aspathParam)
	assert.False(hasOwnASLoop(65100, 10, aspath))
	assert.True(hasOwnASLoop(65100, 0, aspath))
	assert.False(hasOwnASLoop(65200, 0, aspath))
}

func makePeerAndHandler() (*Peer, *FSMHandler) {
	p := &Peer{
		fsm:      NewFSM(&config.Global{}, &config.Neighbor{}, table.NewRoutingPolicy()),
		outgoing: channels.NewInfiniteChannel(),
	}

	h := &FSMHandler{
		fsm:      p.fsm,
		errorCh:  make(chan FsmStateReason, 2),
		incoming: channels.NewInfiniteChannel(),
		outgoing: p.outgoing,
	}

	return p, h

}

func open() *bgp.BGPMessage {
	p1 := bgp.NewOptionParameterCapability(
		[]bgp.ParameterCapabilityInterface{bgp.NewCapRouteRefresh()})
	p2 := bgp.NewOptionParameterCapability(
		[]bgp.ParameterCapabilityInterface{bgp.NewCapMultiProtocol(bgp.RF_IPv4_UC)})
	g := &bgp.CapGracefulRestartTuple{AFI: 4, SAFI: 2, Flags: 3}
	p3 := bgp.NewOptionParameterCapability(
		[]bgp.ParameterCapabilityInterface{bgp.NewCapGracefulRestart(true, true, 100,
			[]*bgp.CapGracefulRestartTuple{g})})
	p4 := bgp.NewOptionParameterCapability(
		[]bgp.ParameterCapabilityInterface{bgp.NewCapFourOctetASNumber(100000)})
	return bgp.NewBGPOpenMessage(11033, 303, "100.4.10.3",
		[]bgp.OptionParameterInterface{p1, p2, p3, p4})
}

func keepalive() *bgp.BGPMessage {
	return bgp.NewBGPKeepAliveMessage()
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"text/template"

	"github.com/jroimartin/monmq"
	"github.com/jroimartin/orujo"
)

type MonmqCmd struct {
	Target  string
	Command string
}

type Filters struct {
	Ip       string
	Ports    []string
	Services []string
	Regexp   string
}

type Banner struct {
	Ip      string
	Port    string
	Service string
	Content string
}

func (s *server) tasksHandler(w http.ResponseWriter, r *http.Request) {
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		orujo.RegisterError(w, fmt.Errorf("Reading POST:", err))
		return
	}
	tasks := strings.Split(string(data), ",")
	tm := newTaskManager(s.client, s.database)
	go tm.runTasks(tasks)
	fmt.Fprintln(w, "{\"status\": \"success\"}")
}

func (s *server) statusHandler(w http.ResponseWriter, r *http.Request) {
	switch {
	case r.Method == "POST":
		data, err := ioutil.ReadAll(r.Body)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			orujo.RegisterError(w, fmt.Errorf("Reading POST:", err))
			return
		}
		var cmd MonmqCmd
		if err = json.Unmarshal(data, &cmd); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			orujo.RegisterError(w, fmt.Errorf("Parsing json:", err))
			return
		}
		var command monmq.Command
		switch {
		case cmd.Command == "softshutdown":
			command = monmq.SoftShutdown
		case cmd.Command == "hardshutdown":
			command = monmq.HardShutdown
		case cmd.Command == "pause":
			command = monmq.Pause
		case cmd.Command == "resume":
			command = monmq.Resume
		}
		if err = s.supervisor.Invoke(command, cmd.Target); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			orujo.RegisterError(w, fmt.Errorf("Invoking monmq command:", err))
			return
		}
	case r.Method == "GET":
		b, err := json.Marshal(s.supervisor.Status())
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			orujo.RegisterError(w, fmt.Errorf("error marshaling status:", err))
			return
		}
		fmt.Fprintf(w, "%s", b)
	}
}

func (s *server) queryHandler(w http.ResponseWriter, r *http.Request) {
	f := extractFilters(r)

	t := template.Must(template.New("query").Parse(queryTemplate))
	query := &bytes.Buffer{}
	err := t.Execute(query, f)
	if err != nil {
		log.Println("executing template:", err)
	}

	stmt, err := s.database.Prepare(query.String())
	if err != nil {
		log.Println("preparing statement:", err)
	}

	data := make([]interface{}, 0, len(f.Ports))
	if f.Ip != "" {
		data = append(data, f.Ip)
	}
	for _, v := range f.Ports {
		data = append(data, v)
	}
	for _, v := range f.Services {
		data = append(data, v)
	}
	if f.Regexp != "" {
		data = append(data, f.Regexp)
	}

	rows, err := stmt.Query(data...)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		orujo.RegisterError(w, fmt.Errorf("error querying:", err))
		return
	}
	defer rows.Close()

	var result []Banner
	for rows.Next() {
		var curr Banner
		if err := rows.Scan(&curr.Ip, &curr.Port, &curr.Service, &curr.Content); err != nil {
			log.Fatal(err)
		}
		result = append(result, curr)
	}
	jsoned, err := json.Marshal(result)
	if err != nil {
		log.Println("marshaling:", err)
	}
	fmt.Fprintf(w, string(jsoned))
}

const (
	queryTemplate = `SELECT DISTINCT INET_NTOA(ip), port, service, content FROM banners
{{if or .Ip .Ports .Services .Regexp}} WHERE{{end}}
{{if .Ip}} ip = INET_ATON((?)){{end}}
{{if and .Ip .Ports}} AND{{end}}{{if .Ports}} port IN ({{range $i, $v := .Ports}}{{if $i}},{{end}}?{{end}}){{end}}
{{if and .Services (or .Ports .Services)}} AND{{end}}{{if .Services}} service IN ({{range $i, $v := .Services}}{{if $i}},{{end}}?{{end}}){{end}}
{{if and .Regexp (or .Ip .Ports .Services)}} AND{{end}}{{if .Regexp}} content regexp (?){{end}};`
)

func extractFilters(request *http.Request) Filters {
	var p, s []string
	var r string

	values := request.URL.Query()
	ip := strings.TrimPrefix(request.URL.Path, "/ips")
	if ip != "" {
		ip = strings.TrimPrefix(ip, "/")
	}
	if values["port"] != nil {
		p = strings.Split(values["port"][0], ",")
	}
	if values["service"] != nil {
		s = strings.Split(values["service"][0], ",")
	}
	if values["regexp"] != nil {
		r = values["regexp"][0]
	}
	filters := Filters{
		Ip:       ip,
		Ports:    p,
		Services: s,
		Regexp:   r,
	}
	return filters
}
<commit_msg>/tasks now speaks json<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"text/template"

	"github.com/jroimartin/monmq"
	"github.com/jroimartin/orujo"
)

type MonmqCmd struct {
	Target  string
	Command string
}

type Filters struct {
	Ip       string
	Ports    []string
	Services []string
	Regexp   string
}

type Banner struct {
	Ip      string
	Port    string
	Service string
	Content string
}

func (s *server) tasksHandler(w http.ResponseWriter, r *http.Request) {
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		orujo.RegisterError(w, fmt.Errorf("Reading POST:", err))
		return
	}
	var tasks []string
	err = json.Unmarshal(data, &tasks)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		orujo.RegisterError(w, fmt.Errorf("Reading POST:", err))
		orujo.RegisterError(w, fmt.Errorf("Unmarshal tasks:", err))
		return
	}
	tm := newTaskManager(s.client, s.database)
	go tm.runTasks(tasks)
	fmt.Fprintln(w, "{\"status\": \"success\"}")
}

func (s *server) statusHandler(w http.ResponseWriter, r *http.Request) {
	switch {
	case r.Method == "POST":
		data, err := ioutil.ReadAll(r.Body)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			orujo.RegisterError(w, fmt.Errorf("Reading POST:", err))
			return
		}
		var cmd MonmqCmd
		if err = json.Unmarshal(data, &cmd); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			orujo.RegisterError(w, fmt.Errorf("Parsing json:", err))
			return
		}
		var command monmq.Command
		switch {
		case cmd.Command == "softshutdown":
			command = monmq.SoftShutdown
		case cmd.Command == "hardshutdown":
			command = monmq.HardShutdown
		case cmd.Command == "pause":
			command = monmq.Pause
		case cmd.Command == "resume":
			command = monmq.Resume
		}
		if err = s.supervisor.Invoke(command, cmd.Target); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			orujo.RegisterError(w, fmt.Errorf("Invoking monmq command:", err))
			return
		}
	case r.Method == "GET":
		b, err := json.Marshal(s.supervisor.Status())
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			orujo.RegisterError(w, fmt.Errorf("error marshaling status:", err))
			return
		}
		fmt.Fprintf(w, "%s", b)
	}
}

func (s *server) queryHandler(w http.ResponseWriter, r *http.Request) {
	f := extractFilters(r)

	t := template.Must(template.New("query").Parse(queryTemplate))
	query := &bytes.Buffer{}
	err := t.Execute(query, f)
	if err != nil {
		log.Println("executing template:", err)
	}

	stmt, err := s.database.Prepare(query.String())
	if err != nil {
		log.Println("preparing statement:", err)
	}

	data := make([]interface{}, 0, len(f.Ports))
	if f.Ip != "" {
		data = append(data, f.Ip)
	}
	for _, v := range f.Ports {
		data = append(data, v)
	}
	for _, v := range f.Services {
		data = append(data, v)
	}
	if f.Regexp != "" {
		data = append(data, f.Regexp)
	}

	rows, err := stmt.Query(data...)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		orujo.RegisterError(w, fmt.Errorf("error querying:", err))
		return
	}
	defer rows.Close()

	var result []Banner
	for rows.Next() {
		var curr Banner
		if err := rows.Scan(&curr.Ip, &curr.Port, &curr.Service, &curr.Content); err != nil {
			log.Fatal(err)
		}
		result = append(result, curr)
	}
	jsoned, err := json.Marshal(result)
	if err != nil {
		log.Println("marshaling:", err)
	}
	fmt.Fprintf(w, string(jsoned))
}

const (
	queryTemplate = `SELECT DISTINCT INET_NTOA(ip), port, service, content FROM banners
{{if or .Ip .Ports .Services .Regexp}} WHERE{{end}}
{{if .Ip}} ip = INET_ATON((?)){{end}}
{{if and .Ip .Ports}} AND{{end}}{{if .Ports}} port IN ({{range $i, $v := .Ports}}{{if $i}},{{end}}?{{end}}){{end}}
{{if and .Services (or .Ports .Services)}} AND{{end}}{{if .Services}} service IN ({{range $i, $v := .Services}}{{if $i}},{{end}}?{{end}}){{end}}
{{if and .Regexp (or .Ip .Ports .Services)}} AND{{end}}{{if .Regexp}} content regexp (?){{end}};`
)

func extractFilters(request *http.Request) Filters {
	var p, s []string
	var r string

	values := request.URL.Query()
	ip := strings.TrimPrefix(request.URL.Path, "/ips")
	if ip != "" {
		ip = strings.TrimPrefix(ip, "/")
	}
	if values["port"] != nil {
		p = strings.Split(values["port"][0], ",")
	}
	if values["service"] != nil {
		s = strings.Split(values["service"][0], ",")
	}
	if values["regexp"] != nil {
		r = values["regexp"][0]
	}
	filters := Filters{
		Ip:       ip,
		Ports:    p,
		Services: s,
		Regexp:   r,
	}
	return filters
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"net/http"
	"time"
)

const (
	redirectTimeout = 3 * time.Second
)

func (srv *server) redirect(wf *runner, wr http.ResponseWriter, req *http.Request) {
	fmt.Println("redir in")
	var url string
	redir := make(chan string, 1)
	accept := make(chan bool, 1)
	cb := func(url string) bool {
		redir <- url
		return <-accept
	}
	fmt.Println("redir pre")
	wf.NextRedirect(cb)
	fmt.Println("redir mid")
	select {
	case url = <-redir:
		accept <- true
	case <-time.After(redirectTimeout):
		accept <- false
	}
	fmt.Println("redir post")

	if url == "" {
		wr.Write([]byte("Done! You may close this page."))
		return
	}
	http.Redirect(wr, req, url, http.StatusFound)
}
<commit_msg>bad fix<commit_after>package server

import (
	"fmt"
	"net/http"
	"time"
)

const (
	redirectTimeout = 3 * time.Second
)

func (srv *server) redirect(wf *runner, wr http.ResponseWriter, req *http.Request) {
	fmt.Println("redir in")
	redir := make(chan string, 1)
	accept := make(chan bool, 1)
	cb := func(url string) bool {
		redir <- url
		return <-accept
	}
	fmt.Println("redir pre")
	go wf.NextRedirect(cb)
	fmt.Println("redir mid")
	var url string
	select {
	case url = <-redir:
		accept <- true
	case <-time.After(redirectTimeout):
		accept <- false
	}
	fmt.Println("redir post")

	if url == "" {
		wr.Write([]byte("Done! You may close this page."))
		return
	}
	http.Redirect(wr, req, url, http.StatusFound)
}
<|endoftext|>


<commit_before>package server

import (
	"errors"

	"warcluster/entities"
	"warcluster/server/response"
)

var missionTypes = map[string]struct{}{
	"Attack": {},
	"Supply": {},
	"Spy":    {},
}

// scopeOfView is not finished yet but the purpose of the function is
// to call calculateCanvasSize and give the player the information
// contained in the given borders.
func scopeOfView(request *Request) error {
	response := response.NewScopeOfView(request.Position, request.Resolution)
	request.Client.Player.ScreenPosition = request.Position
	go entities.Save(request.Client.Player)
	clients.Send(request.Client.Player, response)
	request.Client.MoveToAreas(response.Areas())

	return nil
}

// This function makes all the checks needed for creation of a new mission.
func parseAction(request *Request) error {
	var err error

	sendMissionMessage := response.NewSendMissions()

	defer func() error {
		if panicked := recover(); panicked != nil {
			err = errors.New("Parse action panic!")
		}
		return err
	}()

	if len(request.StartPlanets) == 0 {
		errorMessage := "No start planets provided"
		sendMissionMessage.FailedMissions["Global"] = errorMessage
		return errors.New(errorMessage)
	}

	target, err := entities.Get(request.EndPlanet)
	if err != nil {
		errorMessage := "End planet does not exist"
		sendMissionMessage.FailedMissions["Global"] = errorMessage
		return errors.New(errorMessage)
	}
	endPlanet := target.(*entities.Planet)

	if _, isMissionTypeValid := missionTypes[request.Type]; !isMissionTypeValid {
		errorMessage := "Invalid mission type!"
		sendMissionMessage.FailedMissions["Global"] = errorMessage
		return errors.New(errorMessage)
	}

	for _, startPlanet := range request.StartPlanets {

		mission, err := prepareMission(startPlanet, endPlanet, request)

		if err == nil {
			sendMissionMessage.Missions[mission.Key()] = mission
		} else {
			sendMissionMessage.FailedMissions[startPlanet] = err.Error()
		}
	}

	request.Client.Send(sendMissionMessage)

	return nil
}

func prepareMission(startPlanet string, endPlanet *entities.Planet, request *Request) (*entities.Mission, error) {
	sourceEntity, err := entities.Get(startPlanet)
	if err != nil {
		return nil, err
	}

	source := sourceEntity.(*entities.Planet)

	if source.Owner != request.Client.Player.Username {

		return nil, errors.New("The mission owner does not own the start planet.")
	}

	if startPlanet == request.EndPlanet {
		return nil, errors.New("Start and end planet are the same.")
	}

	mission := request.Client.Player.StartMission(
		source,
		endPlanet,
		request.Fleet,
		request.Type,
	)

	if mission.ShipCount == 0 {
		return nil, errors.New("Not enough pilots on source planet!")
	}

	entities.Save(source)
	go StartMissionary(mission)
	entities.Save(mission)
	clients.Broadcast(source)

	return mission, nil
}
<commit_msg>Broadcast the beginning of new mission<commit_after>package server

import (
	"errors"

	"warcluster/entities"
	"warcluster/server/response"
)

var missionTypes = map[string]struct{}{
	"Attack": {},
	"Supply": {},
	"Spy":    {},
}

// scopeOfView is not finished yet but the purpose of the function is
// to call calculateCanvasSize and give the player the information
// contained in the given borders.
func scopeOfView(request *Request) error {
	response := response.NewScopeOfView(request.Position, request.Resolution)
	request.Client.Player.ScreenPosition = request.Position
	go entities.Save(request.Client.Player)
	clients.Send(request.Client.Player, response)
	request.Client.MoveToAreas(response.Areas())

	return nil
}

// This function makes all the checks needed for creation of a new mission.
func parseAction(request *Request) error {
	var err error

	sendMissionMessage := response.NewSendMissions()

	defer func() error {
		if panicked := recover(); panicked != nil {
			err = errors.New("Parse action panic!")
		}
		return err
	}()

	if len(request.StartPlanets) == 0 {
		errorMessage := "No start planets provided"
		sendMissionMessage.FailedMissions["Global"] = errorMessage
		return errors.New(errorMessage)
	}

	target, err := entities.Get(request.EndPlanet)
	if err != nil {
		errorMessage := "End planet does not exist"
		sendMissionMessage.FailedMissions["Global"] = errorMessage
		return errors.New(errorMessage)
	}
	endPlanet := target.(*entities.Planet)

	if _, isMissionTypeValid := missionTypes[request.Type]; !isMissionTypeValid {
		errorMessage := "Invalid mission type!"
		sendMissionMessage.FailedMissions["Global"] = errorMessage
		return errors.New(errorMessage)
	}

	for _, startPlanet := range request.StartPlanets {

		mission, err := prepareMission(startPlanet, endPlanet, request)

		if err == nil {
			sendMissionMessage.Missions[mission.Key()] = mission
		} else {
			sendMissionMessage.FailedMissions[startPlanet] = err.Error()
		}
	}

	request.Client.Send(sendMissionMessage)

	return nil
}

func prepareMission(startPlanet string, endPlanet *entities.Planet, request *Request) (*entities.Mission, error) {
	sourceEntity, err := entities.Get(startPlanet)
	if err != nil {
		return nil, err
	}

	source := sourceEntity.(*entities.Planet)

	if source.Owner != request.Client.Player.Username {

		return nil, errors.New("The mission owner does not own the start planet.")
	}

	if startPlanet == request.EndPlanet {
		return nil, errors.New("Start and end planet are the same.")
	}

	mission := request.Client.Player.StartMission(
		source,
		endPlanet,
		request.Fleet,
		request.Type,
	)

	if mission.ShipCount == 0 {
		return nil, errors.New("Not enough pilots on source planet!")
	}

	entities.Save(source)
	go StartMissionary(mission)
	entities.Save(mission)
	clients.Broadcast(mission)
	clients.Broadcast(source)

	return mission, nil
}
<|endoftext|>


<commit_before>package session

import (
  "fmt"
  "github.com/rkbodenner/parallel_universe/game"
)

type Session struct {
  Id uint `json:"id"`
  Game *game.Game
  Players []*game.Player
  SetupAssignments StepAssignments
  SetupSteps []game.SetupStep
  freeSetupSteps map[game.SetupStep]bool
}

func NewSession(g *game.Game, players []*game.Player) *Session {
  setupSteps := make([]game.SetupStep, 0)
  for _,rule := range g.SetupRules {
    if "Once" == rule.Arity {
      step, err := game.NewGlobalSetupStep(rule)
      if nil != err {
        fmt.Println(err)
      }
      setupSteps = append(setupSteps, step)
    } else if "Each player" == rule.Arity {
      for _,p := range players {
        step, err := game.NewSinglePlayerSetupStep(rule, p)
        if nil != err {
          fmt.Println(err)
        }
        setupSteps = append(setupSteps, step)
      }
    }
  }

  freeSetupSteps := make(map[game.SetupStep]bool)
  for _,step := range setupSteps {
    freeSetupSteps[step] = true
  }

  return &Session{
    Game: g,
    Players: players,
    SetupAssignments: NewStepMap(),
    SetupSteps: setupSteps,
    freeSetupSteps: freeSetupSteps,
  }
}

func (session *Session) IsRuleDone(rule *game.SetupRule) bool {
  for _,step := range session.SetupSteps {
    if step.GetRule().Description == rule.Description && !step.IsDone() {
      return false
    }
  }
  return true
}

func (session *Session) AreStepDependenciesDone(step game.SetupStep) bool {
  for _,dep := range step.GetRule().Dependencies {
    if !session.IsRuleDone(dep) {
      return false
    }
  }
  return true
}

func (session *Session) findNextUndoneSetupStep(player *game.Player) (game.SetupStep, error) {
  for step,_ := range session.freeSetupSteps {
    if step.CanBeOwnedBy(player) && !step.IsDone() && session.AreStepDependenciesDone(step) {
      return step, nil
    }
  }
  return nil, fmt.Errorf("No undone steps available for %s", player.Name)
}

func (session *Session) Step(player *game.Player) game.SetupStep {
  step,hasAssignment := session.SetupAssignments.Get(player)
  if !hasAssignment || (hasAssignment && step.IsDone()) {
    nextStep,error := session.findNextUndoneSetupStep(player)
    if ( error != nil ) {
      fmt.Println(error.Error())
      return step
    }
    session.SetupAssignments.Set(player, nextStep)
    delete(session.freeSetupSteps, nextStep)
    return nextStep
  }
  return step
}

func (session *Session) Print() {
  for _,step := range session.SetupSteps {
    ownerName := ""
    if nil != step.GetOwner() {
      ownerName = step.GetOwner().Name
    }
    fmt.Printf("%s (%s) %t\n", step, ownerName, step.IsDone())
  }
}
<commit_msg>Fix NPE initializing an empty session<commit_after>package session

import (
  "fmt"
  "github.com/rkbodenner/parallel_universe/game"
)

type Session struct {
  Id uint `json:"id"`
  Game *game.Game
  Players []*game.Player
  SetupAssignments StepAssignments
  SetupSteps []game.SetupStep
  freeSetupSteps map[game.SetupStep]bool
}

func NewSession(g *game.Game, players []*game.Player) *Session {
  setupSteps := make([]game.SetupStep, 0)
  if nil != g {
    for _,rule := range g.SetupRules {
      if "Once" == rule.Arity {
        step, err := game.NewGlobalSetupStep(rule)
        if nil != err {
          fmt.Println(err)
        }
        setupSteps = append(setupSteps, step)
      } else if "Each player" == rule.Arity {
        for _,p := range players {
          step, err := game.NewSinglePlayerSetupStep(rule, p)
          if nil != err {
            fmt.Println(err)
          }
          setupSteps = append(setupSteps, step)
        }
      }
    }
  }

  freeSetupSteps := make(map[game.SetupStep]bool)
  for _,step := range setupSteps {
    freeSetupSteps[step] = true
  }

  return &Session{
    Game: g,
    Players: players,
    SetupAssignments: NewStepMap(),
    SetupSteps: setupSteps,
    freeSetupSteps: freeSetupSteps,
  }
}

func (session *Session) IsRuleDone(rule *game.SetupRule) bool {
  for _,step := range session.SetupSteps {
    if step.GetRule().Description == rule.Description && !step.IsDone() {
      return false
    }
  }
  return true
}

func (session *Session) AreStepDependenciesDone(step game.SetupStep) bool {
  for _,dep := range step.GetRule().Dependencies {
    if !session.IsRuleDone(dep) {
      return false
    }
  }
  return true
}

func (session *Session) findNextUndoneSetupStep(player *game.Player) (game.SetupStep, error) {
  for step,_ := range session.freeSetupSteps {
    if step.CanBeOwnedBy(player) && !step.IsDone() && session.AreStepDependenciesDone(step) {
      return step, nil
    }
  }
  return nil, fmt.Errorf("No undone steps available for %s", player.Name)
}

func (session *Session) Step(player *game.Player) game.SetupStep {
  step,hasAssignment := session.SetupAssignments.Get(player)
  if !hasAssignment || (hasAssignment && step.IsDone()) {
    nextStep,error := session.findNextUndoneSetupStep(player)
    if ( error != nil ) {
      fmt.Println(error.Error())
      return step
    }
    session.SetupAssignments.Set(player, nextStep)
    delete(session.freeSetupSteps, nextStep)
    return nextStep
  }
  return step
}

func (session *Session) Print() {
  for _,step := range session.SetupSteps {
    ownerName := ""
    if nil != step.GetOwner() {
      ownerName = step.GetOwner().Name
    }
    fmt.Printf("%s (%s) %t\n", step, ownerName, step.IsDone())
  }
}
<|endoftext|>