<commit_before>package main

import (
	"net/url"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
)

const namespace = "marathon"

type Exporter struct {
	uri          *url.URL
	duration     prometheus.Gauge
	scrapeError  prometheus.Gauge
	totalErrors  prometheus.Counter
	totalScrapes prometheus.Counter
}

// Describe implements prometheus.Collector.
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
	log.Debugln("Describing metrics")
	metricCh := make(chan prometheus.Metric)
	doneCh := make(chan struct{})

	go func() {
		for m := range metricCh {
			ch <- m.Desc()
		}
		close(doneCh)
	}()

	e.Collect(metricCh)
	close(metricCh)
	<-doneCh
}

// Collect implements prometheus.Collector.
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
	log.Debugln("Collecting metrics")
	e.scrape(ch)

	ch <- e.duration
	ch <- e.totalScrapes
	ch <- e.scrapeError
}

func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
	e.totalScrapes.Inc()

	var err error
	defer func(begin time.Time) {
		e.duration.Set(time.Since(begin).Seconds())
		if err == nil {
			e.scrapeError.Set(0)
		} else {
			e.scrapeError.Set(1)
		}
	}(time.Now())
}

func NewExporter(uri *url.URL) *Exporter {
	return &Exporter{
		uri: uri,
		duration: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: "exporter",
			Name:      "last_scrape_duration_seconds",
			Help:      "Duration of the last scrape of metrics from Marathon.",
		}),
		scrapeError: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: "exporter",
			Name:      "last_scrape_error",
			Help:      "Whether the last scrape of metrics from Marathon resulted in an error (1 for error, 0 for success).",
		}),
		totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: "exporter",
			Name:      "scrapes_total",
			Help:      "Total number of times Marathon was scraped for metrics.",
		}),
		totalErrors: prometheus.NewCounter(prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: "exporter",
			Name:      "errors_total",
			Help:      "Total number of times the exporter experienced errors collecting Marathon metrics.",
		}),
	}
}
<commit_msg>Increment total_errors<commit_after>package main

import (
	"net/url"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
)

const namespace = "marathon"

type Exporter struct {
	uri          *url.URL
	duration     prometheus.Gauge
	scrapeError  prometheus.Gauge
	totalErrors  prometheus.Counter
	totalScrapes prometheus.Counter
}

// Describe implements prometheus.Collector.
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
	log.Debugln("Describing metrics")
	metricCh := make(chan prometheus.Metric)
	doneCh := make(chan struct{})

	go func() {
		for m := range metricCh {
			ch <- m.Desc()
		}
		close(doneCh)
	}()

	e.Collect(metricCh)
	close(metricCh)
	<-doneCh
}

// Collect implements prometheus.Collector.
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
	log.Debugln("Collecting metrics")
	e.scrape(ch)

	ch <- e.duration
	ch <- e.totalScrapes
	ch <- e.totalErrors
	ch <- e.scrapeError
}

func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
	e.totalScrapes.Inc()

	var err error
	defer func(begin time.Time) {
		e.duration.Set(time.Since(begin).Seconds())
		if err == nil {
			e.scrapeError.Set(0)
		} else {
			e.totalErrors.Inc()
			e.scrapeError.Set(1)
		}
	}(time.Now())
}

func NewExporter(uri *url.URL) *Exporter {
	return &Exporter{
		uri: uri,
		duration: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: "exporter",
			Name:      "last_scrape_duration_seconds",
			Help:      "Duration of the last scrape of metrics from Marathon.",
		}),
		scrapeError: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: "exporter",
			Name:      "last_scrape_error",
			Help:      "Whether the last scrape of metrics from Marathon resulted in an error (1 for error, 0 for success).",
		}),
		totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: "exporter",
			Name:      "scrapes_total",
			Help:      "Total number of times Marathon was scraped for metrics.",
		}),
		totalErrors: prometheus.NewCounter(prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: "exporter",
			Name:      "errors_total",
			Help:      "Total number of times the exporter experienced errors collecting Marathon metrics.",
		}),
	}
}
<|endoftext|>


<commit_before>package main

import (
	"./content"
	"./db"
	"fmt"
	"math/rand"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	_FOLLOW     = iota
	_UNFOLLOW   = iota
	_FAVORITE   = iota
	_UNFAVORITE = iota
	_TWEET      = iota
	_REPLY      = iota
)

type Action struct {
	name   int
	weight int
}

func performDailyAction() {
	actions := make([]Action, 0, 6)

	actions = append(actions, Action{name: _FOLLOW, weight: ACTION_FOLLOW_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _UNFOLLOW, weight: ACTION_UNFOLLOW_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _FAVORITE, weight: ACTION_FAVORITE_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _UNFAVORITE, weight: ACTION_UNFAVORITE_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _TWEET, weight: ACTION_TWEET_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _REPLY, weight: ACTION_REPLY_WEIGHT * rand.Intn(100)})

	selectAndPerformAction(actions)
}

func performNightlyAction() {
	actions := make([]Action, 0, 6)

	actions = append(actions, Action{name: _FOLLOW, weight: ACTION_NIGHTLY_FOLLOW_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _UNFOLLOW, weight: ACTION_NIGHTLY_UNFOLLOW_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _FAVORITE, weight: ACTION_NIGHTLY_FAVORITE_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _UNFAVORITE, weight: ACTION_NIGHTLY_UNFAVORITE_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _TWEET, weight: ACTION_NIGHTLY_TWEET_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _REPLY, weight: ACTION_NIGHTLY_REPLY_WEIGHT * rand.Intn(100)})

	selectAndPerformAction(actions)
}

func selectAndPerformAction(actions []Action) {
	selectedAction := Action{name: -1, weight: -1}

	for _, action := range actions {
		if action.weight > selectedAction.weight {
			selectedAction = action
		}
	}

	switch selectedAction.name {
	case _FOLLOW:
		actionFollow()
		break
	case _UNFOLLOW:
		actionUnfollow()
		break
	case _FAVORITE:
		actionFavorite()
		break
	case _UNFAVORITE:
		actionUnfavorite()
		break
	case _TWEET:
		actionTweet()
		break
	case _REPLY:
		actionReply()
		break
	}
}

func actionFollow() {
	fmt.Println("Action follow")

	search, v := generateAPISearchValues(KEYWORDS[rand.Intn(len(KEYWORDS))])

	searchResult, err := api.GetSearch(search, v)
	if err != nil {
		fmt.Println("Error while querying twitter API", err)
		return
	}

	for _, tweet := range searchResult.Statuses {
		if !isUserAcceptable(tweet) {
			fmt.Println("Ignoring user for follow : @" + tweet.User.ScreenName)
			continue
		}

		if isMentionOrRT(tweet) {
			fmt.Println("Ignoring tweet for follow, mention or RT")
			continue
		}

		if isMe(tweet) {
			fmt.Println("Ignoring my own tweet for follow")
			continue
		}

		follow, err := db.AlreadyFollow(tweet.User.Id)
		if err != nil {
			fmt.Println("Error while checking if already follow")
			return
		}

		if follow {
			fmt.Println("Ignoring user for follow, already follow @" + tweet.User.ScreenName)
			continue
		}

		err = db.Follow{UserId: tweet.User.Id, UserName: tweet.User.ScreenName, Status: tweet.Text, FollowDate: time.Now()}.Persist()
		if err != nil {
			fmt.Println("Error while persisting follow", err)
			return
		}

		_, err = api.FollowUser(tweet.User.ScreenName)
		if err != nil {
			fmt.Println("Error while following user "+tweet.User.ScreenName+" : ", err)
		}

		fmt.Println("Now follow ", tweet.User.ScreenName)
		return
	}
}

func actionUnfollow() {
	fmt.Println("Action unfollow")

	date := time.Now()
	duration, err := time.ParseDuration("-72h") // -3 days
	date = date.Add(duration)

	follows, err := db.GetNotUnfollowed(date, UNFOLLOW_LIMIT_IN_A_ROW)
	if err != nil {
		fmt.Println("Error while querying db to find people to unfollow", err)
		return
	}

	for _, follow := range follows {
		follow.LastAction = time.Now()

		isFollowing, err := isUserFollowing(follow.UserName)
		if err != nil {
			fmt.Println("Error while querying API for friendships", err)
			return
		}

		if isFollowing {
			err = follow.Persist()
			if err != nil {
				fmt.Println("Error while persisting follow", err)
				return
			}

			continue
		}

		follow.UnfollowDate = time.Now()

		err = follow.Persist()
		if err != nil {
			fmt.Println("Error while persisting follow", err)
			return
		}

		_, err = api.UnfollowUser(follow.UserName)
		if err != nil {
			fmt.Println("Error while querying API to unfollow @"+follow.UserName, err)
			return
		}

		fmt.Println("Unfollowed @" + follow.UserName)
	}
}

func actionFavorite() {
	fmt.Println("Action fav")

	search, v := generateAPISearchValues(KEYWORDS[rand.Intn(len(KEYWORDS))])

	searchResult, err := api.GetSearch(search, v)
	if err != nil {
		fmt.Println("Error while querying twitter API", err)
		return
	}

	i := 0
	for _, tweet := range searchResult.Statuses {
		if i >= FAV_LIMIT_IN_A_ROW {
			return
		}

		if !isUserAcceptable(tweet) {
			fmt.Println("Ignoring user for favorite : @" + tweet.User.ScreenName)
			continue
		}

		if isMentionOrRT(tweet) {
			fmt.Println("Ignoring tweet for favorite, mention or RT")
			continue
		}

		if isMe(tweet) {
			fmt.Println("Ignoring my own tweet for favorite")
			continue
		}

		follow, err := db.AlreadyFollow(tweet.User.Id)
		if err == nil && follow {
			fmt.Println("Ignoring tweet for favorite, already follow @" + tweet.User.ScreenName)
			continue
		}

		alreadyFav, err := db.HasAlreadyFav(tweet.Id)
		if err != nil {
			fmt.Println("Error while checking already fav", err)
			return
		}

		if alreadyFav {
			fmt.Println("Ignoring tweet for favorite, already fav tweet from @" + tweet.User.ScreenName)
			continue
		}

		err = db.Favorite{UserId: tweet.User.Id, UserName: tweet.User.ScreenName, TweetId: tweet.Id, Status: tweet.Text, FavDate: time.Now()}.Persist()
		if err != nil {
			fmt.Println("Error while persisting fav", err)
			return
		}

		_, err = api.Favorite(tweet.Id)
		if err != nil {
			if strings.Contains(err.Error(), "139") { // Case of an already favorited tweet
				continue
			}

			fmt.Println("Error while favoriting tweet", err)
		} else {
			fmt.Println("Just favorited tweet : ", tweet.Text)
		}

		i++
	}
}

func actionUnfavorite() {
	fmt.Println("Action unfavorite")

	date := time.Now()
	duration, err := time.ParseDuration("-72h") // -3 days
	date = date.Add(duration)

	favs, err := db.GetNotUnfavorite(date, UNFAVORITE_LIMIT_IN_A_ROW)
	if err != nil {
		fmt.Println("Error while querying db to find tweet to unfav", err)
		return
	}

	for _, fav := range favs {
		fav.LastAction = time.Now()

		isFollowing, err := isUserFollowing(fav.UserName)
		if err != nil {
			fmt.Println("Error while querying API for friendships", err)
			return
		}

		if isFollowing {
			err = fav.Persist()
			if err != nil {
				fmt.Println("Error while persisting fav", err)
				return
			}

			continue
		}

		fav.UnfavDate = time.Now()

		err = fav.Persist()
		if err != nil {
			fmt.Println("Error while persisting fav", err)
			return
		}

		_, err = api.Unfavorite(fav.TweetId)
		if err != nil {
			fmt.Println("Error while querying API to unfav : "+fav.Status, err)
			return
		}

		fmt.Println("Unfaved @" + fav.Status)
	}
}

func actionTweet() {
	fmt.Println("Action tweet")

	hasReachLimit, err := hasReachDailyTweetLimit()
	if err != nil {
		fmt.Println("Error while getting daily limit reached", err)
		return
	}

	if hasReachLimit {
		fmt.Println("Day tweet limit reached, abording")
		return
	}

	content, err := content.GenerateTweetContent()
	if err != nil {
		fmt.Println("Error while getting tweet content : ", err)
		return
	}

	tweetText := content.Text + " " + content.Url + content.Hashtags

	err = db.Tweet{Content: tweetText, Date: time.Now()}.Persist()
	if err != nil {
		fmt.Println("Error while persisting tweet", err)
		return
	}

	tweet, err := api.PostTweet(tweetText, nil)
	if err != nil {
		fmt.Println("Error while posting tweet", err)
		return
	}

	fmt.Println("Tweet posted : ", tweet.Text)
}

func actionReply() {
	fmt.Println("Action reply")

	tweets, err := api.GetMentionsTimeline(nil)
	if err != nil {
		fmt.Println("Error while querying twitter mention API", err)
		return
	}

	for _, tweet := range tweets {

		replied, err := db.HasAlreadyReplied(tweet.Id)
		if err == nil && !replied {
			fmt.Println("Building reply for tweet : " + tweet.Text)

			response, err := buildReply(tweet)
			if err != nil {
				fmt.Println("Error while building reply", err)
				return
			}

			err = db.Reply{UserId: tweet.User.Id, UserName: tweet.User.ScreenName, TweetId: tweet.Id, Status: tweet.Text, Answer: response, ReplyDate: time.Now()}.Persist()
			if err != nil {
				fmt.Println("Error while persisting reply", err)
				return
			}

			if response != "" {
				v := url.Values{}
				v.Add("in_reply_to_status_id", strconv.FormatInt(tweet.Id, 10))

				respTweet, err := api.PostTweet(response, v)
				if err != nil {
					fmt.Println("Error while posting reply", err)
					return
				}

				fmt.Println("Reply posted : ", respTweet.Text)
			} else {
				fmt.Println("No response found for tweet : " + tweet.Text)
			}

			return
		}
	}

	fmt.Println("Nothing to reply found :(")
}
<commit_msg>Check if user already follow us before following him<commit_after>package main

import (
	"./content"
	"./db"
	"fmt"
	"math/rand"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	_FOLLOW     = iota
	_UNFOLLOW   = iota
	_FAVORITE   = iota
	_UNFAVORITE = iota
	_TWEET      = iota
	_REPLY      = iota
)

type Action struct {
	name   int
	weight int
}

func performDailyAction() {
	actions := make([]Action, 0, 6)

	actions = append(actions, Action{name: _FOLLOW, weight: ACTION_FOLLOW_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _UNFOLLOW, weight: ACTION_UNFOLLOW_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _FAVORITE, weight: ACTION_FAVORITE_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _UNFAVORITE, weight: ACTION_UNFAVORITE_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _TWEET, weight: ACTION_TWEET_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _REPLY, weight: ACTION_REPLY_WEIGHT * rand.Intn(100)})

	selectAndPerformAction(actions)
}

func performNightlyAction() {
	actions := make([]Action, 0, 6)

	actions = append(actions, Action{name: _FOLLOW, weight: ACTION_NIGHTLY_FOLLOW_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _UNFOLLOW, weight: ACTION_NIGHTLY_UNFOLLOW_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _FAVORITE, weight: ACTION_NIGHTLY_FAVORITE_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _UNFAVORITE, weight: ACTION_NIGHTLY_UNFAVORITE_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _TWEET, weight: ACTION_NIGHTLY_TWEET_WEIGHT * rand.Intn(100)})
	actions = append(actions, Action{name: _REPLY, weight: ACTION_NIGHTLY_REPLY_WEIGHT * rand.Intn(100)})

	selectAndPerformAction(actions)
}

func selectAndPerformAction(actions []Action) {
	selectedAction := Action{name: -1, weight: -1}

	for _, action := range actions {
		if action.weight > selectedAction.weight {
			selectedAction = action
		}
	}

	switch selectedAction.name {
	case _FOLLOW:
		actionFollow()
		break
	case _UNFOLLOW:
		actionUnfollow()
		break
	case _FAVORITE:
		actionFavorite()
		break
	case _UNFAVORITE:
		actionUnfavorite()
		break
	case _TWEET:
		actionTweet()
		break
	case _REPLY:
		actionReply()
		break
	}
}

func actionFollow() {
	fmt.Println("Action follow")

	search, v := generateAPISearchValues(KEYWORDS[rand.Intn(len(KEYWORDS))])

	searchResult, err := api.GetSearch(search, v)
	if err != nil {
		fmt.Println("Error while querying twitter API", err)
		return
	}

	for _, tweet := range searchResult.Statuses {
		if !isUserAcceptable(tweet) {
			fmt.Println("Ignoring user for follow : @" + tweet.User.ScreenName)
			continue
		}

		if isMentionOrRT(tweet) {
			fmt.Println("Ignoring tweet for follow, mention or RT")
			continue
		}

		if isMe(tweet) {
			fmt.Println("Ignoring my own tweet for follow")
			continue
		}

		follow, err := db.AlreadyFollow(tweet.User.Id)
		if err != nil {
			fmt.Println("Error while checking if already follow")
			return
		}

		if follow {
			fmt.Println("Ignoring user for follow, already follow @" + tweet.User.ScreenName)
			continue
		}

		alreadyFollowMe, err := isUserFollowing(tweet.User.ScreenName)
		if err != nil {
			fmt.Println("Error while checking user already follow", err)
			return
		}

		if alreadyFollowMe {
			fmt.Println("Ignoring user @" + tweet.User.ScreenName + " for follow cause he already follow us")
			continue
		}

		err = db.Follow{UserId: tweet.User.Id, UserName: tweet.User.ScreenName, Status: tweet.Text, FollowDate: time.Now()}.Persist()
		if err != nil {
			fmt.Println("Error while persisting follow", err)
			return
		}

		_, err = api.FollowUser(tweet.User.ScreenName)
		if err != nil {
			fmt.Println("Error while following user "+tweet.User.ScreenName+" : ", err)
		}

		fmt.Println("Now follow ", tweet.User.ScreenName)
		return
	}
}

func actionUnfollow() {
	fmt.Println("Action unfollow")

	date := time.Now()
	duration, err := time.ParseDuration("-72h") // -3 days
	date = date.Add(duration)

	follows, err := db.GetNotUnfollowed(date, UNFOLLOW_LIMIT_IN_A_ROW)
	if err != nil {
		fmt.Println("Error while querying db to find people to unfollow", err)
		return
	}

	for _, follow := range follows {
		follow.LastAction = time.Now()

		isFollowing, err := isUserFollowing(follow.UserName)
		if err != nil {
			fmt.Println("Error while querying API for friendships", err)
			return
		}

		if isFollowing {
			err = follow.Persist()
			if err != nil {
				fmt.Println("Error while persisting follow", err)
				return
			}

			continue
		}

		follow.UnfollowDate = time.Now()

		err = follow.Persist()
		if err != nil {
			fmt.Println("Error while persisting follow", err)
			return
		}

		_, err = api.UnfollowUser(follow.UserName)
		if err != nil {
			fmt.Println("Error while querying API to unfollow @"+follow.UserName, err)
			return
		}

		fmt.Println("Unfollowed @" + follow.UserName)
	}
}

func actionFavorite() {
	fmt.Println("Action fav")

	search, v := generateAPISearchValues(KEYWORDS[rand.Intn(len(KEYWORDS))])

	searchResult, err := api.GetSearch(search, v)
	if err != nil {
		fmt.Println("Error while querying twitter API", err)
		return
	}

	i := 0
	for _, tweet := range searchResult.Statuses {
		if i >= FAV_LIMIT_IN_A_ROW {
			return
		}

		if !isUserAcceptable(tweet) {
			fmt.Println("Ignoring user for favorite : @" + tweet.User.ScreenName)
			continue
		}

		if isMentionOrRT(tweet) {
			fmt.Println("Ignoring tweet for favorite, mention or RT")
			continue
		}

		if isMe(tweet) {
			fmt.Println("Ignoring my own tweet for favorite")
			continue
		}

		follow, err := db.AlreadyFollow(tweet.User.Id)
		if err == nil && follow {
			fmt.Println("Ignoring tweet for favorite, already follow @" + tweet.User.ScreenName)
			continue
		}

		alreadyFav, err := db.HasAlreadyFav(tweet.Id)
		if err != nil {
			fmt.Println("Error while checking already fav", err)
			return
		}

		if alreadyFav {
			fmt.Println("Ignoring tweet for favorite, already fav tweet from @" + tweet.User.ScreenName)
			continue
		}

		err = db.Favorite{UserId: tweet.User.Id, UserName: tweet.User.ScreenName, TweetId: tweet.Id, Status: tweet.Text, FavDate: time.Now()}.Persist()
		if err != nil {
			fmt.Println("Error while persisting fav", err)
			return
		}

		_, err = api.Favorite(tweet.Id)
		if err != nil {
			if strings.Contains(err.Error(), "139") { // Case of an already favorited tweet
				continue
			}

			fmt.Println("Error while favoriting tweet", err)
		} else {
			fmt.Println("Just favorited tweet : ", tweet.Text)
		}

		i++
	}
}

func actionUnfavorite() {
	fmt.Println("Action unfavorite")

	date := time.Now()
	duration, err := time.ParseDuration("-72h") // -3 days
	date = date.Add(duration)

	favs, err := db.GetNotUnfavorite(date, UNFAVORITE_LIMIT_IN_A_ROW)
	if err != nil {
		fmt.Println("Error while querying db to find tweet to unfav", err)
		return
	}

	for _, fav := range favs {
		fav.LastAction = time.Now()

		isFollowing, err := isUserFollowing(fav.UserName)
		if err != nil {
			fmt.Println("Error while querying API for friendships", err)
			return
		}

		if isFollowing {
			err = fav.Persist()
			if err != nil {
				fmt.Println("Error while persisting fav", err)
				return
			}

			continue
		}

		fav.UnfavDate = time.Now()

		err = fav.Persist()
		if err != nil {
			fmt.Println("Error while persisting fav", err)
			return
		}

		_, err = api.Unfavorite(fav.TweetId)
		if err != nil {
			fmt.Println("Error while querying API to unfav : "+fav.Status, err)
			return
		}

		fmt.Println("Unfaved @" + fav.Status)
	}
}

func actionTweet() {
	fmt.Println("Action tweet")

	hasReachLimit, err := hasReachDailyTweetLimit()
	if err != nil {
		fmt.Println("Error while getting daily limit reached", err)
		return
	}

	if hasReachLimit {
		fmt.Println("Day tweet limit reached, abording")
		return
	}

	content, err := content.GenerateTweetContent()
	if err != nil {
		fmt.Println("Error while getting tweet content : ", err)
		return
	}

	tweetText := content.Text + " " + content.Url + content.Hashtags

	err = db.Tweet{Content: tweetText, Date: time.Now()}.Persist()
	if err != nil {
		fmt.Println("Error while persisting tweet", err)
		return
	}

	tweet, err := api.PostTweet(tweetText, nil)
	if err != nil {
		fmt.Println("Error while posting tweet", err)
		return
	}

	fmt.Println("Tweet posted : ", tweet.Text)
}

func actionReply() {
	fmt.Println("Action reply")

	tweets, err := api.GetMentionsTimeline(nil)
	if err != nil {
		fmt.Println("Error while querying twitter mention API", err)
		return
	}

	for _, tweet := range tweets {

		replied, err := db.HasAlreadyReplied(tweet.Id)
		if err == nil && !replied {
			fmt.Println("Building reply for tweet : " + tweet.Text)

			response, err := buildReply(tweet)
			if err != nil {
				fmt.Println("Error while building reply", err)
				return
			}

			err = db.Reply{UserId: tweet.User.Id, UserName: tweet.User.ScreenName, TweetId: tweet.Id, Status: tweet.Text, Answer: response, ReplyDate: time.Now()}.Persist()
			if err != nil {
				fmt.Println("Error while persisting reply", err)
				return
			}

			if response != "" {
				v := url.Values{}
				v.Add("in_reply_to_status_id", strconv.FormatInt(tweet.Id, 10))

				respTweet, err := api.PostTweet(response, v)
				if err != nil {
					fmt.Println("Error while posting reply", err)
					return
				}

				fmt.Println("Reply posted : ", respTweet.Text)
			} else {
				fmt.Println("No response found for tweet : " + tweet.Text)
			}

			return
		}
	}

	fmt.Println("Nothing to reply found :(")
}
<|endoftext|>


<commit_before>package sysinit

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/dotcloud/docker/execdriver"
	_ "github.com/dotcloud/docker/execdriver/chroot"
	_ "github.com/dotcloud/docker/execdriver/lxc"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
)

// Clear environment pollution introduced by lxc-start
func setupEnv(args *execdriver.InitArgs) {
	os.Clearenv()
	for _, kv := range args.Env {
		parts := strings.SplitN(kv, "=", 2)
		if len(parts) == 1 {
			parts = append(parts, "")
		}
		os.Setenv(parts[0], parts[1])
	}
}

func executeProgram(args *execdriver.InitArgs) error {
	setupEnv(args)

	dockerInitFct, err := execdriver.GetInitFunc(args.Driver)
	if err != nil {
		panic(err)
	}
	return dockerInitFct(args)
}

// Sys Init code
// This code is run INSIDE the container and is responsible for setting
// up the environment before running the actual process
func SysInit() {
	if len(os.Args) <= 1 {
		fmt.Println("You should not invoke dockerinit manually")
		os.Exit(1)
	}

	var (
		// Get cmdline arguments
		user       = flag.String("u", "", "username or uid")
		gateway    = flag.String("g", "", "gateway address")
		ip         = flag.String("i", "", "ip address")
		workDir    = flag.String("w", "", "workdir")
		privileged = flag.Bool("privileged", false, "privileged mode")
		mtu        = flag.Int("mtu", 1500, "interface mtu")
		driver     = flag.String("driver", "", "exec driver")
		pipe       = flag.Int("pipe", 0, "sync pipe fd")
		console    = flag.String("console", "", "console (pty slave) path")
		logFile    = flag.String("log", "", "log file path")
	)
	flag.Parse()

	if err := setupLogging(*logFile); err != nil {
		log.Fatalf("setup logging %s", err)
	}

	// Get env
	var env []string
	content, err := ioutil.ReadFile("/.dockerenv")
	if err != nil {
		log.Fatalf("Unable to load environment variables: %v", err)
	}
	if err := json.Unmarshal(content, &env); err != nil {
		log.Fatalf("Unable to unmarshal environment variables: %v", err)
	}
	// Propagate the plugin-specific container env variable
	env = append(env, "container="+os.Getenv("container"))

	args := &execdriver.InitArgs{
		User:       *user,
		Gateway:    *gateway,
		Ip:         *ip,
		WorkDir:    *workDir,
		Privileged: *privileged,
		Env:        env,
		Args:       flag.Args(),
		Mtu:        *mtu,
		Driver:     *driver,
		Console:    *console,
		Pipe:       *pipe,
	}

	if err := executeProgram(args); err != nil {
		log.Fatal(err)
	}
}

func setupLogging(logFile string) (err error) {
	var writer io.Writer
	switch logFile {
	case "stderr":
		writer = os.Stderr
	case "none", "":
		writer = ioutil.Discard
	default:
		writer, err = os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755)
		if err != nil {
			return err
		}
	}
	log.SetOutput(writer)
	return nil
}
<commit_msg>Use the dockerenv file from the current dir instead of root<commit_after>package sysinit

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/dotcloud/docker/execdriver"
	_ "github.com/dotcloud/docker/execdriver/chroot"
	_ "github.com/dotcloud/docker/execdriver/lxc"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
)

// Clear environment pollution introduced by lxc-start
func setupEnv(args *execdriver.InitArgs) {
	os.Clearenv()
	for _, kv := range args.Env {
		parts := strings.SplitN(kv, "=", 2)
		if len(parts) == 1 {
			parts = append(parts, "")
		}
		os.Setenv(parts[0], parts[1])
	}
}

func executeProgram(args *execdriver.InitArgs) error {
	setupEnv(args)

	dockerInitFct, err := execdriver.GetInitFunc(args.Driver)
	if err != nil {
		panic(err)
	}
	return dockerInitFct(args)
}

// Sys Init code
// This code is run INSIDE the container and is responsible for setting
// up the environment before running the actual process
func SysInit() {
	if len(os.Args) <= 1 {
		fmt.Println("You should not invoke dockerinit manually")
		os.Exit(1)
	}

	var (
		// Get cmdline arguments
		user       = flag.String("u", "", "username or uid")
		gateway    = flag.String("g", "", "gateway address")
		ip         = flag.String("i", "", "ip address")
		workDir    = flag.String("w", "", "workdir")
		privileged = flag.Bool("privileged", false, "privileged mode")
		mtu        = flag.Int("mtu", 1500, "interface mtu")
		driver     = flag.String("driver", "", "exec driver")
		pipe       = flag.Int("pipe", 0, "sync pipe fd")
		console    = flag.String("console", "", "console (pty slave) path")
		logFile    = flag.String("log", "", "log file path")
	)
	flag.Parse()

	if err := setupLogging(*logFile); err != nil {
		log.Fatalf("setup logging %s", err)
	}

	// Get env
	var env []string
	content, err := ioutil.ReadFile(".dockerenv")
	if err != nil {
		log.Fatalf("Unable to load environment variables: %v", err)
	}
	if err := json.Unmarshal(content, &env); err != nil {
		log.Fatalf("Unable to unmarshal environment variables: %v", err)
	}
	// Propagate the plugin-specific container env variable
	env = append(env, "container="+os.Getenv("container"))

	args := &execdriver.InitArgs{
		User:       *user,
		Gateway:    *gateway,
		Ip:         *ip,
		WorkDir:    *workDir,
		Privileged: *privileged,
		Env:        env,
		Args:       flag.Args(),
		Mtu:        *mtu,
		Driver:     *driver,
		Console:    *console,
		Pipe:       *pipe,
	}

	if err := executeProgram(args); err != nil {
		log.Fatal(err)
	}
}

func setupLogging(logFile string) (err error) {
	var writer io.Writer
	switch logFile {
	case "stderr":
		writer = os.Stderr
	case "none", "":
		writer = ioutil.Discard
	default:
		writer, err = os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755)
		if err != nil {
			return err
		}
	}
	log.SetOutput(writer)
	return nil
}
<|endoftext|>


<commit_before>package db

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// CacheControl sets the default cache policy on static asset responses
func CacheControl(next http.Handler) http.HandlerFunc {
	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		etag := ConfigCache("etag")
		policy := fmt.Sprintf("max-age=%d, public, must-revalidate, proxy-revalidate", 60*60*24*30)
		res.Header().Add("Etag", etag)
		res.Header().Add("Cache-Control", policy)

		if match := res.Header().Get("If-None-Match"); match != "" {
			if strings.Contains(match, etag) {
				res.WriteHeader(http.StatusNotModified)
				return
			}
		}

		next.ServeHTTP(res, req)
	})
}

// NewEtag generates a new Etag for response caching
func NewEtag() string {
	now := fmt.Sprintf("%d", time.Now().Unix())
	etag := base64.StdEncoding.EncodeToString([]byte(now))

	return etag
}

// InvalidateCache sets a new Etag for http responses
func InvalidateCache() error {
	kv := make(map[string]interface{})

	c, err := ConfigAll()
	if err != nil {
		return err
	}

	err = json.Unmarshal(c, &kv)
	if err != nil {
		return err
	}

	kv["etag"] = NewEtag()

	data := make(url.Values)
	for k, v := range kv {
		switch v.(type) {
		case string:
			data.Set(k, v.(string))
		case []string:
			vv := v.([]string)
			for i := range vv {
				if i == 0 {
					data.Set(k, vv[i])
				} else {
					data.Add(k, vv[i])
				}
			}
		}
	}

	err = SetConfig(data)
	if err != nil {

	}

	return nil
}
<commit_msg>adding debug prints -- cache control not effecting json responses<commit_after>package db

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// CacheControl sets the default cache policy on static asset responses
func CacheControl(next http.Handler) http.HandlerFunc {
	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		etag := ConfigCache("etag")
		policy := fmt.Sprintf("max-age=%d, public, must-revalidate, proxy-revalidate", 60*60*24*30)
		res.Header().Add("Etag", etag)
		res.Header().Add("Cache-Control", policy)

		if match := res.Header().Get("If-None-Match"); match != "" {
			if strings.Contains(match, etag) {
				fmt.Println("matched etag")
				res.WriteHeader(http.StatusNotModified)
				return
			}

			fmt.Println("checked, no match")
		}

		next.ServeHTTP(res, req)
	})
}

// NewEtag generates a new Etag for response caching
func NewEtag() string {
	now := fmt.Sprintf("%d", time.Now().Unix())
	etag := base64.StdEncoding.EncodeToString([]byte(now))

	return etag
}

// InvalidateCache sets a new Etag for http responses
func InvalidateCache() error {
	kv := make(map[string]interface{})

	c, err := ConfigAll()
	if err != nil {
		return err
	}

	err = json.Unmarshal(c, &kv)
	if err != nil {
		return err
	}

	kv["etag"] = NewEtag()

	data := make(url.Values)
	for k, v := range kv {
		switch v.(type) {
		case string:
			data.Set(k, v.(string))
		case []string:
			vv := v.([]string)
			for i := range vv {
				if i == 0 {
					data.Set(k, vv[i])
				} else {
					data.Add(k, vv[i])
				}
			}
		}
	}

	err = SetConfig(data)
	if err != nil {

	}

	return nil
}
<|endoftext|>


<commit_before>package router

import (
	"github.com/ewhal/nyaa/service/user/permission"
	"github.com/nicksnyder/go-i18n/i18n"
	"html/template"
	"log"
	"math"
	"net/url"
	"strconv"
)

var FuncMap = template.FuncMap{
	"min": math.Min,
	"genRoute": func(name string, params ...string) string {
		url, err := Router.Get(name).URL(params...)
		if err == nil {
			return url.String()
		}
		return "error"
	},
	"genRouteWithQuery": func(name string, currentUrl *url.URL, params ...string) template.HTML {
		url, err := Router.Get(name).URL(params...)
		if err == nil {
			return template.HTML(template.HTMLEscapeString(url.String() + "?" + currentUrl.RawQuery)) // TODO: Review application of character escaping
		}
		return "error"
	},
	"genNav": func(nav Navigation, currentUrl *url.URL, pagesSelectable int) template.HTML {
		var ret = ""
		if (nav.TotalItem > 0) {
		maxPages := math.Ceil(float64(nav.TotalItem) / float64(nav.MaxItemPerPage))

		if nav.CurrentPage-1 > 0 {
			url, _ := Router.Get(nav.Route).URL("page", "1")
			ret = ret + "
«
"
		}
		startValue := 1
		if nav.CurrentPage > pagesSelectable/2 {
			startValue = (int(math.Min((float64(nav.CurrentPage)+math.Floor(float64(pagesSelectable)/2)), maxPages)) - pagesSelectable + 1)
		}
		endValue := (startValue + pagesSelectable - 1)
		if endValue > int(maxPages) {
			endValue = int(maxPages)
		}
		log.Println(nav.TotalItem)
		for i := startValue; i <= endValue; i++ {
			pageNum := strconv.Itoa(i)
			url, _ := Router.Get(nav.Route).URL("page", pageNum)
			ret = ret + "
" + strconv.Itoa(i) + "
"
		}
		if nav.CurrentPage < int(maxPages) {
			url, _ := Router.Get(nav.Route).URL("page", strconv.Itoa(nav.CurrentPage+1))
			ret = ret + "
»
"
		}
		}
		return template.HTML(ret)
	},
	"T": i18n.IdentityTfunc,
	"getAvatar": func(hash string, size int) string {
		return "https://www.gravatar.com/avatar/" + hash + "?s=" + strconv.Itoa(size)
	},
	"CurrentOrAdmin":       userPermission.CurrentOrAdmin,
	"CurrentUserIdentical": userPermission.CurrentUserIdentical,
	"HasAdmin":             userPermission.HasAdmin,
	"GetRole":              userPermission.GetRole,
	"IsFollower":           userPermission.IsFollower,
}
<commit_msg>Added a function to not have characters encoded by go template<commit_after>package router

import (
	"github.com/ewhal/nyaa/service/user/permission"
	"github.com/nicksnyder/go-i18n/i18n"
	"html/template"
	"log"
	"math"
	"net/url"
	"strconv"
)

var FuncMap = template.FuncMap{
	"min": math.Min,
	"genRoute": func(name string, params ...string) string {
		url, err := Router.Get(name).URL(params...)
		if err == nil {
			return url.String()
		}
		return "error"
	},
	"genRouteWithQuery": func(name string, currentUrl *url.URL, params ...string) template.HTML {
		url, err := Router.Get(name).URL(params...)
		if err == nil {
			return template.HTML(template.HTMLEscapeString(url.String() + "?" + currentUrl.RawQuery)) // TODO: Review application of character escaping
		}
		return "error"
	},
	"genNav": func(nav Navigation, currentUrl *url.URL, pagesSelectable int) template.HTML {
		var ret = ""
		if (nav.TotalItem > 0) {
		maxPages := math.Ceil(float64(nav.TotalItem) / float64(nav.MaxItemPerPage))

		if nav.CurrentPage-1 > 0 {
			url, _ := Router.Get(nav.Route).URL("page", "1")
			ret = ret + "
«
"
		}
		startValue := 1
		if nav.CurrentPage > pagesSelectable/2 {
			startValue = (int(math.Min((float64(nav.CurrentPage)+math.Floor(float64(pagesSelectable)/2)), maxPages)) - pagesSelectable + 1)
		}
		endValue := (startValue + pagesSelectable - 1)
		if endValue > int(maxPages) {
			endValue = int(maxPages)
		}
		log.Println(nav.TotalItem)
		for i := startValue; i <= endValue; i++ {
			pageNum := strconv.Itoa(i)
			url, _ := Router.Get(nav.Route).URL("page", pageNum)
			ret = ret + "
" + strconv.Itoa(i) + "
"
		}
		if nav.CurrentPage < int(maxPages) {
			url, _ := Router.Get(nav.Route).URL("page", strconv.Itoa(nav.CurrentPage+1))
			ret = ret + "
»
"
		}
		}
		return template.HTML(ret)
	},
	"T": i18n.IdentityTfunc,
	"getAvatar": func(hash string, size int) string {
		return "https://www.gravatar.com/avatar/" + hash + "?s=" + strconv.Itoa(size)
	},
	"CurrentOrAdmin":       userPermission.CurrentOrAdmin,
	"CurrentUserIdentical": userPermission.CurrentUserIdentical,
	"HasAdmin":             userPermission.HasAdmin,
	"GetRole":              userPermission.GetRole,
	"IsFollower":           userPermission.IsFollower,
	"NoEncode": func(str string) template.HTML {
		return template.HTML(str)
	},
}
<|endoftext|>


<commit_before>package couchbase

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/andrewwebber/cqrs"
	"github.com/couchbaselabs/go-couchbase"
	"log"
	"reflect"
	"sort"
	"time"
)

type cbVersionedEvent struct {
	ID            string    `json:"id"`
	CorrelationID string    `json:"correlationID"`
	SourceID      string    `json:"sourceID"`
	Version       int       `json:"version"`
	EventType     string    `json:"eventType"`
	Created       time.Time `json:"time"`
	Event         json.RawMessage
}

// EventStreamRepository : a Couchbase Server event stream repository
type EventStreamRepository struct {
	bucket *couchbase.Bucket
}

// NewEventStreamRepository creates new Couchbase Server based event stream repository
func NewEventStreamRepository(connectionString string) (*EventStreamRepository, error) {
	c, err := couchbase.Connect(connectionString)
	if err != nil {
		log.Println(fmt.Sprintf("Error connecting to couchbase : %v", err))
		return nil, err
	}

	pool, err := c.GetPool("default")
	if err != nil {
		log.Println(fmt.Sprintf("Error getting pool:  %v", err))
		return nil, err
	}

	bucket, err := pool.GetBucket("cqrs")
	if err != nil {
		log.Println(fmt.Sprintf("Error getting bucket:  %v", err))
		return nil, err
	}

	return &EventStreamRepository{bucket}, nil
}

// Save persists an event sourced object into the repository
func (r *EventStreamRepository) Save(sourceID string, events []cqrs.VersionedEvent) error {
	latestVersion := events[len(events)-1].Version
	for _, versionedEvent := range events {
		key := fmt.Sprintf("%s:%d", sourceID, versionedEvent.Version)
		if added, error := r.bucket.Add(key, 0, versionedEvent); error != nil || !added {
			return error
		}
	}

	return r.bucket.Set(sourceID, 0, latestVersion)
}

// SaveIntegrationEvent persists a published integration event
func (r *EventStreamRepository) SaveIntegrationEvent(event cqrs.VersionedEvent) error {
	counter, err := r.bucket.Incr("integration", 1, 1, 0)
	if err != nil {
		return err
	}

	key := fmt.Sprintf("integration::%d", counter)
	if err = r.bucket.Set(key, 0, event); err != nil {
		return err
	}

	return nil
}

// AllEventsEverPublished retreives all events every persisted
func (r *EventStreamRepository) AllEventsEverPublished() ([]cqrs.VersionedEvent, error) {
	var counter int
	if err := r.bucket.Get("integration", &counter); err != nil {
		return nil, err
	}

	var result []cqrs.VersionedEvent
	for i := 0; i < counter; i++ {
		key := fmt.Sprintf("integration::%d", i)
		events, err := r.Get(key)
		if err != nil {
			return nil, err
		}

		for _, event := range events {
			result = append(result, event)
		}
	}

	sort.Sort(cqrs.ByCreated(result))

	return result, nil
}

// Get retrieves events assoicated with an event sourced object by ID
func (r *EventStreamRepository) Get(id string) ([]cqrs.VersionedEvent, error) {
	var version int
	if error := r.bucket.Get(id, &version); error != nil {
		log.Println("Error getting event source ", id)
		return nil, error
	}

	var events []cqrs.VersionedEvent
	for versionNumber := 1; versionNumber <= version; versionNumber++ {
		eventKey := fmt.Sprintf("%s:%d", id, versionNumber)
		raw := new(cbVersionedEvent)

		if error := r.bucket.Get(eventKey, raw); error != nil {
			log.Println("Error getting event :", eventKey)
			return nil, error
		}

		typeRegistry := cqrs.NewTypeRegistry()

		eventType, ok := typeRegistry.GetEventType(raw.EventType)
		if !ok {
			log.Println("Cannot find event type", raw.EventType)
			return nil, errors.New("Cannot find event type " + raw.EventType)
		}

		eventValue := reflect.New(eventType)
		event := eventValue.Interface()
		if err := json.Unmarshal(raw.Event, event); err != nil {
			log.Println("Error deserializing event ", raw.Event)
			return nil, err
		}

		versionedEvent := cqrs.VersionedEvent{
			ID:        raw.ID,
			SourceID:  raw.SourceID,
			Version:   raw.Version,
			EventType: raw.EventType,
			Created:   raw.Created,
			Event:     reflect.Indirect(eventValue).Interface()}

		events = append(events, versionedEvent)
	}

	return events, nil
}
<commit_msg>Fixed couchbase provider after refactoring type registry<commit_after>package couchbase

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"reflect"
	"sort"
	"time"

	"github.com/andrewwebber/cqrs"
	"github.com/couchbaselabs/go-couchbase"
)

type cbVersionedEvent struct {
	ID            string    `json:"id"`
	CorrelationID string    `json:"correlationID"`
	SourceID      string    `json:"sourceID"`
	Version       int       `json:"version"`
	EventType     string    `json:"eventType"`
	Created       time.Time `json:"time"`
	Event         json.RawMessage
}

// EventStreamRepository : a Couchbase Server event stream repository
type EventStreamRepository struct {
	bucket *couchbase.Bucket
}

// NewEventStreamRepository creates new Couchbase Server based event stream repository
func NewEventStreamRepository(connectionString string) (*EventStreamRepository, error) {
	c, err := couchbase.Connect(connectionString)
	if err != nil {
		log.Println(fmt.Sprintf("Error connecting to couchbase : %v", err))
		return nil, err
	}

	pool, err := c.GetPool("default")
	if err != nil {
		log.Println(fmt.Sprintf("Error getting pool:  %v", err))
		return nil, err
	}

	bucket, err := pool.GetBucket("cqrs")
	if err != nil {
		log.Println(fmt.Sprintf("Error getting bucket:  %v", err))
		return nil, err
	}

	return &EventStreamRepository{bucket}, nil
}

// Save persists an event sourced object into the repository
func (r *EventStreamRepository) Save(sourceID string, events []cqrs.VersionedEvent) error {
	latestVersion := events[len(events)-1].Version
	for _, versionedEvent := range events {
		key := fmt.Sprintf("%s:%d", sourceID, versionedEvent.Version)
		if added, error := r.bucket.Add(key, 0, versionedEvent); error != nil || !added {
			return error
		}
	}

	return r.bucket.Set(sourceID, 0, latestVersion)
}

// SaveIntegrationEvent persists a published integration event
func (r *EventStreamRepository) SaveIntegrationEvent(event cqrs.VersionedEvent) error {
	counter, err := r.bucket.Incr("integration", 1, 1, 0)
	if err != nil {
		return err
	}

	key := fmt.Sprintf("integration::%d", counter)
	if err = r.bucket.Set(key, 0, event); err != nil {
		return err
	}

	return nil
}

// AllEventsEverPublished retreives all events every persisted
func (r *EventStreamRepository) AllEventsEverPublished() ([]cqrs.VersionedEvent, error) {
	var counter int
	if err := r.bucket.Get("integration", &counter); err != nil {
		return nil, err
	}

	var result []cqrs.VersionedEvent
	for i := 0; i < counter; i++ {
		key := fmt.Sprintf("integration::%d", i)
		events, err := r.Get(key)
		if err != nil {
			return nil, err
		}

		for _, event := range events {
			result = append(result, event)
		}
	}

	sort.Sort(cqrs.ByCreated(result))

	return result, nil
}

// Get retrieves events assoicated with an event sourced object by ID
func (r *EventStreamRepository) Get(id string) ([]cqrs.VersionedEvent, error) {
	var version int
	if error := r.bucket.Get(id, &version); error != nil {
		log.Println("Error getting event source ", id)
		return nil, error
	}

	var events []cqrs.VersionedEvent
	for versionNumber := 1; versionNumber <= version; versionNumber++ {
		eventKey := fmt.Sprintf("%s:%d", id, versionNumber)
		raw := new(cbVersionedEvent)

		if error := r.bucket.Get(eventKey, raw); error != nil {
			log.Println("Error getting event :", eventKey)
			return nil, error
		}

		typeRegistry := cqrs.NewTypeRegistry()

		eventType, ok := typeRegistry.GetTypeByName(raw.EventType)
		if !ok {
			log.Println("Cannot find event type", raw.EventType)
			return nil, errors.New("Cannot find event type " + raw.EventType)
		}

		eventValue := reflect.New(eventType)
		event := eventValue.Interface()
		if err := json.Unmarshal(raw.Event, event); err != nil {
			log.Println("Error deserializing event ", raw.Event)
			return nil, err
		}

		versionedEvent := cqrs.VersionedEvent{
			ID:        raw.ID,
			SourceID:  raw.SourceID,
			Version:   raw.Version,
			EventType: raw.EventType,
			Created:   raw.Created,
			Event:     reflect.Indirect(eventValue).Interface()}

		events = append(events, versionedEvent)
	}

	return events, nil
}
<|endoftext|>


<commit_before>package v1

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	redis "gopkg.in/redis.v5"
	"zxq.co/ripple/rippleapi/common"
)

type setAllowedData struct {
	UserID  int `json:"user_id"`
	Allowed int `json:"allowed"`
}

// UserManageSetAllowedPOST allows to set the allowed status of an user.
func UserManageSetAllowedPOST(md common.MethodData) common.CodeMessager {
	var data setAllowedData
	if err := md.Unmarshal(&data); err != nil {
		return ErrBadJSON
	}
	if data.Allowed < 0 || data.Allowed > 2 {
		return common.SimpleResponse(400, "Allowed status must be between 0 and 2")
	}
	var banDatetime int64
	var privsSet string
	if data.Allowed == 0 {
		banDatetime = time.Now().Unix()
		privsSet = "privileges = (privileges & ~3)"
	} else if data.Allowed == 1 {
		banDatetime = 0
		privsSet = "privileges = (privileges | 3)"
	} else if data.Allowed == 2 {
		banDatetime = time.Now().Unix()
		privsSet = "privileges = (privileges | 2) & (privileges & ~1)"
	}
	_, err := md.DB.Exec("UPDATE users SET "+privsSet+", ban_datetime = ? WHERE id = ?", banDatetime, data.UserID)
	if err != nil {
		md.Err(err)
		return Err500
	}
	rapLog(md, fmt.Sprintf("changed UserID:%d's allowed to %d. This was done using the API's terrible ManageSetAllowed.", data.UserID, data.Allowed))
	go fixPrivileges(data.UserID, md.DB)
	query := `
SELECT users.id, users.username, register_datetime, privileges,
	latest_activity, users_stats.username_aka,
	users_stats.country
FROM users
LEFT JOIN users_stats
ON users.id=users_stats.id
WHERE users.id=?
LIMIT 1`
	return userPutsSingle(md, md.DB.QueryRowx(query, data.UserID))
}

type userEditData struct {
	ID            int          `json:"id"`
	Username      *string      `json:"username"`
	UsernameAKA   *string      `json:"username_aka"`
	Privileges    *uint64      `json:"privileges"`
	Country       *string      `json:"country"`
	SilenceInfo   *silenceInfo `json:"silence_info"`
	ResetUserpage bool         `json:"reset_userpage"`
	//ResetAvatar bool `json:"reset_avatar"`
}

var privChangeList = [...]string{
	"banned",
	"locked",
	"restricted",
	"removed all restrictions on",
}

// UserEditPOST allows to edit an user's information.
func UserEditPOST(md common.MethodData) common.CodeMessager {
	var data userEditData
	if err := md.Unmarshal(&data); err != nil {
		fmt.Println(err)
		return ErrBadJSON
	}

	if data.ID == 0 {
		return common.SimpleResponse(404, "That user could not be found")
	}

	var prevUser struct {
		Username   string
		Privileges uint64
	}
	err := md.DB.Get(&prevUser, "SELECT username, privileges FROM users WHERE id = ? LIMIT 1", data.ID)

	switch err {
	case nil: // carry on
	case sql.ErrNoRows:
		return common.SimpleResponse(404, "That user could not be found")
	default:
		md.Err(err)
		return Err500
	}

	const initQuery = "UPDATE users SET\n"
	q := initQuery
	var args []interface{}

	// totally did not realise I had to update some fields in users_stats as well
	// and just copy pasting the above code by prefixing "stats" to every
	// variable
	const statsInitQuery = "UPDATE users_stats SET\n"
	statsQ := statsInitQuery
	var statsArgs []interface{}

	if common.UserPrivileges(prevUser.Privileges)&common.AdminPrivilegeManageUsers != 0 &&
		data.ID != md.User.UserID {
		return common.SimpleResponse(403, "Can't edit that user")
	}

	var isBanned bool
	var isSilenced bool
	if data.Privileges != nil {
		// If we want to modify privileges other than Normal/Public, we need to have
		// the right privilege ourselves and AdminManageUsers won't suffice.
		if (*data.Privileges&^3) != (prevUser.Privileges&^3) &&
			md.User.UserPrivileges&common.AdminPrivilegeManagePrivilege == 0 {
			return common.SimpleResponse(403, "Can't modify user privileges without AdminManagePrivileges")
		}
		q += "privileges = ?,\n"
		args = append(args, *data.Privileges)

		// UserPublic became 0, so banned or restricted
		const uPublic = uint64(common.UserPrivilegePublic)
		if *data.Privileges&uPublic == 0 && prevUser.Privileges&uPublic != 0 {
			q += "ban_datetime = ?,\n"
			args = append(args, time.Now().Unix())
			isBanned = true
		}

		// If we modified other privileges apart from Normal and Public, we use a generic
		// "changed user's privileges". Otherwise, we are more descriptive.
		if *data.Privileges^prevUser.Privileges > 3 {
			rapLog(md, fmt.Sprintf("has changed %s's privileges", prevUser.Username))
		} else {
			rapLog(md, fmt.Sprintf("has %s %s", privChangeList[*data.Privileges&3], prevUser.Username))
		}
	}
	if data.Username != nil {
		if strings.Contains(*data.Username, " ") && strings.Contains(*data.Username, "_") {
			return common.SimpleResponse(400, "Mixed spaces and underscores")
		}
		if usernameAvailable(md, *data.Username, data.ID) {
			return common.SimpleResponse(409, "User with that username exists")
		}
		jsonData, _ := json.Marshal(struct {
			UserID      int    `json:"userID"`
			NewUsername string `json:"newUsername"`
		}{data.ID, *data.Username})
		md.R.Publish("peppy:change_username", string(jsonData))
	}
	if data.UsernameAKA != nil {
		statsQ += "username_aka = ?,\n"
		statsArgs = append(statsArgs, *data.UsernameAKA)
	}
	if data.Country != nil {
		statsQ += "country = ?,\n"
		statsArgs = append(statsArgs, *data.Country)
		rapLog(md, fmt.Sprintf("has changed %s country to %s", prevUser.Username, *data.Country))
		appendToUserNotes(md, "country changed to "+*data.Country, data.ID)
	}
	if data.SilenceInfo != nil && md.User.UserPrivileges&common.AdminPrivilegeSilenceUsers != 0 {
		q += "silence_end = ?, silence_reason = ?,\n"
		args = append(args, time.Time(data.SilenceInfo.End).Unix(), data.SilenceInfo.Reason)
		isSilenced = true
	}
	if data.ResetUserpage {
		statsQ += "userpage_content = '',\n"
	}

	if q != initQuery {
		q = q[:len(q)-2] + " WHERE id = ? LIMIT 1"
		args = append(args, data.ID)
		_, err = md.DB.Exec(q, args...)
		if err != nil {
			md.Err(err)
			return Err500
		}

	}
	if statsQ != statsInitQuery {
		statsQ = statsQ[:len(statsQ)-2] + " WHERE id = ? LIMIT 1"
		statsArgs = append(statsArgs, data.ID)
		_, err = md.DB.Exec(statsQ, statsArgs...)
		if err != nil {
			md.Err(err)
			return Err500
		}
	}

	if isBanned || isSilenced {
		if err := updateBanBancho(md.R, data.ID); err != nil {
			md.Err(err)
			return Err500
		}
	}

	rapLog(md, fmt.Sprintf("has updated user %s", prevUser.Username))

	return userPutsSingle(md, md.DB.QueryRowx(userFields+" WHERE users.id = ? LIMIT 1", data.ID))
}

func updateBanBancho(r *redis.Client, user int) error {
	return r.Publish("peppy:ban", strconv.Itoa(user)).Err()
}

type wipeUserData struct {
	ID    int   `json:"id"`
	Modes []int `json:"modes"`
	Relax int   `json:"relax"`
}

// WipeUserPOST wipes an user's scores.
func WipeUserPOST(md common.MethodData) common.CodeMessager {
	data := wipeUserData{
		// Wipe both classic & relax by default
		Relax: -1,
	}
	if err := md.Unmarshal(&data); err != nil {
		return ErrBadJSON
	}
	if data.ID == 0 {
		return ErrMissingField("id")
	}
	if len(data.Modes) == 0 {
		return ErrMissingField("modes")
	}

	var userData struct {
		Username   string
		Privileges uint64
	}
	err := md.DB.Get(&userData, "SELECT username, privileges FROM users WHERE id = ?", data.ID)
	switch err {
	case sql.ErrNoRows:
		return common.SimpleResponse(404, "That user could not be found!")
	case nil: // carry on
	default:
		md.Err(err)
		return Err500
	}

	if common.UserPrivileges(userData.Privileges)&common.AdminPrivilegeManageUsers != 0 {
		return common.SimpleResponse(403, "Can't edit that user")
	}

	tx, err := md.DB.Beginx()
	if err != nil {
		md.Err(err)
		return Err500
	}

	for _, mode := range data.Modes {
		if mode < 0 || mode > 3 {
			continue
		}
		var suffix string
		if data.Relax > -1 {
			suffix = fmt.Sprintf("AND is_relax = %d", data.Relax)
		}
		_, err = tx.Exec("INSERT INTO scores_removed SELECT * FROM scores WHERE userid = ? AND play_mode = ? "+suffix, data.ID, mode)
		if err != nil {
			md.Err(err)
		}
		_, err = tx.Exec("DELETE FROM scores WHERE userid = ? AND play_mode = ? "+suffix, data.ID, mode)
		if err != nil {
			md.Err(err)
		}
		if data.Relax <= 0 {
			// Wipe classic stats
			_, err = tx.Exec(strings.Replace(
				`UPDATE users_stats SET total_score_MODE = 0, ranked_score_MODE = 0, replays_watched_MODE = 0,
				playcount_MODE = 0, avg_accuracy_MODE = 0, total_hits_MODE = 0, level_MODE = 0, pp_MODE = 0
				WHERE id = ?`, "MODE", modesToReadable[mode], -1,
			), data.ID)
			if err != nil {
				md.Err(err)
			}
		}
		if data.Relax < 0 || data.Relax == 1 {
			// Wipe relax stats
			_, err = tx.Exec(strings.Replace(
				`UPDATE users_stats_relax SET total_score_MODE = 0, ranked_score_MODE = 0,
				playcount_MODE = 0, avg_accuracy_MODE = 0, total_hits_MODE = 0, level_MODE = 0, pp_MODE = 0
				WHERE id = ?`, "MODE", modesToReadable[mode], -1,
			), data.ID)
			if err != nil {
				md.Err(err)
			}
		}
		_, err = tx.Exec("DELETE FROM users_beatmap_playcount WHERE user_id = ? AND game_mode = ?", data.ID, mode)
		if err != nil {
			md.Err(err)
		}
	}

	if err = tx.Commit(); err != nil {
		md.Err(err)
		return Err500
	}

	rapLog(md, fmt.Sprintf("has wiped %s's account", userData.Username))

	return userPutsSingle(md, md.DB.QueryRowx(userFields+" WHERE users.id = ? LIMIT 1", data.ID))
}

func appendToUserNotes(md common.MethodData, message string, user int) {
	message = "\n[" + time.Now().Format("2006-01-02 15:04:05") + "] API: " + message
	_, err := md.DB.Exec("UPDATE users SET notes = CONCAT(COALESCE(notes, ''), ?) WHERE id = ?",
		message, user)
	if err != nil {
		md.Err(err)
	}
}

func usernameAvailable(md common.MethodData, u string, userID int) (r bool) {
	err := md.DB.QueryRow("SELECT EXISTS(SELECT 1 FROM users WHERE username_safe = ? AND id != ?)", common.SafeUsername(u), userID).Scan(&r)
	if err != nil && err != sql.ErrNoRows {
		md.Err(err)
	}
	return
}
<commit_msg>Wipe beatmap playcount only when wiping both classic and relax<commit_after>package v1

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	redis "gopkg.in/redis.v5"
	"zxq.co/ripple/rippleapi/common"
)

type setAllowedData struct {
	UserID  int `json:"user_id"`
	Allowed int `json:"allowed"`
}

// UserManageSetAllowedPOST allows to set the allowed status of an user.
func UserManageSetAllowedPOST(md common.MethodData) common.CodeMessager {
	var data setAllowedData
	if err := md.Unmarshal(&data); err != nil {
		return ErrBadJSON
	}
	if data.Allowed < 0 || data.Allowed > 2 {
		return common.SimpleResponse(400, "Allowed status must be between 0 and 2")
	}
	var banDatetime int64
	var privsSet string
	if data.Allowed == 0 {
		banDatetime = time.Now().Unix()
		privsSet = "privileges = (privileges & ~3)"
	} else if data.Allowed == 1 {
		banDatetime = 0
		privsSet = "privileges = (privileges | 3)"
	} else if data.Allowed == 2 {
		banDatetime = time.Now().Unix()
		privsSet = "privileges = (privileges | 2) & (privileges & ~1)"
	}
	_, err := md.DB.Exec("UPDATE users SET "+privsSet+", ban_datetime = ? WHERE id = ?", banDatetime, data.UserID)
	if err != nil {
		md.Err(err)
		return Err500
	}
	rapLog(md, fmt.Sprintf("changed UserID:%d's allowed to %d. This was done using the API's terrible ManageSetAllowed.", data.UserID, data.Allowed))
	go fixPrivileges(data.UserID, md.DB)
	query := `
SELECT users.id, users.username, register_datetime, privileges,
	latest_activity, users_stats.username_aka,
	users_stats.country
FROM users
LEFT JOIN users_stats
ON users.id=users_stats.id
WHERE users.id=?
LIMIT 1`
	return userPutsSingle(md, md.DB.QueryRowx(query, data.UserID))
}

type userEditData struct {
	ID            int          `json:"id"`
	Username      *string      `json:"username"`
	UsernameAKA   *string      `json:"username_aka"`
	Privileges    *uint64      `json:"privileges"`
	Country       *string      `json:"country"`
	SilenceInfo   *silenceInfo `json:"silence_info"`
	ResetUserpage bool         `json:"reset_userpage"`
	//ResetAvatar bool `json:"reset_avatar"`
}

var privChangeList = [...]string{
	"banned",
	"locked",
	"restricted",
	"removed all restrictions on",
}

// UserEditPOST allows to edit an user's information.
func UserEditPOST(md common.MethodData) common.CodeMessager {
	var data userEditData
	if err := md.Unmarshal(&data); err != nil {
		fmt.Println(err)
		return ErrBadJSON
	}

	if data.ID == 0 {
		return common.SimpleResponse(404, "That user could not be found")
	}

	var prevUser struct {
		Username   string
		Privileges uint64
	}
	err := md.DB.Get(&prevUser, "SELECT username, privileges FROM users WHERE id = ? LIMIT 1", data.ID)

	switch err {
	case nil: // carry on
	case sql.ErrNoRows:
		return common.SimpleResponse(404, "That user could not be found")
	default:
		md.Err(err)
		return Err500
	}

	const initQuery = "UPDATE users SET\n"
	q := initQuery
	var args []interface{}

	// totally did not realise I had to update some fields in users_stats as well
	// and just copy pasting the above code by prefixing "stats" to every
	// variable
	const statsInitQuery = "UPDATE users_stats SET\n"
	statsQ := statsInitQuery
	var statsArgs []interface{}

	if common.UserPrivileges(prevUser.Privileges)&common.AdminPrivilegeManageUsers != 0 &&
		data.ID != md.User.UserID {
		return common.SimpleResponse(403, "Can't edit that user")
	}

	var isBanned bool
	var isSilenced bool
	if data.Privileges != nil {
		// If we want to modify privileges other than Normal/Public, we need to have
		// the right privilege ourselves and AdminManageUsers won't suffice.
		if (*data.Privileges&^3) != (prevUser.Privileges&^3) &&
			md.User.UserPrivileges&common.AdminPrivilegeManagePrivilege == 0 {
			return common.SimpleResponse(403, "Can't modify user privileges without AdminManagePrivileges")
		}
		q += "privileges = ?,\n"
		args = append(args, *data.Privileges)

		// UserPublic became 0, so banned or restricted
		const uPublic = uint64(common.UserPrivilegePublic)
		if *data.Privileges&uPublic == 0 && prevUser.Privileges&uPublic != 0 {
			q += "ban_datetime = ?,\n"
			args = append(args, time.Now().Unix())
			isBanned = true
		}

		// If we modified other privileges apart from Normal and Public, we use a generic
		// "changed user's privileges". Otherwise, we are more descriptive.
		if *data.Privileges^prevUser.Privileges > 3 {
			rapLog(md, fmt.Sprintf("has changed %s's privileges", prevUser.Username))
		} else {
			rapLog(md, fmt.Sprintf("has %s %s", privChangeList[*data.Privileges&3], prevUser.Username))
		}
	}
	if data.Username != nil {
		if strings.Contains(*data.Username, " ") && strings.Contains(*data.Username, "_") {
			return common.SimpleResponse(400, "Mixed spaces and underscores")
		}
		if usernameAvailable(md, *data.Username, data.ID) {
			return common.SimpleResponse(409, "User with that username exists")
		}
		jsonData, _ := json.Marshal(struct {
			UserID      int    `json:"userID"`
			NewUsername string `json:"newUsername"`
		}{data.ID, *data.Username})
		md.R.Publish("peppy:change_username", string(jsonData))
	}
	if data.UsernameAKA != nil {
		statsQ += "username_aka = ?,\n"
		statsArgs = append(statsArgs, *data.UsernameAKA)
	}
	if data.Country != nil {
		statsQ += "country = ?,\n"
		statsArgs = append(statsArgs, *data.Country)
		rapLog(md, fmt.Sprintf("has changed %s country to %s", prevUser.Username, *data.Country))
		appendToUserNotes(md, "country changed to "+*data.Country, data.ID)
	}
	if data.SilenceInfo != nil && md.User.UserPrivileges&common.AdminPrivilegeSilenceUsers != 0 {
		q += "silence_end = ?, silence_reason = ?,\n"
		args = append(args, time.Time(data.SilenceInfo.End).Unix(), data.SilenceInfo.Reason)
		isSilenced = true
	}
	if data.ResetUserpage {
		statsQ += "userpage_content = '',\n"
	}

	if q != initQuery {
		q = q[:len(q)-2] + " WHERE id = ? LIMIT 1"
		args = append(args, data.ID)
		_, err = md.DB.Exec(q, args...)
		if err != nil {
			md.Err(err)
			return Err500
		}

	}
	if statsQ != statsInitQuery {
		statsQ = statsQ[:len(statsQ)-2] + " WHERE id = ? LIMIT 1"
		statsArgs = append(statsArgs, data.ID)
		_, err = md.DB.Exec(statsQ, statsArgs...)
		if err != nil {
			md.Err(err)
			return Err500
		}
	}

	if isBanned || isSilenced {
		if err := updateBanBancho(md.R, data.ID); err != nil {
			md.Err(err)
			return Err500
		}
	}

	rapLog(md, fmt.Sprintf("has updated user %s", prevUser.Username))

	return userPutsSingle(md, md.DB.QueryRowx(userFields+" WHERE users.id = ? LIMIT 1", data.ID))
}

func updateBanBancho(r *redis.Client, user int) error {
	return r.Publish("peppy:ban", strconv.Itoa(user)).Err()
}

type wipeUserData struct {
	ID    int   `json:"id"`
	Modes []int `json:"modes"`
	Relax int   `json:"relax"`
}

// WipeUserPOST wipes an user's scores.
func WipeUserPOST(md common.MethodData) common.CodeMessager {
	data := wipeUserData{
		// Wipe both classic & relax by default
		Relax: -1,
	}
	if err := md.Unmarshal(&data); err != nil {
		return ErrBadJSON
	}
	if data.ID == 0 {
		return ErrMissingField("id")
	}
	if len(data.Modes) == 0 {
		return ErrMissingField("modes")
	}

	var userData struct {
		Username   string
		Privileges uint64
	}
	err := md.DB.Get(&userData, "SELECT username, privileges FROM users WHERE id = ?", data.ID)
	switch err {
	case sql.ErrNoRows:
		return common.SimpleResponse(404, "That user could not be found!")
	case nil: // carry on
	default:
		md.Err(err)
		return Err500
	}

	if common.UserPrivileges(userData.Privileges)&common.AdminPrivilegeManageUsers != 0 {
		return common.SimpleResponse(403, "Can't edit that user")
	}

	tx, err := md.DB.Beginx()
	if err != nil {
		md.Err(err)
		return Err500
	}

	for _, mode := range data.Modes {
		if mode < 0 || mode > 3 {
			continue
		}
		var suffix string
		if data.Relax > -1 {
			suffix = fmt.Sprintf("AND is_relax = %d", data.Relax)
		}
		_, err = tx.Exec("INSERT INTO scores_removed SELECT * FROM scores WHERE userid = ? AND play_mode = ? "+suffix, data.ID, mode)
		if err != nil {
			md.Err(err)
		}
		_, err = tx.Exec("DELETE FROM scores WHERE userid = ? AND play_mode = ? "+suffix, data.ID, mode)
		if err != nil {
			md.Err(err)
		}
		if data.Relax <= 0 {
			// Wipe classic stats
			_, err = tx.Exec(strings.Replace(
				`UPDATE users_stats SET total_score_MODE = 0, ranked_score_MODE = 0, replays_watched_MODE = 0,
				playcount_MODE = 0, avg_accuracy_MODE = 0, total_hits_MODE = 0, level_MODE = 0, pp_MODE = 0
				WHERE id = ?`, "MODE", modesToReadable[mode], -1,
			), data.ID)
			if err != nil {
				md.Err(err)
			}
		}
		if data.Relax < 0 || data.Relax == 1 {
			// Wipe relax stats
			_, err = tx.Exec(strings.Replace(
				`UPDATE users_stats_relax SET total_score_MODE = 0, ranked_score_MODE = 0,
				playcount_MODE = 0, avg_accuracy_MODE = 0, total_hits_MODE = 0, level_MODE = 0, pp_MODE = 0
				WHERE id = ?`, "MODE", modesToReadable[mode], -1,
			), data.ID)
			if err != nil {
				md.Err(err)
			}
		}
		if data.Relax < 0 {
			// Reset playcount for each beatmap only when wiping
			// both classic and relax. Per-betmap playcount is
			// shared between classic and relax
			_, err = tx.Exec("DELETE FROM users_beatmap_playcount WHERE user_id = ? AND game_mode = ?", data.ID, mode)
			if err != nil {
				md.Err(err)
			}
		}
	}

	if err = tx.Commit(); err != nil {
		md.Err(err)
		return Err500
	}

	rapLog(md, fmt.Sprintf("has wiped %s's account", userData.Username))

	return userPutsSingle(md, md.DB.QueryRowx(userFields+" WHERE users.id = ? LIMIT 1", data.ID))
}

func appendToUserNotes(md common.MethodData, message string, user int) {
	message = "\n[" + time.Now().Format("2006-01-02 15:04:05") + "] API: " + message
	_, err := md.DB.Exec("UPDATE users SET notes = CONCAT(COALESCE(notes, ''), ?) WHERE id = ?",
		message, user)
	if err != nil {
		md.Err(err)
	}
}

func usernameAvailable(md common.MethodData, u string, userID int) (r bool) {
	err := md.DB.QueryRow("SELECT EXISTS(SELECT 1 FROM users WHERE username_safe = ? AND id != ?)", common.SafeUsername(u), userID).Scan(&r)
	if err != nil && err != sql.ErrNoRows {
		md.Err(err)
	}
	return
}
<|endoftext|>


<commit_before>package vindexes

import (
	"reflect"
	"testing"
)

var varcharHash Vindex

func init() {
	varcharHash, _ = CreateVindex("varcharHash", "vch", nil)
}

func TestVarcharHashCost(t *testing.T) {
	if varcharHash.Cost() != 1 {
		t.Errorf("Cost(): %d, want 1", varcharHash.Cost())
	}
}

func TestVarcharMap(t *testing.T) {
	/*const sample  = "TEST"
	for i := 0; i < len(sample); i++ {
		fmt.Printf("%x ", sample[i])
	}*/
	got, err := varcharHash.(Unique).Map(nil, []interface{}{"TEST", "test", "TeSt"})
	if err != nil {
		t.Error(err)
	}
	want := [][]byte{
		[]byte("\x07\x0c\x00\x04\x74\x65\x73\x74"),
		[]byte("\x07\x0c\x00\x04\x74\x65\x73\x74"),
		[]byte("\x07\x0c\x00\x04\x74\x65\x73\x74"),
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("Map(): %#v, want %+v", got, want)
	}
}

func TestVarCharVerify(t *testing.T) {
	success, err := varcharHash.Verify(nil, "TeSt", []byte("\x07\x0c\x00\x04\x74\x65\x73\x74"))
	if err != nil {
		t.Error(err)
	}
	if !success {
		t.Errorf("Verify(): %+v, want true", success)
	}
}
<commit_msg>Removing Commented Code<commit_after>package vindexes

import (
	"reflect"
	"testing"
)

var varcharHash Vindex

func init() {
	varcharHash, _ = CreateVindex("varcharHash", "vch", nil)
}

func TestVarcharHashCost(t *testing.T) {
	if varcharHash.Cost() != 1 {
		t.Errorf("Cost(): %d, want 1", varcharHash.Cost())
	}
}

func TestVarcharMap(t *testing.T) {
	got, err := varcharHash.(Unique).Map(nil, []interface{}{"TEST", "test", "TeSt"})
	if err != nil {
		t.Error(err)
	}
	want := [][]byte{
		[]byte("\x07\x0c\x00\x04\x74\x65\x73\x74"),
		[]byte("\x07\x0c\x00\x04\x74\x65\x73\x74"),
		[]byte("\x07\x0c\x00\x04\x74\x65\x73\x74"),
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("Map(): %#v, want %+v", got, want)
	}
}

func TestVarCharVerify(t *testing.T) {
	success, err := varcharHash.Verify(nil, "TeSt", []byte("\x07\x0c\x00\x04\x74\x65\x73\x74"))
	if err != nil {
		t.Error(err)
	}
	if !success {
		t.Errorf("Verify(): %+v, want true", success)
	}
}
<|endoftext|>


<commit_before>// Package size implements human representation of bytes size.
package size

import "fmt"

// A Byte represents a number of bytes.
type Byte int64

const (
	_       = iota // ignore first value by assigning to blank identifier
	KB Byte = 1 << (10 * iota)
	MB
	GB
	TB
	PB
	EB
)

// String represents a Byte with the most appropriate unit.
func (b Byte) String() string {
	switch {
	case b >= EB:
		return fmt.Sprintf("%vEB", b.fmt(EB))
	case b >= PB:
		return fmt.Sprintf("%vPB", b.fmt(PB))
	case b >= TB:
		return fmt.Sprintf("%vTB", b.fmt(TB))
	case b >= GB:
		return fmt.Sprintf("%vGB", b.fmt(GB))
	case b >= MB:
		return fmt.Sprintf("%vMB", b.fmt(MB))
	case b >= KB:
		return fmt.Sprintf("%vKB", b.fmt(KB))
	}
	return fmt.Sprintf("%vB", b)
}

// fmt represents a Byte in a unit with 3 significant digits minimum.
func (b Byte) fmt(unit Byte) string {
	f := "%.0f"
	if b < 10*unit {
		f = "%.2f"
	} else if b < 100*unit {
		f = "%.1f"
	}
	return fmt.Sprintf(f, float64(b)/float64(unit))
}
<commit_msg>lib/size: avoid recursive call to String<commit_after>// Package size implements human representation of bytes size.
package size

import "fmt"

// A Byte represents a number of bytes.
type Byte int64

const (
	_       = iota // ignore first value by assigning to blank identifier
	KB Byte = 1 << (10 * iota)
	MB
	GB
	TB
	PB
	EB
)

// String represents a Byte with the most appropriate unit.
func (b Byte) String() string {
	switch {
	case b >= EB:
		return fmt.Sprintf("%vEB", b.fmt(EB))
	case b >= PB:
		return fmt.Sprintf("%vPB", b.fmt(PB))
	case b >= TB:
		return fmt.Sprintf("%vTB", b.fmt(TB))
	case b >= GB:
		return fmt.Sprintf("%vGB", b.fmt(GB))
	case b >= MB:
		return fmt.Sprintf("%vMB", b.fmt(MB))
	case b >= KB:
		return fmt.Sprintf("%vKB", b.fmt(KB))
	}
	return fmt.Sprintf("%dB", b)
}

// fmt represents a Byte in a unit with 3 significant digits minimum.
func (b Byte) fmt(unit Byte) string {
	f := "%.0f"
	if b < 10*unit {
		f = "%.2f"
	} else if b < 100*unit {
		f = "%.1f"
	}
	return fmt.Sprintf(f, float64(b)/float64(unit))
}
<|endoftext|>


<commit_before>package clienttest

import (
	"fmt"
	"time"

	check "gopkg.in/check.v1"

	"github.com/radanalyticsio/oshinko-cli/rest/client/clusters"
	"github.com/radanalyticsio/oshinko-cli/rest/helpers/errors"
	"github.com/radanalyticsio/oshinko-cli/rest/models"
)

type lessThanChecker struct {
	*check.CheckerInfo
}

// The LessThan checker attempts to determine if the observed value is
// less than the expected. It will only attempt this on values that can be
// cast as int64.
var LessThan check.Checker = &lessThanChecker{
	&check.CheckerInfo{Name: "LessThan", Params: []string{"obtained", "compare"}},
}

func (checker *lessThanChecker) Check(params []interface{}, names []string) (result bool, error string) {
	defer func() {
		if v := recover(); v != nil {
			result = false
			error = fmt.Sprint(v)
		}
	}()
	return params[0].(int) < params[1].(int), ""
}

func checkClusterHelper(s *OshinkoRestTestSuite, c *check.C, params *clusters.FindSingleClusterParams, count int) (obsmcount int64, obswcount int64) {
	const retries = 30
	var tries int

	for tries = 0; tries < retries; tries++ {
		cldresult, err := s.cli.Clusters.FindSingleCluster(params)
		if err != nil {
			msg := fmt.Sprintf("%s \n %+v",
				err.(*clusters.FindSingleClusterDefault).Error(),
				err.(*clusters.FindSingleClusterDefault).Payload.Errors)
			c.Fatal(msg)
		}

		// if enough pods are available, we loop through them and exit
		// the retry loop, otherwise sleep for 1 second.
		if len(cldresult.Payload.Cluster.Pods) >= count {
			// loop through the pods to count workers and masters
			obsmcount = 0
			obswcount = 0
			for _, pod := range cldresult.Payload.Cluster.Pods {
				switch *pod.Type {
				case "master":
					if *pod.Status == "Running" {
						obsmcount += 1
					}
				case "worker":
					if *pod.Status == "Running" {
						obswcount += 1
					}
				}
			}
			if int(obsmcount+obswcount) == count {
				break
			}
		}

		time.Sleep(1 * time.Second)
	}
	c.Assert(tries, LessThan, retries)
	return
}

func (s *OshinkoRestTestSuite) TestCreateAndDeleteCluster(c *check.C) {
	cname := "e2ecluster"
	mcount := int64(1)
	wcount := int64(3)

	cconfig := models.NewClusterConfig{MasterCount: &mcount, WorkerCount: &wcount}
	cdetails := models.NewCluster{Name: &cname, Config: &cconfig}
	clparams := clusters.NewCreateClusterParams().WithCluster(&cdetails)

	// create a cluster
	_, err := s.cli.Clusters.CreateCluster(clparams)
	if err != nil {
		msg := err.(*clusters.CreateClusterDefault).Error() + "\n"
		for _, e := range err.(*clusters.CreateClusterDefault).Payload.Errors {
			msg += errors.SingleErrorToString(e)
		}
		c.Fatal(msg)
	}

	// read the cluster details
	// because it may take some time for the pods to become available, we
	// must loop and try multiple times to read them. if we fail to read
	// them after a set number of retries, we consider the test to have
	// failed.
	cldparams := clusters.NewFindSingleClusterParams().WithName(cname)
	obsmcount, obswcount := checkClusterHelper(s, c, cldparams, int(mcount+wcount))

	c.Assert(obsmcount, check.Equals, mcount)
	c.Assert(obswcount, check.Equals, wcount)

	// scale up the cluster
	// this will attempt to scale up the number of workers by 1. as with
	// the creation test, this test will loop for a number of retries to
	// give time for the new worker to be created.
	uwcount := int64(wcount + 1)
	ucconfig := models.NewClusterConfig{MasterCount: &mcount, WorkerCount: &uwcount}
	ucdetails := models.NewCluster{Name: &cname, Config: &ucconfig}
	uclparams := clusters.NewUpdateSingleClusterParams().WithCluster(&ucdetails).WithName(cname)

	// update the cluster
	_, err = s.cli.Clusters.UpdateSingleCluster(uclparams)
	if err != nil {
		msg := err.(*clusters.UpdateSingleClusterDefault).Error() + "\n"
		for _, e := range err.(*clusters.UpdateSingleClusterDefault).Payload.Errors {
			msg += errors.SingleErrorToString(e)
		}
		c.Fatal(msg)
	}

	// check for update completion
	obsmcount, obswcount = checkClusterHelper(s, c, cldparams, int(mcount+uwcount))

	c.Assert(obsmcount, check.Equals, mcount)
	c.Assert(obswcount, check.Equals, uwcount)

	// scale down the cluster
	uwcount = int64(wcount - 1)
	ucconfig = models.NewClusterConfig{MasterCount: &mcount, WorkerCount: &uwcount}
	ucdetails = models.NewCluster{Name: &cname, Config: &ucconfig}
	uclparams = clusters.NewUpdateSingleClusterParams().WithCluster(&ucdetails).WithName(cname)

	// update the cluster
	_, err = s.cli.Clusters.UpdateSingleCluster(uclparams)
	if err != nil {
		msg := err.(*clusters.UpdateSingleClusterDefault).Error() + "\n"
		for _, e := range err.(*clusters.UpdateSingleClusterDefault).Payload.Errors {
			msg += errors.SingleErrorToString(e)
		}
		c.Fatal(msg)
	}

	// check for update completion
	obsmcount, obswcount = checkClusterHelper(s, c, cldparams, int(mcount+uwcount))

	c.Assert(obsmcount, check.Equals, mcount)
	c.Assert(obswcount, check.Equals, uwcount)

	// delete the cluster
	delparams := clusters.NewDeleteSingleClusterParams().WithName(cname)
	_, err = s.cli.Clusters.DeleteSingleCluster(delparams)
	if err != nil {
		switch err.(type) {
		case *clusters.DeleteSingleClusterDefault:
			c.Fatal(err.(*clusters.DeleteSingleClusterDefault).Error())
		default:
			c.Fatal(err)
		}
	}

	// confirm delete
	var tries int
	const retries = 30
	obsclcount := 0
	for tries = 0; tries < retries; tries++ {
		fcresult, err := s.cli.Clusters.FindClusters(nil)
		if err != nil {
			c.Fatal(err.(*clusters.FindClustersDefault).Error())
		}
		if obsclcount = len(fcresult.Payload.Clusters); obsclcount == 0 {
			break
		}
		time.Sleep(1 * time.Second)
	}

	c.Assert(tries, LessThan, retries)
	expclcount := 0
	c.Assert(obsclcount, check.Equals, expclcount)
}
<commit_msg>Lengthen retries for client tests<commit_after>package clienttest

import (
	"fmt"
	"time"

	check "gopkg.in/check.v1"

	"github.com/radanalyticsio/oshinko-cli/rest/client/clusters"
	"github.com/radanalyticsio/oshinko-cli/rest/helpers/errors"
	"github.com/radanalyticsio/oshinko-cli/rest/models"
)

type lessThanChecker struct {
	*check.CheckerInfo
}

// The LessThan checker attempts to determine if the observed value is
// less than the expected. It will only attempt this on values that can be
// cast as int64.
var LessThan check.Checker = &lessThanChecker{
	&check.CheckerInfo{Name: "LessThan", Params: []string{"obtained", "compare"}},
}

func (checker *lessThanChecker) Check(params []interface{}, names []string) (result bool, error string) {
	defer func() {
		if v := recover(); v != nil {
			result = false
			error = fmt.Sprint(v)
		}
	}()
	return params[0].(int) < params[1].(int), ""
}

func checkClusterHelper(s *OshinkoRestTestSuite, c *check.C, params *clusters.FindSingleClusterParams, count int) (obsmcount int64, obswcount int64) {
	const retries = 60
	var tries int

	for tries = 0; tries < retries; tries++ {
		cldresult, err := s.cli.Clusters.FindSingleCluster(params)
		if err != nil {
			msg := fmt.Sprintf("%s \n %+v",
				err.(*clusters.FindSingleClusterDefault).Error(),
				err.(*clusters.FindSingleClusterDefault).Payload.Errors)
			c.Fatal(msg)
		}

		// if enough pods are available, we loop through them and exit
		// the retry loop, otherwise sleep for 1 second.
		if len(cldresult.Payload.Cluster.Pods) >= count {
			// loop through the pods to count workers and masters
			obsmcount = 0
			obswcount = 0
			for _, pod := range cldresult.Payload.Cluster.Pods {
				switch *pod.Type {
				case "master":
					if *pod.Status == "Running" {
						obsmcount += 1
					}
				case "worker":
					if *pod.Status == "Running" {
						obswcount += 1
					}
				}
			}
			if int(obsmcount+obswcount) == count {
				break
			}
		}

		time.Sleep(1 * time.Second)
	}
	c.Assert(tries, LessThan, retries)
	return
}

func (s *OshinkoRestTestSuite) TestCreateAndDeleteCluster(c *check.C) {
	cname := "e2ecluster"
	mcount := int64(1)
	wcount := int64(3)

	cconfig := models.NewClusterConfig{MasterCount: &mcount, WorkerCount: &wcount}
	cdetails := models.NewCluster{Name: &cname, Config: &cconfig}
	clparams := clusters.NewCreateClusterParams().WithCluster(&cdetails)

	// create a cluster
	_, err := s.cli.Clusters.CreateCluster(clparams)
	if err != nil {
		msg := err.(*clusters.CreateClusterDefault).Error() + "\n"
		for _, e := range err.(*clusters.CreateClusterDefault).Payload.Errors {
			msg += errors.SingleErrorToString(e)
		}
		c.Fatal(msg)
	}

	// read the cluster details
	// because it may take some time for the pods to become available, we
	// must loop and try multiple times to read them. if we fail to read
	// them after a set number of retries, we consider the test to have
	// failed.
	cldparams := clusters.NewFindSingleClusterParams().WithName(cname)
	obsmcount, obswcount := checkClusterHelper(s, c, cldparams, int(mcount+wcount))

	c.Assert(obsmcount, check.Equals, mcount)
	c.Assert(obswcount, check.Equals, wcount)

	// scale up the cluster
	// this will attempt to scale up the number of workers by 1. as with
	// the creation test, this test will loop for a number of retries to
	// give time for the new worker to be created.
	uwcount := int64(wcount + 1)
	ucconfig := models.NewClusterConfig{MasterCount: &mcount, WorkerCount: &uwcount}
	ucdetails := models.NewCluster{Name: &cname, Config: &ucconfig}
	uclparams := clusters.NewUpdateSingleClusterParams().WithCluster(&ucdetails).WithName(cname)

	// update the cluster
	_, err = s.cli.Clusters.UpdateSingleCluster(uclparams)
	if err != nil {
		msg := err.(*clusters.UpdateSingleClusterDefault).Error() + "\n"
		for _, e := range err.(*clusters.UpdateSingleClusterDefault).Payload.Errors {
			msg += errors.SingleErrorToString(e)
		}
		c.Fatal(msg)
	}

	// check for update completion
	obsmcount, obswcount = checkClusterHelper(s, c, cldparams, int(mcount+uwcount))

	c.Assert(obsmcount, check.Equals, mcount)
	c.Assert(obswcount, check.Equals, uwcount)

	// scale down the cluster
	uwcount = int64(wcount - 1)
	ucconfig = models.NewClusterConfig{MasterCount: &mcount, WorkerCount: &uwcount}
	ucdetails = models.NewCluster{Name: &cname, Config: &ucconfig}
	uclparams = clusters.NewUpdateSingleClusterParams().WithCluster(&ucdetails).WithName(cname)

	// update the cluster
	_, err = s.cli.Clusters.UpdateSingleCluster(uclparams)
	if err != nil {
		msg := err.(*clusters.UpdateSingleClusterDefault).Error() + "\n"
		for _, e := range err.(*clusters.UpdateSingleClusterDefault).Payload.Errors {
			msg += errors.SingleErrorToString(e)
		}
		c.Fatal(msg)
	}

	// check for update completion
	obsmcount, obswcount = checkClusterHelper(s, c, cldparams, int(mcount+uwcount))

	c.Assert(obsmcount, check.Equals, mcount)
	c.Assert(obswcount, check.Equals, uwcount)

	// delete the cluster
	delparams := clusters.NewDeleteSingleClusterParams().WithName(cname)
	_, err = s.cli.Clusters.DeleteSingleCluster(delparams)
	if err != nil {
		switch err.(type) {
		case *clusters.DeleteSingleClusterDefault:
			c.Fatal(err.(*clusters.DeleteSingleClusterDefault).Error())
		default:
			c.Fatal(err)
		}
	}

	// confirm delete
	var tries int
	const retries = 30
	obsclcount := 0
	for tries = 0; tries < retries; tries++ {
		fcresult, err := s.cli.Clusters.FindClusters(nil)
		if err != nil {
			c.Fatal(err.(*clusters.FindClustersDefault).Error())
		}
		if obsclcount = len(fcresult.Payload.Clusters); obsclcount == 0 {
			break
		}
		time.Sleep(1 * time.Second)
	}

	c.Assert(tries, LessThan, retries)
	expclcount := 0
	c.Assert(obsclcount, check.Equals, expclcount)
}
<|endoftext|>


<commit_before>package libcomfo

import (
	"bufio"
	"io"
	"sync"
	"time"
)

var (
	// Package-wide serial connection Mutex
	pm = &sync.Mutex{}
)

// WaitTimeout takes a channel and a timer. If the channel reads before
// the timer expires, true is returned. If the timer expires, false is returned.
func WaitTimeout(wait <-chan bool, timeout *time.Timer) (success bool, err error) {
	select {
	case result := <-wait:
		// Operation succeeded
		return result, nil
	case <-timeout.C:
		// Time out the transaction
		return false, errTimeout
	}
}

// ScanTimeout advances the given scanner until the timer expires.
// All output found on the way is returned.
func ScanTimeout(scanner *bufio.Scanner, timer *time.Timer) (out []byte, err error) {

	done := make(chan bool)

	// Advance the scanner past one delimiter
	go func() { done <- scanner.Scan() }()

	// Wait for delimiter to appear, or time out
	if _, err := WaitTimeout(done, timer); err != nil {
		return out, err
	}

	// Return all characters found on the way to the delimiter
	out = scanner.Bytes()

	return
}

// ReadPacket scans a connection for 3 escape sequences - ack, start and end
// A Packet structure is extracted from the connection.
func ReadPacket(conn io.Reader, timer *time.Timer, expect bool) (out Packet, err error) {

	// Read the first byte in the stream
	scanner := bufio.NewScanner(conn)

	// Split function with configurable
	splitFunc := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		for i := 0; i < len(data); i++ {
			// Look for escape characters
			if data[i] == esc {
				// Look for the given byte following the escape character within bounds
				if i+1 < len(data) {
					char := data[i+1]
					switch char {
					case ack, start, end:
						// Advance the pointer past the 2nd command character
						return i + 2, data[:i], nil
					case esc:
						// Ignore double 0x07
						return i + 2, nil, nil
					default:
					}
				}
			}
		}
		return
	}

	scanner.Split(splitFunc)

	// Read until first escape sequence (ACK)
	pr, err := ScanTimeout(scanner, timer)
	if err != nil {
		return out, err
	}

	// Found bytes before ACK
	if len(pr) != 0 {
		return out, errScanInput
	}

	// We're not expecting data (only ACK), return
	if !expect {
		return
	}
	// Read second escape sequence (start)
	pr, err = ScanTimeout(scanner, timer)
	if err != nil {
		return out, err
	}

	// Found bytes between ACK and packet start
	if len(pr) != 0 {
		return out, errScanInput
	}

	// Read second escape sequence (data + stop)
	pr, err = ScanTimeout(scanner, timer)
	if err != nil {
		return out, err
	}

	// Abort on invalid input
	if len(pr) > 0 && len(pr) < 4 {
		return out, errTooShort
	}

	// Decode the payload
	if len(pr) >= 4 {
		out, err = UnmarshalPacket(pr)
		if err != nil {
			return out, err
		}
	}

	return
}

// WritePacket serializes a Packet into wire representation, wrapped
// in start and end sequences, and writes to the given connection.
func WritePacket(in Packet, conn io.Writer) (out bool, err error) {

	// Get wire representation of the given Packet
	pb, err := MarshalPacket(in)
	if err != nil {
		return out, errMarshalPacket
	}

	// Wrap start and end escape sequences
	wr := append(pktStart, pb...)
	wr = append(wr, pktEnd...)

	// Write slice to connection
	_, err = conn.Write(wr)
	if err != nil {
		return false, errWrite
	}

	return true, nil
}

// WriteAck writes an ACK response to a connection.
func WriteAck(conn io.Writer) (out bool, err error) {
	num, err := conn.Write(pktAck)
	if err != nil {
		return false, err
	}

	if num != len(pktAck) {
		return false, errAck
	}

	return
}

// QueryPacket locks the package-wide mutex, writes the given packet to
// the connection, starts the timeout timer, reads the response back from
// the connection, validates the response sequence (command) number and sends
// an ACK to the remote.
func QueryPacket(in Packet, conn io.ReadWriter) (out Packet, err error) {

	// Take out lock - start critical section
	pm.Lock()
	defer pm.Unlock()

	// Write the Packet to the connection
	if _, err := WritePacket(in, conn); err != nil {
		return out, err
	}

	// Start a query-scoped timeout
	timer := time.NewTimer(time.Second)

	// Read packet from the remote
	out, err = ReadPacket(conn, timer, in.Expect)
	if err != nil {
		return out, err
	}

	// Check for the correct response command (request command + 1)
	if in.Expect && out.Command != in.Command+1 {
		return out, errInvalidResponse
	}

	// Send ACK
	_, err = WriteAck(conn)
	if err != nil {
		return out, err
	}

	// Return query result
	return out, nil
}
<commit_msg>libcomfo - conn: surface errors in MarshalPacket and conn.Write to caller<commit_after>package libcomfo

import (
	"bufio"
	"io"
	"sync"
	"time"
)

var (
	// Package-wide serial connection Mutex
	pm = &sync.Mutex{}
)

// WaitTimeout takes a channel and a timer. If the channel reads before
// the timer expires, true is returned. If the timer expires, false is returned.
func WaitTimeout(wait <-chan bool, timeout *time.Timer) (success bool, err error) {
	select {
	case result := <-wait:
		// Operation succeeded
		return result, nil
	case <-timeout.C:
		// Time out the transaction
		return false, errTimeout
	}
}

// ScanTimeout advances the given scanner until the timer expires.
// All output found on the way is returned.
func ScanTimeout(scanner *bufio.Scanner, timer *time.Timer) (out []byte, err error) {

	done := make(chan bool)

	// Advance the scanner past one delimiter
	go func() { done <- scanner.Scan() }()

	// Wait for delimiter to appear, or time out
	if _, err := WaitTimeout(done, timer); err != nil {
		return out, err
	}

	// Return all characters found on the way to the delimiter
	out = scanner.Bytes()

	return
}

// ReadPacket scans a connection for 3 escape sequences - ack, start and end
// A Packet structure is extracted from the connection.
func ReadPacket(conn io.Reader, timer *time.Timer, expect bool) (out Packet, err error) {

	// Read the first byte in the stream
	scanner := bufio.NewScanner(conn)

	// Split function with configurable
	splitFunc := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		for i := 0; i < len(data); i++ {
			// Look for escape characters
			if data[i] == esc {
				// Look for the given byte following the escape character within bounds
				if i+1 < len(data) {
					char := data[i+1]
					switch char {
					case ack, start, end:
						// Advance the pointer past the 2nd command character
						return i + 2, data[:i], nil
					case esc:
						// Ignore double 0x07
						return i + 2, nil, nil
					default:
					}
				}
			}
		}
		return
	}

	scanner.Split(splitFunc)

	// Read until first escape sequence (ACK)
	pr, err := ScanTimeout(scanner, timer)
	if err != nil {
		return out, err
	}

	// Found bytes before ACK
	if len(pr) != 0 {
		return out, errScanInput
	}

	// We're not expecting data (only ACK), return
	if !expect {
		return
	}
	// Read second escape sequence (start)
	pr, err = ScanTimeout(scanner, timer)
	if err != nil {
		return out, err
	}

	// Found bytes between ACK and packet start
	if len(pr) != 0 {
		return out, errScanInput
	}

	// Read second escape sequence (data + stop)
	pr, err = ScanTimeout(scanner, timer)
	if err != nil {
		return out, err
	}

	// Abort on invalid input
	if len(pr) > 0 && len(pr) < 4 {
		return out, errTooShort
	}

	// Decode the payload
	if len(pr) >= 4 {
		out, err = UnmarshalPacket(pr)
		if err != nil {
			return out, err
		}
	}

	return
}

// WritePacket serializes a Packet into wire representation, wrapped
// in start and end sequences, and writes to the given connection.
func WritePacket(in Packet, conn io.Writer) (out bool, err error) {

	// Get wire representation of the given Packet
	// TODO: Wrap original error and return errMarshalPacket to the caller
	pb, err := MarshalPacket(in)
	if err != nil {
		return out, err
	}

	// Wrap start and end escape sequences
	wr := append(pktStart, pb...)
	wr = append(wr, pktEnd...)

	// Write slice to connection
	// TODO: Wrap original error and return errWrite to the caller
	_, err = conn.Write(wr)
	if err != nil {
		return false, err
	}

	return true, nil
}

// WriteAck writes an ACK response to a connection.
func WriteAck(conn io.Writer) (out bool, err error) {
	num, err := conn.Write(pktAck)
	if err != nil {
		return false, err
	}

	if num != len(pktAck) {
		return false, errAck
	}

	return
}

// QueryPacket locks the package-wide mutex, writes the given packet to
// the connection, starts the timeout timer, reads the response back from
// the connection, validates the response sequence (command) number and sends
// an ACK to the remote.
func QueryPacket(in Packet, conn io.ReadWriter) (out Packet, err error) {

	// Take out lock - start critical section
	pm.Lock()
	defer pm.Unlock()

	// Write the Packet to the connection
	if _, err := WritePacket(in, conn); err != nil {
		return out, err
	}

	// Start a query-scoped timeout
	timer := time.NewTimer(time.Second)

	// Read packet from the remote
	out, err = ReadPacket(conn, timer, in.Expect)
	if err != nil {
		return out, err
	}

	// Check for the correct response command (request command + 1)
	if in.Expect && out.Command != in.Command+1 {
		return out, errInvalidResponse
	}

	// Send ACK
	_, err = WriteAck(conn)
	if err != nil {
		return out, err
	}

	// Return query result
	return out, nil
}
<|endoftext|>