<commit_before>package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"time"

	"context"

	"github.com/pkg/errors"
	"gopkg.in/yaml.v2"

	"github.com/lxc/lxd/lxd/backup"
	"github.com/lxc/lxd/lxd/cluster"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/lxd/state"
	storagePools "github.com/lxc/lxd/lxd/storage"
	"github.com/lxc/lxd/lxd/task"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/idmap"
	"github.com/lxc/lxd/shared/instancewriter"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/logging"
)

// Create a new backup.
func backupCreate(s *state.State, args db.InstanceBackupArgs, sourceInst instance.Instance) error {
	logger := logging.AddContext(logger.Log, log.Ctx{"project": sourceInst.Project(), "instance": sourceInst.Name(), "name": args.Name})
	logger.Debug("Instance backup started")
	defer logger.Debug("Instance backup finished")

	revert := revert.New()
	defer revert.Fail()

	// Get storage pool.
	pool, err := storagePools.GetPoolByInstance(s, sourceInst)
	if err != nil {
		return errors.Wrap(err, "Load instance storage pool")
	}

	// Ignore requests for optimized backups when pool driver doesn't support it.
	if args.OptimizedStorage && !pool.Driver().Info().OptimizedBackups {
		args.OptimizedStorage = false
	}

	// Create the database entry.
	err = s.Cluster.CreateInstanceBackup(args)
	if err != nil {
		if err == db.ErrAlreadyDefined {
			return fmt.Errorf("Backup %q already exists", args.Name)
		}

		return errors.Wrap(err, "Insert backup info into database")
	}

	revert.Add(func() { s.Cluster.DeleteInstanceBackup(args.Name) })

	// Get the backup struct.
	b, err := instance.BackupLoadByName(s, sourceInst.Project(), args.Name)
	if err != nil {
		return errors.Wrap(err, "Load backup object")
	}

	// Detect compression method.
	var compress string
	b.SetCompressionAlgorithm(args.CompressionAlgorithm)
	if b.CompressionAlgorithm() != "" {
		compress = b.CompressionAlgorithm()
	} else {
		compress, err = cluster.ConfigGetString(s.Cluster, "backups.compression_algorithm")
		if err != nil {
			return err
		}
	}

	// Create the target path if needed.
	backupsPath := shared.VarPath("backups", project.Instance(sourceInst.Project(), sourceInst.Name()))
	if !shared.PathExists(backupsPath) {
		err := os.MkdirAll(backupsPath, 0700)
		if err != nil {
			return err
		}

		revert.Add(func() { os.Remove(backupsPath) })
	}

	target := shared.VarPath("backups", project.Instance(sourceInst.Project(), b.Name()))

	// Setup the tarball writer.
	logger.Debug("Opening backup tarball for writing", log.Ctx{"path": target})
	tarFileWriter, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY, 0600)
	if err != nil {
		return errors.Wrapf(err, "Error opening backup tarball for writing %q", target)
	}
	defer tarFileWriter.Close()
	revert.Add(func() { os.Remove(target) })

	// Get IDMap to unshift container as the tarball is created.
	var idmap *idmap.IdmapSet
	if sourceInst.Type() == instancetype.Container {
		c := sourceInst.(instance.Container)
		idmap, err = c.DiskIdmap()
		if err != nil {
			return errors.Wrap(err, "Error getting container IDMAP")
		}
	}

	// Create the tarball.
	tarPipeReader, tarPipeWriter := io.Pipe()
	defer tarPipeWriter.Close() // Ensure that go routine below always ends.
	tarWriter := instancewriter.NewInstanceTarWriter(tarPipeWriter, idmap)

	// Setup tar writer go routine, with optional compression.
	tarWriterRes := make(chan error, 0)
	go func(resCh chan<- error) {
		logger.Debug("Started backup tarball writer")
		defer logger.Debug("Finished backup tarball writer")
		if compress != "none" {
			err = compressFile(compress, tarPipeReader, tarFileWriter)
		} else {
			_, err = io.Copy(tarFileWriter, tarPipeReader)
		}
		resCh <- err
	}(tarWriterRes)

	// Write index file.
	logger.Debug("Adding backup index file")
	err = backupWriteIndex(sourceInst, pool, b.OptimizedStorage(), !b.InstanceOnly(), tarWriter)
	if err != nil {
		return errors.Wrapf(err, "Error writing backup index file")
	}

	err = pool.BackupInstance(sourceInst, tarWriter, b.OptimizedStorage(), !b.InstanceOnly(), nil)
	if err != nil {
		return errors.Wrap(err, "Backup create")
	}

	// Close off the tarball file.
	err = tarWriter.Close()
	if err != nil {
		return errors.Wrap(err, "Error closing tarball writer")
	}

	// Close off the tarball pipe writer (this will end the go routine above).
	err = tarPipeWriter.Close()
	if err != nil {
		return errors.Wrap(err, "Error closing tarball pipe writer")
	}

	err = <-tarWriterRes
	if err != nil {
		return errors.Wrap(err, "Error writing tarball")
	}

	revert.Success()
	return nil
}

// backupWriteIndex generates an index.yaml file and then writes it to the root of the backup tarball.
func backupWriteIndex(sourceInst instance.Instance, pool storagePools.Pool, optimized bool, snapshots bool, tarWriter *instancewriter.InstanceTarWriter) error {
	indexInfo := backup.Info{
		Name:             sourceInst.Name(),
		Pool:             pool.Name(),
		Snapshots:        []string{},
		Backend:          pool.Driver().Info().Name,
		Type:             api.InstanceType(sourceInst.Type().String()),
		OptimizedStorage: &optimized,
	}

	if snapshots {
		snaps, err := sourceInst.Snapshots()
		if err != nil {
			return err
		}

		for _, snap := range snaps {
			_, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
			indexInfo.Snapshots = append(indexInfo.Snapshots, snapName)
		}
	}

	// Convert to JSON.
	indexData, err := yaml.Marshal(&indexInfo)
	if err != nil {
		return err
	}
	r := bytes.NewReader(indexData)

	indexFileInfo := instancewriter.FileInfo{
		FileName:    "backup/index.yaml",
		FileSize:    int64(len(indexData)),
		FileMode:    0644,
		FileModTime: time.Now(),
	}

	// Write to tarball.
	err = tarWriter.WriteFileFromReader(r, &indexFileInfo)
	if err != nil {
		return err
	}

	return nil
}

func pruneExpiredContainerBackupsTask(d *Daemon) (task.Func, task.Schedule) {
	f := func(ctx context.Context) {
		opRun := func(op *operations.Operation) error {
			return pruneExpiredContainerBackups(ctx, d)
		}

		op, err := operations.OperationCreate(d.State(), "", operations.OperationClassTask, db.OperationBackupsExpire, nil, nil, opRun, nil, nil)
		if err != nil {
			logger.Error("Failed to start expired instance backups operation", log.Ctx{"err": err})
			return
		}

		logger.Info("Pruning expired instance backups")
		_, err = op.Run()
		if err != nil {
			logger.Error("Failed to expire instance backups", log.Ctx{"err": err})
		}
		logger.Info("Done pruning expired instance backups")
	}

	f(context.Background())

	first := true
	schedule := func() (time.Duration, error) {
		interval := time.Hour

		if first {
			first = false
			return interval, task.ErrSkip
		}

		return interval, nil
	}

	return f, schedule
}

func pruneExpiredContainerBackups(ctx context.Context, d *Daemon) error {
	// Get the list of expired backups.
	backups, err := d.cluster.GetExpiredInstanceBackups()
	if err != nil {
		return errors.Wrap(err, "Unable to retrieve the list of expired instance backups")
	}

	for _, b := range backups {
		inst, err := instance.LoadByID(d.State(), b.InstanceID)
		if err != nil {
			return errors.Wrapf(err, "Error deleting instance backup %s", b.Name)
		}

		err = backup.DoBackupDelete(d.State(), inst.Project(), b.Name, inst.Name())
		if err != nil {
			return errors.Wrapf(err, "Error deleting instance backup %s", b.Name)
		}
	}

	return nil
}
<commit_msg>lxd/backup: Updates backupWriteIndex to populate the OptimizedHeader field<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"time"

	"context"

	"github.com/pkg/errors"
	"gopkg.in/yaml.v2"

	"github.com/lxc/lxd/lxd/backup"
	"github.com/lxc/lxd/lxd/cluster"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/lxd/state"
	storagePools "github.com/lxc/lxd/lxd/storage"
	"github.com/lxc/lxd/lxd/task"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/idmap"
	"github.com/lxc/lxd/shared/instancewriter"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/logging"
)

// Create a new backup.
func backupCreate(s *state.State, args db.InstanceBackupArgs, sourceInst instance.Instance) error {
	logger := logging.AddContext(logger.Log, log.Ctx{"project": sourceInst.Project(), "instance": sourceInst.Name(), "name": args.Name})
	logger.Debug("Instance backup started")
	defer logger.Debug("Instance backup finished")

	revert := revert.New()
	defer revert.Fail()

	// Get storage pool.
	pool, err := storagePools.GetPoolByInstance(s, sourceInst)
	if err != nil {
		return errors.Wrap(err, "Load instance storage pool")
	}

	// Ignore requests for optimized backups when pool driver doesn't support it.
	if args.OptimizedStorage && !pool.Driver().Info().OptimizedBackups {
		args.OptimizedStorage = false
	}

	// Create the database entry.
	err = s.Cluster.CreateInstanceBackup(args)
	if err != nil {
		if err == db.ErrAlreadyDefined {
			return fmt.Errorf("Backup %q already exists", args.Name)
		}

		return errors.Wrap(err, "Insert backup info into database")
	}

	revert.Add(func() { s.Cluster.DeleteInstanceBackup(args.Name) })

	// Get the backup struct.
	b, err := instance.BackupLoadByName(s, sourceInst.Project(), args.Name)
	if err != nil {
		return errors.Wrap(err, "Load backup object")
	}

	// Detect compression method.
	var compress string
	b.SetCompressionAlgorithm(args.CompressionAlgorithm)
	if b.CompressionAlgorithm() != "" {
		compress = b.CompressionAlgorithm()
	} else {
		compress, err = cluster.ConfigGetString(s.Cluster, "backups.compression_algorithm")
		if err != nil {
			return err
		}
	}

	// Create the target path if needed.
	backupsPath := shared.VarPath("backups", project.Instance(sourceInst.Project(), sourceInst.Name()))
	if !shared.PathExists(backupsPath) {
		err := os.MkdirAll(backupsPath, 0700)
		if err != nil {
			return err
		}

		revert.Add(func() { os.Remove(backupsPath) })
	}

	target := shared.VarPath("backups", project.Instance(sourceInst.Project(), b.Name()))

	// Setup the tarball writer.
	logger.Debug("Opening backup tarball for writing", log.Ctx{"path": target})
	tarFileWriter, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY, 0600)
	if err != nil {
		return errors.Wrapf(err, "Error opening backup tarball for writing %q", target)
	}
	defer tarFileWriter.Close()
	revert.Add(func() { os.Remove(target) })

	// Get IDMap to unshift container as the tarball is created.
	var idmap *idmap.IdmapSet
	if sourceInst.Type() == instancetype.Container {
		c := sourceInst.(instance.Container)
		idmap, err = c.DiskIdmap()
		if err != nil {
			return errors.Wrap(err, "Error getting container IDMAP")
		}
	}

	// Create the tarball.
	tarPipeReader, tarPipeWriter := io.Pipe()
	defer tarPipeWriter.Close() // Ensure that go routine below always ends.
	tarWriter := instancewriter.NewInstanceTarWriter(tarPipeWriter, idmap)

	// Setup tar writer go routine, with optional compression.
	tarWriterRes := make(chan error, 0)
	go func(resCh chan<- error) {
		logger.Debug("Started backup tarball writer")
		defer logger.Debug("Finished backup tarball writer")
		if compress != "none" {
			err = compressFile(compress, tarPipeReader, tarFileWriter)
		} else {
			_, err = io.Copy(tarFileWriter, tarPipeReader)
		}
		resCh <- err
	}(tarWriterRes)

	// Write index file.
	logger.Debug("Adding backup index file")
	err = backupWriteIndex(sourceInst, pool, b.OptimizedStorage(), !b.InstanceOnly(), tarWriter)
	if err != nil {
		return errors.Wrapf(err, "Error writing backup index file")
	}

	err = pool.BackupInstance(sourceInst, tarWriter, b.OptimizedStorage(), !b.InstanceOnly(), nil)
	if err != nil {
		return errors.Wrap(err, "Backup create")
	}

	// Close off the tarball file.
	err = tarWriter.Close()
	if err != nil {
		return errors.Wrap(err, "Error closing tarball writer")
	}

	// Close off the tarball pipe writer (this will end the go routine above).
	err = tarPipeWriter.Close()
	if err != nil {
		return errors.Wrap(err, "Error closing tarball pipe writer")
	}

	err = <-tarWriterRes
	if err != nil {
		return errors.Wrap(err, "Error writing tarball")
	}

	revert.Success()
	return nil
}

// backupWriteIndex generates an index.yaml file and then writes it to the root of the backup tarball.
func backupWriteIndex(sourceInst instance.Instance, pool storagePools.Pool, optimized bool, snapshots bool, tarWriter *instancewriter.InstanceTarWriter) error {
	// Indicate whether the driver will include a driver-specific optimized header.
	poolDriverOptimizedHeader := false
	if optimized {
		poolDriverOptimizedHeader = pool.Driver().Info().OptimizedBackupHeader
	}

	indexInfo := backup.Info{
		Name:             sourceInst.Name(),
		Pool:             pool.Name(),
		Snapshots:        []string{},
		Backend:          pool.Driver().Info().Name,
		Type:             api.InstanceType(sourceInst.Type().String()),
		OptimizedStorage: &optimized,
		OptimizedHeader:  &poolDriverOptimizedHeader,
	}

	if snapshots {
		snaps, err := sourceInst.Snapshots()
		if err != nil {
			return err
		}

		for _, snap := range snaps {
			_, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
			indexInfo.Snapshots = append(indexInfo.Snapshots, snapName)
		}
	}

	// Convert to YAML.
	indexData, err := yaml.Marshal(&indexInfo)
	if err != nil {
		return err
	}
	r := bytes.NewReader(indexData)

	indexFileInfo := instancewriter.FileInfo{
		FileName:    "backup/index.yaml",
		FileSize:    int64(len(indexData)),
		FileMode:    0644,
		FileModTime: time.Now(),
	}

	// Write to tarball.
	err = tarWriter.WriteFileFromReader(r, &indexFileInfo)
	if err != nil {
		return err
	}

	return nil
}

func pruneExpiredContainerBackupsTask(d *Daemon) (task.Func, task.Schedule) {
	f := func(ctx context.Context) {
		opRun := func(op *operations.Operation) error {
			return pruneExpiredContainerBackups(ctx, d)
		}

		op, err := operations.OperationCreate(d.State(), "", operations.OperationClassTask, db.OperationBackupsExpire, nil, nil, opRun, nil, nil)
		if err != nil {
			logger.Error("Failed to start expired instance backups operation", log.Ctx{"err": err})
			return
		}

		logger.Info("Pruning expired instance backups")
		_, err = op.Run()
		if err != nil {
			logger.Error("Failed to expire instance backups", log.Ctx{"err": err})
		}
		logger.Info("Done pruning expired instance backups")
	}

	f(context.Background())

	first := true
	schedule := func() (time.Duration, error) {
		interval := time.Hour

		if first {
			first = false
			return interval, task.ErrSkip
		}

		return interval, nil
	}

	return f, schedule
}

func pruneExpiredContainerBackups(ctx context.Context, d *Daemon) error {
	// Get the list of expired backups.
	backups, err := d.cluster.GetExpiredInstanceBackups()
	if err != nil {
		return errors.Wrap(err, "Unable to retrieve the list of expired instance backups")
	}

	for _, b := range backups {
		inst, err := instance.LoadByID(d.State(), b.InstanceID)
		if err != nil {
			return errors.Wrapf(err, "Error deleting instance backup %s", b.Name)
		}

		err = backup.DoBackupDelete(d.State(), inst.Project(), b.Name, inst.Name())
		if err != nil {
			return errors.Wrapf(err, "Error deleting instance backup %s", b.Name)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package tumblr

import (
	"net/url"
	"path"
)

type Blog struct {
	Basename string
}

type PostRequestParams struct{}

type Post struct{}

const (
	urlBase = "http://api.tumblr.com/v2/"
)
<commit_msg>Constants for post types specified by API, function for determining correct post URL, skeleton of Posts liked by a blog as well as posts posted in a blog<commit_after>package tumblr

import (
	"net/url"
	"path"
)

type Blog struct {
	Basename string
}

type PostRequestParams struct{}

type Post struct{}

const (
	urlBase = "http://api.tumblr.com/v2/"
)

// Post Types
const (
	Text   = "text"
	Quote  = "quote"
	Link   = "link"
	Answer = "answer"
	Video  = "video"
	Audio  = "audio"
	Photo  = "photo"
	Chat   = "chat"
)

// Where the request is directed to
func entityURL(entityType string) (*url.URL, error) {
	url, err := url.Parse(urlBase)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, entityType)
	return url, nil
}

// Posts posted by a blog
func (blog Blog) Posts(params PostRequestParams) []Post {
	// TODO
	return nil
}

// Posts liked by a blog
func (blog Blog) Likes() []Post {
	// TODO
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/andygrunwald/go-trending"
	"log"
	"time"
)

// Tweet is a structure to store the tweet and the project name based on the tweet.
type Tweet struct {
	Tweet       string
	ProjectName string
}

// generateNewTweet is responsible to search a new project / repository
// and build a new tweet based on this.
// The generated tweet will be sent to tweetChan.
func generateNewTweet(tweetChan chan *Tweet, config *Configuration) {
	// TODO Make generation of trending project more intelligent.
	// Currently we do:
	// 		* Get all timeframes and get a random timeframe.
	//		  Get projects based on this timeframe and check if we can tweet them.
	//		  If this is not a success remove this timeframe from slice and continue.
	//
	// Steps to improve the area:
	//		* If no project was chosen, lets get trending languages
	//		  and chose one randomly and request the timeframes again
	//		  and repeat the "slice from removes trick there"
	var projectToTweet trending.Project
	trendingClient := NewTrendingClient()
	redisClient, err := NewRedisClient(&config.Redis)
	if err != nil {
		log.Fatal(err)
	}

	// Get timeframes and randomize them
	timeFrames := trendingClient.GetTimeFrames()
	ShuffleStringSlice(timeFrames)

	for _, timeFrame := range timeFrames {
		log.Printf("Getting projects for timeframe %s", timeFrame)
		getProject := trendingClient.GetRandomProjectGenerator(timeFrame)
		projectToTweet = findProjectWithRandomProjectGenerator(getProject, redisClient)

		// Check if we found a project.
		// If yes we can leave the loop and keep on rockin
		if len(projectToTweet.Name) > 0 {
			break
		}
	}

	tweet := &Tweet{
		Tweet:       buildTweet(projectToTweet),
		ProjectName: projectToTweet.Name,
	}
	tweetChan <- tweet
}

// findProjectWithRandomProjectGenerator retrieves a new project and
// checks if this was already tweeted.
func findProjectWithRandomProjectGenerator(getProject func() (trending.Project, error), redisClient *Redis) trending.Project {
	var projectToTweet trending.Project

	for project, err := getProject(); err == nil; {
		// Check if the project was already tweeted
		alreadyTweeted, err := redisClient.IsRepositoryAlreadyTweeted(project.Name)
		if err != nil {
			log.Println(err)
			continue
		}

		// If the project was already tweeted
		// we will skip this project and go to the next one
		if alreadyTweeted > 0 {
			continue
		}

		// This project wasn`t tweeted yet, so we will take over this job
		projectToTweet = project
		break
	}

	return projectToTweet
}

// buildTweet is responsible to build a 140 length string based on the project we found.
func buildTweet(p trending.Project) string {
	// TODO: We need to improve this.
	// 		* Get the real number of chars for a URL for t.co.
	//		* Get the length of the description and check if we need to trim this.
	// 		* Try to put more information into it (like Language)
	tweet := ""

	// Base length of a tweet
	tweetLen := 140

	// 20 letters for the url
	// see https://dev.twitter.com/overview/t.co
	// TODO we have to replace this by an API call
	tweetLen -= 20

	// Check if the length of the project name is > 120 chars
	// We substract 3 chars, because we will add a suffix " - "
	if nameLen := len(p.Name); nameLen < (tweetLen - 3) {
		tweetLen -= len(p.Name)
		tweet += p.Name

		// Add name suffix " - "
		tweetLen -= 3
		tweet += " - "
	}

	// We only post descriptions if we got more than 20 charactes available
	// + 1 character for a whitespace
	if tweetLen > 21 {
		if len(p.Description) < tweetLen {
			tweet += p.Description
		} else {
			tweet += p.Description[0:(tweetLen - 1)]
		}
		tweet += " "
	}

	// Lets add the URL, but we don`t need to substract the chars
	// because we have done this before
	if p.URL != nil {
		tweet += p.URL.String()
	}

	return tweet
}

// markTweetAsAlreadyTweeted adds a projectName to the global blacklist of already tweeted projects.
// For this we use a Sorted Set where the score is the timestamp of the tweet.
func markTweetAsAlreadyTweeted(projectName string, config *Configuration) (int, error) {
	redisClient, err := NewRedisClient(&config.Redis)
	if err != nil {
		log.Fatal(err)
	}

	// Generate score in format YYYYMMDDHHiiss
	now := time.Now()
	score := now.Format("20060102150405")

	res, err := redisClient.AddRepositoryToTweetedList(projectName, score)
	if err != nil || res != 1 {
		log.Printf("Error during adding project %s to tweeted list: %s (%d)", projectName, err, res)
	}

	return res, err
}
<commit_msg>Add a whitespace in front of the url if the description is longer than 100 chars<commit_after>package main

import (
	"github.com/andygrunwald/go-trending"
	"log"
	"time"
)

// Tweet is a structure to store the tweet and the project name based on the tweet.
type Tweet struct {
	Tweet       string
	ProjectName string
}

// generateNewTweet is responsible to search a new project / repository
// and build a new tweet based on this.
// The generated tweet will be sent to tweetChan.
func generateNewTweet(tweetChan chan *Tweet, config *Configuration) {
	// TODO Make generation of trending project more intelligent.
	// Currently we do:
	// 		* Get all timeframes and get a random timeframe.
	//		  Get projects based on this timeframe and check if we can tweet them.
	//		  If this is not a success remove this timeframe from slice and continue.
	//
	// Steps to improve the area:
	//		* If no project was chosen, lets get trending languages
	//		  and chose one randomly and request the timeframes again
	//		  and repeat the "slice from removes trick there"
	var projectToTweet trending.Project
	trendingClient := NewTrendingClient()
	redisClient, err := NewRedisClient(&config.Redis)
	if err != nil {
		log.Fatal(err)
	}

	// Get timeframes and randomize them
	timeFrames := trendingClient.GetTimeFrames()
	ShuffleStringSlice(timeFrames)

	for _, timeFrame := range timeFrames {
		log.Printf("Getting projects for timeframe %s", timeFrame)
		getProject := trendingClient.GetRandomProjectGenerator(timeFrame)
		projectToTweet = findProjectWithRandomProjectGenerator(getProject, redisClient)

		// Check if we found a project.
		// If yes we can leave the loop and keep on rockin
		if len(projectToTweet.Name) > 0 {
			break
		}
	}

	tweet := &Tweet{
		Tweet:       buildTweet(projectToTweet),
		ProjectName: projectToTweet.Name,
	}
	tweetChan <- tweet
}

// findProjectWithRandomProjectGenerator retrieves a new project and
// checks if this was already tweeted.
func findProjectWithRandomProjectGenerator(getProject func() (trending.Project, error), redisClient *Redis) trending.Project {
	var projectToTweet trending.Project

	for project, err := getProject(); err == nil; {
		// Check if the project was already tweeted
		alreadyTweeted, err := redisClient.IsRepositoryAlreadyTweeted(project.Name)
		if err != nil {
			log.Println(err)
			continue
		}

		// If the project was already tweeted
		// we will skip this project and go to the next one
		if alreadyTweeted > 0 {
			continue
		}

		// This project wasn`t tweeted yet, so we will take over this job
		projectToTweet = project
		break
	}

	return projectToTweet
}

// buildTweet is responsible to build a 140 length string based on the project we found.
func buildTweet(p trending.Project) string {
	// TODO: We need to improve this.
	// 		* Get the real number of chars for a URL for t.co.
	//		* Get the length of the description and check if we need to trim this.
	// 		* Try to put more information into it (like Language)
	tweet := ""

	// Base length of a tweet
	tweetLen := 140

	// 20 letters for the url (+ 1 character for a whitespace)
	// see https://dev.twitter.com/overview/t.co
	// TODO we have to replace this by an API call
	tweetLen -= 21

	// Check if the length of the project name is > 120 chars
	// We substract 3 chars, because we will add a suffix " - "
	if nameLen := len(p.Name); nameLen < (tweetLen - 3) {
		tweetLen -= len(p.Name)
		tweet += p.Name

		// Add name suffix " - "
		tweetLen -= 3
		tweet += " - "
	}

	// We only post descriptions if we got more than 20 charactes available
	if tweetLen > 20 {
		if len(p.Description) < tweetLen {
			tweet += p.Description
		} else {
			tweet += p.Description[0:(tweetLen - 1)]
		}
	}

	// Lets add the URL, but we don`t need to substract the chars
	// because we have done this before
	if p.URL != nil {
		tweet += " "
		tweet += p.URL.String()
	}

	return tweet
}

// markTweetAsAlreadyTweeted adds a projectName to the global blacklist of already tweeted projects.
// For this we use a Sorted Set where the score is the timestamp of the tweet.
func markTweetAsAlreadyTweeted(projectName string, config *Configuration) (int, error) {
	redisClient, err := NewRedisClient(&config.Redis)
	if err != nil {
		log.Fatal(err)
	}

	// Generate score in format YYYYMMDDHHiiss
	now := time.Now()
	score := now.Format("20060102150405")

	res, err := redisClient.AddRepositoryToTweetedList(projectName, score)
	if err != nil || res != 1 {
		log.Printf("Error during adding project %s to tweeted list: %s (%d)", projectName, err, res)
	}

	return res, err
}
<|endoftext|>


<commit_before>package ui

import (
	"fmt"
	"log"
	"math/rand"
	"sort"
	"strconv"
	"strings"

	"github.com/fabiofalci/sconsify/events"
	"github.com/jroimartin/gocui"
	sp "github.com/op/go-libspotify/spotify"
)

var g *gocui.Gui
var playlistsView *gocui.View
var tracksView *gocui.View
var statusView *gocui.View
var queueView *gocui.View

var queue *Queue
var state *UiState
var playlists map[string]*sp.Playlist

var playEvents *events.Events

func Start(allEvents *events.Events) {
	playEvents = allEvents

	playlists = <-playEvents.Playlists
	if playlists == nil {
		return
	}

	queue = InitQueue()
	state = InitState()

	go func() {
		for {
			select {
			case message := <-playEvents.Status:
				updateStatus(message)
			case <-playEvents.NextPlay:
				playNext()
			}
		}
	}()

	g = gocui.NewGui()
	if err := g.Init(); err != nil {
		log.Panicln(err)
	}
	defer g.Close()

	g.SetLayout(layout)
	if err := keybindings(g); err != nil {
		log.Panicln(err)
	}
	g.SelBgColor = gocui.ColorGreen
	g.SelFgColor = gocui.ColorBlack
	g.ShowCursor = true

	err := g.MainLoop()
	if err != nil && err != gocui.ErrorQuit {
		log.Panicln(err)
	}
}

func updateStatus(message string) {
	statusView.Clear()
	statusView.SetCursor(0, 0)
	statusView.SetOrigin(0, 0)

	state.currentMessage = message
	fmt.Fprintf(statusView, state.getModeAsString()+"%v", state.currentMessage)

	// otherwise the update will appear only in the next keyboard move
	g.Flush()
}

func nextView(g *gocui.Gui, v *gocui.View) error {
	currentView := g.CurrentView()
	if currentView == nil || currentView.Name() == "side" {
		return g.SetCurrentView("main")
	}
	return g.SetCurrentView("side")
}

func cursorDown(g *gocui.Gui, v *gocui.View) error {
	if v != nil {
		cx, cy := v.Cursor()
		if err := v.SetCursor(cx, cy+1); err != nil {
			ox, oy := v.Origin()
			if err := v.SetOrigin(ox, oy+1); err != nil {
				return err
			}
		}
		if v == playlistsView {
			updateTracksView(g)
		}
	}
	return nil
}

func cursorUp(g *gocui.Gui, v *gocui.View) error {
	if v != nil {
		ox, oy := v.Origin()
		cx, cy := v.Cursor()
		if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
			if err := v.SetOrigin(ox, oy-1); err != nil {
				return err
			}
		}
		if v == playlistsView {
			updateTracksView(g)
		}
	}
	return nil
}

func getSelectedPlaylist(g *gocui.Gui) (string, error) {
	return getSelected(g, playlistsView)
}

func getSelectedTrack(g *gocui.Gui) (string, error) {
	return getSelected(g, tracksView)
}

func getSelected(g *gocui.Gui, v *gocui.View) (string, error) {
	var l string
	var err error

	_, cy := v.Cursor()
	if l, err = v.Line(cy); err != nil {
		l = ""
	}

	return l, nil
}

func quit(g *gocui.Gui, v *gocui.View) error {
	playEvents.Shutdown <- true
	<-playEvents.Shutdown
	return gocui.ErrorQuit
}

func playNext() error {
	if !queue.isEmpty() {
		playEvents.ToPlay <- queue.Pop()
		updateQueueView()
	} else if state.currentPlaylist != "" {
		playlist := playlists[state.currentPlaylist]
		if !state.randomMode {
			state.currentIndexTrack = getNextTrack(playlist)
		} else {
			state.currentIndexTrack = getRandomNextTrack(playlist)
		}
		playlistTrack := playlist.Track(state.currentIndexTrack)
		track := playlistTrack.Track()
		track.Wait()

		playEvents.ToPlay <- track
	}
	return nil
}

func getNextTrack(playlist *sp.Playlist) int {
	if state.currentIndexTrack >= playlist.Tracks()-1 {
		return 0
	}
	return state.currentIndexTrack + 1
}

func getRandomNextTrack(playlist *sp.Playlist) int {
	return rand.Intn(playlist.Tracks())
}

func playCurrentSelectedTrack(g *gocui.Gui, v *gocui.View) error {
	track := getCurrentSelectedTrack()
	if track != nil {
		playEvents.ToPlay <- track
	}
	return nil
}

func pauseCurrentSelectedTrack(g *gocui.Gui, v *gocui.View) error {
	playEvents.Pause <- true
	return nil
}

func setRandomMode(g *gocui.Gui, v *gocui.View) error {
	state.invertMode()
	updateStatus(state.currentMessage)
	return nil
}

func nextCommand(g *gocui.Gui, v *gocui.View) error {
	playNext()
	return nil
}

func queueCommand(g *gocui.Gui, v *gocui.View) error {
	track := getCurrentSelectedTrack()
	if track != nil {
		fmt.Fprintf(queueView, "%v - %v", track.Artist(0).Name(), track.Name())
		queue.Add(track)
	}
	return nil
}

func getCurrentSelectedTrack() *sp.Track {
	var errPlaylist error
	state.currentPlaylist, errPlaylist = getSelectedPlaylist(g)
	currentTrack, errTrack := getSelectedTrack(g)
	if errPlaylist == nil && errTrack == nil && playlists != nil {
		playlist := playlists[state.currentPlaylist]

		if playlist != nil {
			playlist.Wait()
			currentTrack = currentTrack[0:strings.Index(currentTrack, ".")]
			converted, _ := strconv.Atoi(currentTrack)
			state.currentIndexTrack = converted - 1
			playlistTrack := playlist.Track(state.currentIndexTrack)
			track := playlistTrack.Track()
			track.Wait()
			return track
		}
	}
	return nil
}

func keybindings(g *gocui.Gui) error {
	if err := g.SetKeybinding("main", gocui.KeySpace, 0, playCurrentSelectedTrack); err != nil {
		return err
	}
	if err := g.SetKeybinding("", 'p', 0, pauseCurrentSelectedTrack); err != nil {
		return err
	}
	if err := g.SetKeybinding("", 'r', 0, setRandomMode); err != nil {
		return err
	}
	if err := g.SetKeybinding("", '>', 0, nextCommand); err != nil {
		return err
	}
	if err := g.SetKeybinding("", 'u', 0, queueCommand); err != nil {
		return err
	}

	if err := g.SetKeybinding("", gocui.KeyArrowDown, 0, cursorDown); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyArrowUp, 0, cursorUp); err != nil {
		return err
	}
	if err := g.SetKeybinding("main", gocui.KeyArrowLeft, 0, nextView); err != nil {
		return err
	}
	if err := g.SetKeybinding("side", gocui.KeyArrowRight, 0, nextView); err != nil {
		return err
	}

	// vi navigation
	if err := g.SetKeybinding("", 'j', 0, cursorDown); err != nil {
		return err
	}
	if err := g.SetKeybinding("", 'k', 0, cursorUp); err != nil {
		return err
	}
	if err := g.SetKeybinding("main", 'h', 0, nextView); err != nil {
		return err
	}
	if err := g.SetKeybinding("side", 'l', 0, nextView); err != nil {
		return err
	}

	if err := g.SetKeybinding("", gocui.KeyCtrlC, 0, quit); err != nil {
		return err
	}
	if err := g.SetKeybinding("", 'q', 0, quit); err != nil {
		return err
	}

	return nil
}

func updateTracksView(g *gocui.Gui) {
	tracksView.Clear()
	tracksView.SetCursor(0, 0)
	tracksView.SetOrigin(0, 0)
	currentPlaylist, err := getSelectedPlaylist(g)
	if err == nil && playlists != nil {
		playlist := playlists[currentPlaylist]

		if playlist != nil {
			playlist.Wait()
			for i := 0; i < playlist.Tracks(); i++ {
				playlistTrack := playlist.Track(i)
				track := playlistTrack.Track()
				track.Wait()
				fmt.Fprintf(tracksView, "%v. %v - %v", (i + 1), track.Artist(0).Name(), track.Name())
			}
		}
	}
}

func updatePlaylistsView(g *gocui.Gui) {
	playlistsView.Clear()
	if playlists != nil {
		keys := make([]string, len(playlists))
		i := 0
		for k, _ := range playlists {
			keys[i] = k
			i++
		}
		sort.Strings(keys)
		for _, key := range keys {
			fmt.Fprintln(playlistsView, key)
		}
	}
}

func updateQueueView() {
	queueView.Clear()
	if !queue.isEmpty() {
		for _, track := range queue.Contents() {
			fmt.Fprintf(queueView, "%v - %v", track.Artist(0).Name(), track.Name())
		}
	}
}

func layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	if v, err := g.SetView("side", -1, -1, 25, maxY-2); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		playlistsView = v
		playlistsView.Highlight = true

		updatePlaylistsView(g)
	}
	if v, err := g.SetView("main", 25, -1, maxX-50, maxY-2); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		tracksView = v
		tracksView.Highlight = true

		updateTracksView(g)

		if err := g.SetCurrentView("main"); err != nil {
			return err
		}
	}
	if v, err := g.SetView("queue", maxX-50, -1, maxX, maxY-2); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		queueView = v
	}
	if v, err := g.SetView("status", -1, maxY-2, maxX, maxY); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		statusView = v
	}
	return nil
}
<commit_msg>Highlight only selected view<commit_after>package ui

import (
	"fmt"
	"log"
	"math/rand"
	"sort"
	"strconv"
	"strings"

	"github.com/fabiofalci/sconsify/events"
	"github.com/jroimartin/gocui"
	sp "github.com/op/go-libspotify/spotify"
)

var g *gocui.Gui
var playlistsView *gocui.View
var tracksView *gocui.View
var statusView *gocui.View
var queueView *gocui.View

var queue *Queue
var state *UiState
var playlists map[string]*sp.Playlist

var playEvents *events.Events

func Start(allEvents *events.Events) {
	playEvents = allEvents

	playlists = <-playEvents.Playlists
	if playlists == nil {
		return
	}

	queue = InitQueue()
	state = InitState()

	go func() {
		for {
			select {
			case message := <-playEvents.Status:
				updateStatus(message)
			case <-playEvents.NextPlay:
				playNext()
			}
		}
	}()

	g = gocui.NewGui()
	if err := g.Init(); err != nil {
		log.Panicln(err)
	}
	defer g.Close()

	g.SetLayout(layout)
	if err := keybindings(g); err != nil {
		log.Panicln(err)
	}
	g.SelBgColor = gocui.ColorGreen
	g.SelFgColor = gocui.ColorBlack
	g.ShowCursor = true

	err := g.MainLoop()
	if err != nil && err != gocui.ErrorQuit {
		log.Panicln(err)
	}
}

func updateStatus(message string) {
	statusView.Clear()
	statusView.SetCursor(0, 0)
	statusView.SetOrigin(0, 0)

	state.currentMessage = message
	fmt.Fprintf(statusView, state.getModeAsString()+"%v", state.currentMessage)

	// otherwise the update will appear only in the next keyboard move
	g.Flush()
}

func nextView(g *gocui.Gui, v *gocui.View) error {
	currentView := g.CurrentView()
	if currentView == nil || currentView.Name() == "side" {
		tracksView.Highlight = true
		playlistsView.Highlight = false
		return g.SetCurrentView("main")
	}
	tracksView.Highlight = false
	playlistsView.Highlight = true
	return g.SetCurrentView("side")
}

func cursorDown(g *gocui.Gui, v *gocui.View) error {
	if v != nil {
		cx, cy := v.Cursor()
		if err := v.SetCursor(cx, cy+1); err != nil {
			ox, oy := v.Origin()
			if err := v.SetOrigin(ox, oy+1); err != nil {
				return err
			}
		}
		if v == playlistsView {
			updateTracksView(g)
		}
	}
	return nil
}

func cursorUp(g *gocui.Gui, v *gocui.View) error {
	if v != nil {
		ox, oy := v.Origin()
		cx, cy := v.Cursor()
		if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
			if err := v.SetOrigin(ox, oy-1); err != nil {
				return err
			}
		}
		if v == playlistsView {
			updateTracksView(g)
		}
	}
	return nil
}

func getSelectedPlaylist(g *gocui.Gui) (string, error) {
	return getSelected(g, playlistsView)
}

func getSelectedTrack(g *gocui.Gui) (string, error) {
	return getSelected(g, tracksView)
}

func getSelected(g *gocui.Gui, v *gocui.View) (string, error) {
	var l string
	var err error

	_, cy := v.Cursor()
	if l, err = v.Line(cy); err != nil {
		l = ""
	}

	return l, nil
}

func quit(g *gocui.Gui, v *gocui.View) error {
	playEvents.Shutdown <- true
	<-playEvents.Shutdown
	return gocui.ErrorQuit
}

func playNext() error {
	if !queue.isEmpty() {
		playEvents.ToPlay <- queue.Pop()
		updateQueueView()
	} else if state.currentPlaylist != "" {
		playlist := playlists[state.currentPlaylist]
		if !state.randomMode {
			state.currentIndexTrack = getNextTrack(playlist)
		} else {
			state.currentIndexTrack = getRandomNextTrack(playlist)
		}
		playlistTrack := playlist.Track(state.currentIndexTrack)
		track := playlistTrack.Track()
		track.Wait()

		playEvents.ToPlay <- track
	}
	return nil
}

func getNextTrack(playlist *sp.Playlist) int {
	if state.currentIndexTrack >= playlist.Tracks()-1 {
		return 0
	}
	return state.currentIndexTrack + 1
}

func getRandomNextTrack(playlist *sp.Playlist) int {
	return rand.Intn(playlist.Tracks())
}

func playCurrentSelectedTrack(g *gocui.Gui, v *gocui.View) error {
	track := getCurrentSelectedTrack()
	if track != nil {
		playEvents.ToPlay <- track
	}
	return nil
}

func pauseCurrentSelectedTrack(g *gocui.Gui, v *gocui.View) error {
	playEvents.Pause <- true
	return nil
}

func setRandomMode(g *gocui.Gui, v *gocui.View) error {
	state.invertMode()
	updateStatus(state.currentMessage)
	return nil
}

func nextCommand(g *gocui.Gui, v *gocui.View) error {
	playNext()
	return nil
}

func queueCommand(g *gocui.Gui, v *gocui.View) error {
	track := getCurrentSelectedTrack()
	if track != nil {
		fmt.Fprintf(queueView, "%v - %v", track.Artist(0).Name(), track.Name())
		queue.Add(track)
	}
	return nil
}

func getCurrentSelectedTrack() *sp.Track {
	var errPlaylist error
	state.currentPlaylist, errPlaylist = getSelectedPlaylist(g)
	currentTrack, errTrack := getSelectedTrack(g)
	if errPlaylist == nil && errTrack == nil && playlists != nil {
		playlist := playlists[state.currentPlaylist]

		if playlist != nil {
			playlist.Wait()
			currentTrack = currentTrack[0:strings.Index(currentTrack, ".")]
			converted, _ := strconv.Atoi(currentTrack)
			state.currentIndexTrack = converted - 1
			playlistTrack := playlist.Track(state.currentIndexTrack)
			track := playlistTrack.Track()
			track.Wait()
			return track
		}
	}
	return nil
}

func keybindings(g *gocui.Gui) error {
	if err := g.SetKeybinding("main", gocui.KeySpace, 0, playCurrentSelectedTrack); err != nil {
		return err
	}
	if err := g.SetKeybinding("", 'p', 0, pauseCurrentSelectedTrack); err != nil {
		return err
	}
	if err := g.SetKeybinding("", 'r', 0, setRandomMode); err != nil {
		return err
	}
	if err := g.SetKeybinding("", '>', 0, nextCommand); err != nil {
		return err
	}
	if err := g.SetKeybinding("", 'u', 0, queueCommand); err != nil {
		return err
	}

	if err := g.SetKeybinding("", gocui.KeyArrowDown, 0, cursorDown); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyArrowUp, 0, cursorUp); err != nil {
		return err
	}
	if err := g.SetKeybinding("main", gocui.KeyArrowLeft, 0, nextView); err != nil {
		return err
	}
	if err := g.SetKeybinding("side", gocui.KeyArrowRight, 0, nextView); err != nil {
		return err
	}

	// vi navigation
	if err := g.SetKeybinding("", 'j', 0, cursorDown); err != nil {
		return err
	}
	if err := g.SetKeybinding("", 'k', 0, cursorUp); err != nil {
		return err
	}
	if err := g.SetKeybinding("main", 'h', 0, nextView); err != nil {
		return err
	}
	if err := g.SetKeybinding("side", 'l', 0, nextView); err != nil {
		return err
	}

	if err := g.SetKeybinding("", gocui.KeyCtrlC, 0, quit); err != nil {
		return err
	}
	if err := g.SetKeybinding("", 'q', 0, quit); err != nil {
		return err
	}

	return nil
}

func updateTracksView(g *gocui.Gui) {
	tracksView.Clear()
	tracksView.SetCursor(0, 0)
	tracksView.SetOrigin(0, 0)
	currentPlaylist, err := getSelectedPlaylist(g)
	if err == nil && playlists != nil {
		playlist := playlists[currentPlaylist]

		if playlist != nil {
			playlist.Wait()
			for i := 0; i < playlist.Tracks(); i++ {
				playlistTrack := playlist.Track(i)
				track := playlistTrack.Track()
				track.Wait()
				fmt.Fprintf(tracksView, "%v. %v - %v", (i + 1), track.Artist(0).Name(), track.Name())
			}
		}
	}
}

func updatePlaylistsView(g *gocui.Gui) {
	playlistsView.Clear()
	if playlists != nil {
		keys := make([]string, len(playlists))
		i := 0
		for k, _ := range playlists {
			keys[i] = k
			i++
		}
		sort.Strings(keys)
		for _, key := range keys {
			fmt.Fprintln(playlistsView, key)
		}
	}
}

func updateQueueView() {
	queueView.Clear()
	if !queue.isEmpty() {
		for _, track := range queue.Contents() {
			fmt.Fprintf(queueView, "%v - %v", track.Artist(0).Name(), track.Name())
		}
	}
}

func layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	if v, err := g.SetView("side", -1, -1, 25, maxY-2); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		playlistsView = v

		updatePlaylistsView(g)
	}
	if v, err := g.SetView("main", 25, -1, maxX-50, maxY-2); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		tracksView = v
		tracksView.Highlight = true

		updateTracksView(g)

		if err := g.SetCurrentView("main"); err != nil {
			return err
		}
	}
	if v, err := g.SetView("queue", maxX-50, -1, maxX, maxY-2); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		queueView = v
	}
	if v, err := g.SetView("status", -1, maxY-2, maxX, maxY); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		statusView = v
	}
	return nil
}
<|endoftext|>


<commit_before>package ui

import (
	"fmt"
	"log"
	"math/rand"
	"sort"
	"strconv"
	"strings"

	"github.com/fabiofalci/sconsify/events"
	"github.com/jroimartin/gocui"
	sp "github.com/op/go-libspotify/spotify"
)

var (
	gui       *Gui
	queue     *Queue
	state     *UiState
	playlists map[string]*sp.Playlist
)

type Gui struct {
	g             *gocui.Gui
	playlistsView *gocui.View
	tracksView    *gocui.View
	statusView    *gocui.View
	queueView     *gocui.View
	events        *events.Events
}

func StartConsoleUserInterface(events *events.Events) {
	select {
	case playlists = <-events.WaitForPlaylists():
		if playlists == nil {
			return
		}
	case <-events.WaitForShutdown():
		return
	}

	gui = &Gui{events: events}

	queue = InitQueue()
	state = InitState()

	go func() {
		for {
			select {
			case message := <-gui.events.WaitForStatus():
				gui.updateStatus(message)
			case <-gui.events.WaitForPlayTokenLost():
				gui.updateStatus("Play token lost")
			case <-gui.events.NextPlay:
				gui.playNext()
			}
		}
	}()

	gui.g = gocui.NewGui()
	if err := gui.g.Init(); err != nil {
		log.Panicln(err)
	}
	defer gui.g.Close()

	gui.g.SetLayout(layout)
	if err := keybindings(); err != nil {
		log.Panicln(err)
	}
	gui.g.SelBgColor = gocui.ColorGreen
	gui.g.SelFgColor = gocui.ColorBlack
	gui.g.ShowCursor = true

	err := gui.g.MainLoop()
	if err != nil && err != gocui.ErrorQuit {
		log.Panicln(err)
	}
}

func (gui *Gui) updateStatus(message string) {
	gui.statusView.Clear()
	gui.statusView.SetCursor(0, 0)
	gui.statusView.SetOrigin(0, 0)

	state.currentMessage = message
	fmt.Fprintf(gui.statusView, state.getModeAsString()+"%v", state.currentMessage)

	// otherwise the update will appear only in the next keyboard move
	gui.g.Flush()
}

func (gui *Gui) getSelectedPlaylist() (string, error) {
	return gui.getSelected(gui.playlistsView)
}

func (gui *Gui) getSelectedTrack() (string, error) {
	return gui.getSelected(gui.tracksView)
}

func (gui *Gui) getSelected(v *gocui.View) (string, error) {
	var l string
	var err error

	_, cy := v.Cursor()
	if l, err = v.Line(cy); err != nil {
		l = ""
	}

	return l, nil
}

func (gui *Gui) playNext() error {
	if !queue.isEmpty() {
		gui.playNextFromQueue()
	} else if state.hasPlaylistSelected() {
		gui.playNextFromPlaylist()
	}
	return nil
}

func (gui *Gui) playNextFromPlaylist() {
	playlist := playlists[state.currentPlaylist]
	if state.isAllRandomMode() {
		state.currentPlaylist, state.currentIndexTrack = getRandomNextPlaylistAndTrack()
		playlist = playlists[state.currentPlaylist]
	} else if state.isRandomMode() {
		state.currentIndexTrack = getRandomNextTrack(playlist)
	} else {
		state.currentIndexTrack = getNextTrack(playlist)
	}
	playlistTrack := playlist.Track(state.currentIndexTrack)
	track := playlistTrack.Track()
	track.Wait()

	gui.events.Play(track)
}

func (gui *Gui) playNextFromQueue() {
	gui.events.Play(queue.Pop())
	gui.updateQueueView()
}

func getNextTrack(playlist *sp.Playlist) int {
	if state.currentIndexTrack >= playlist.Tracks()-1 {
		return 0
	}
	return state.currentIndexTrack + 1
}

func getRandomNextTrack(playlist *sp.Playlist) int {
	return rand.Intn(playlist.Tracks())
}

func getRandomNextPlaylistAndTrack() (string, int) {
	index := rand.Intn(len(playlists))
	count := 0
	var playlist *sp.Playlist
	var newPlaylistName string
	for key, value := range playlists {
		if index == count {
			newPlaylistName = key
			playlist = value
			break
		}
		count++
	}
	return newPlaylistName, rand.Intn(playlist.Tracks())
}

func getCurrentSelectedTrack() *sp.Track {
	var errPlaylist error
	state.currentPlaylist, errPlaylist = gui.getSelectedPlaylist()
	currentTrack, errTrack := gui.getSelectedTrack()
	if errPlaylist == nil && errTrack == nil && playlists != nil {
		playlist := playlists[state.currentPlaylist]

		if playlist != nil {
			playlist.Wait()
			currentTrack = currentTrack[0:strings.Index(currentTrack, ".")]
			converted, _ := strconv.Atoi(currentTrack)
			state.currentIndexTrack = converted - 1
			playlistTrack := playlist.Track(state.currentIndexTrack)
			track := playlistTrack.Track()
			track.Wait()
			return track
		}
	}
	return nil
}

func keybindings() error {
	if err := gui.g.SetKeybinding("main", gocui.KeySpace, 0, playCurrentSelectedTrack); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", 'p', 0, pauseCurrentSelectedTrack); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", 'r', 0, setRandomMode); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", 'R', 0, setAllRandomMode); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", '>', 0, nextCommand); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", 'u', 0, queueCommand); err != nil {
		return err
	}

	if err := gui.g.SetKeybinding("", gocui.KeyHome, 0, cursorHome); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", gocui.KeyEnd, 0, cursorEnd); err != nil {
		return err
	}

	if err := gui.g.SetKeybinding("", gocui.KeyPgup, 0, cursorPgup); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", gocui.KeyPgdn, 0, cursorPgdn); err != nil {
		return err
	}

	if err := gui.g.SetKeybinding("", gocui.KeyArrowDown, 0, cursorDown); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", gocui.KeyArrowUp, 0, cursorUp); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("main", gocui.KeyArrowLeft, 0, nextView); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("side", gocui.KeyArrowRight, 0, nextView); err != nil {
		return err
	}

	// vi navigation
	if err := gui.g.SetKeybinding("", 'j', 0, cursorDown); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", 'k', 0, cursorUp); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("main", 'h', 0, nextView); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("side", 'l', 0, nextView); err != nil {
		return err
	}

	if err := gui.g.SetKeybinding("", gocui.KeyCtrlC, 0, quit); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", 'q', 0, quit); err != nil {
		return err
	}

	return nil
}

func (gui *Gui) updateTracksView() {
	gui.tracksView.Clear()
	gui.tracksView.SetCursor(0, 0)
	gui.tracksView.SetOrigin(0, 0)
	currentPlaylist, err := gui.getSelectedPlaylist()
	if err == nil && playlists != nil {
		playlist := playlists[currentPlaylist]

		if playlist != nil {
			playlist.Wait()
			for i := 0; i < playlist.Tracks(); i++ {
				playlistTrack := playlist.Track(i)
				track := playlistTrack.Track()
				track.Wait()
				fmt.Fprintf(gui.tracksView, "%v. %v - %v", (i + 1), track.Artist(0).Name(), track.Name())
			}
		}
	}
}

func (gui *Gui) updatePlaylistsView() {
	gui.playlistsView.Clear()
	if playlists != nil {
		keys := make([]string, len(playlists))
		i := 0
		for k, _ := range playlists {
			keys[i] = k
			i++
		}
		sort.Strings(keys)
		for _, key := range keys {
			fmt.Fprintln(gui.playlistsView, key)
		}
	}
}

func (gui *Gui) updateQueueView() {
	gui.queueView.Clear()
	if !queue.isEmpty() {
		for _, track := range queue.Contents() {
			fmt.Fprintf(gui.queueView, "%v - %v", track.Artist(0).Name(), track.Name())
		}
	}
}

func layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	if v, err := g.SetView("side", -1, -1, 25, maxY-2); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		gui.playlistsView = v
		gui.playlistsView.Highlight = true

		gui.updatePlaylistsView()

		if err := g.SetCurrentView("side"); err != nil {
			return err
		}
	}
	if v, err := g.SetView("main", 25, -1, maxX-50, maxY-2); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		gui.tracksView = v

		gui.updateTracksView()
	}
	if v, err := g.SetView("queue", maxX-50, -1, maxX, maxY-2); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		gui.queueView = v
	}
	if v, err := g.SetView("status", -1, maxY-2, maxX, maxY); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		gui.statusView = v
	}
	return nil
}
<commit_msg>Single method to handle play events<commit_after>package ui

import (
	"fmt"
	"log"
	"math/rand"
	"sort"
	"strconv"
	"strings"

	"github.com/fabiofalci/sconsify/events"
	"github.com/jroimartin/gocui"
	sp "github.com/op/go-libspotify/spotify"
)

var (
	gui       *Gui
	queue     *Queue
	state     *UiState
	playlists map[string]*sp.Playlist
)

type Gui struct {
	g             *gocui.Gui
	playlistsView *gocui.View
	tracksView    *gocui.View
	statusView    *gocui.View
	queueView     *gocui.View
	events        *events.Events
	currentTrack  *sp.Track
}

func StartConsoleUserInterface(events *events.Events) {
	select {
	case playlists = <-events.WaitForPlaylists():
		if playlists == nil {
			return
		}
	case <-events.WaitForShutdown():
		return
	}

	gui = &Gui{events: events}

	queue = InitQueue()
	state = InitState()

	go func() {
		for {
			select {
			case message := <-gui.events.WaitForStatus():
				gui.updateStatus(message)
			case <-gui.events.WaitForPlayTokenLost():
				gui.updateStatus("Play token lost")
			case <-gui.events.NextPlay:
				gui.playNext()
			}
		}
	}()

	gui.g = gocui.NewGui()
	if err := gui.g.Init(); err != nil {
		log.Panicln(err)
	}
	defer gui.g.Close()

	gui.g.SetLayout(layout)
	if err := keybindings(); err != nil {
		log.Panicln(err)
	}
	gui.g.SelBgColor = gocui.ColorGreen
	gui.g.SelFgColor = gocui.ColorBlack
	gui.g.ShowCursor = true

	err := gui.g.MainLoop()
	if err != nil && err != gocui.ErrorQuit {
		log.Panicln(err)
	}
}

func (gui *Gui) updateStatus(message string) {
	gui.statusView.Clear()
	gui.statusView.SetCursor(0, 0)
	gui.statusView.SetOrigin(0, 0)

	state.currentMessage = message
	fmt.Fprintf(gui.statusView, state.getModeAsString()+"%v", state.currentMessage)

	// otherwise the update will appear only in the next keyboard move
	gui.g.Flush()
}

func (gui *Gui) getSelectedPlaylist() (string, error) {
	return gui.getSelected(gui.playlistsView)
}

func (gui *Gui) getSelectedTrack() (string, error) {
	return gui.getSelected(gui.tracksView)
}

func (gui *Gui) getSelected(v *gocui.View) (string, error) {
	var l string
	var err error

	_, cy := v.Cursor()
	if l, err = v.Line(cy); err != nil {
		l = ""
	}

	return l, nil
}

func (gui *Gui) playNext() error {
	if !queue.isEmpty() {
		gui.playNextFromQueue()
	} else if state.hasPlaylistSelected() {
		gui.playNextFromPlaylist()
	}
	return nil
}

func (gui *Gui) playNextFromPlaylist() {
	playlist := playlists[state.currentPlaylist]
	if state.isAllRandomMode() {
		state.currentPlaylist, state.currentIndexTrack = getRandomNextPlaylistAndTrack()
		playlist = playlists[state.currentPlaylist]
	} else if state.isRandomMode() {
		state.currentIndexTrack = getRandomNextTrack(playlist)
	} else {
		state.currentIndexTrack = getNextTrack(playlist)
	}
	playlistTrack := playlist.Track(state.currentIndexTrack)
	track := playlistTrack.Track()
	track.Wait()

	gui.play(track)
}

func (gui *Gui) playNextFromQueue() {
	gui.play(queue.Pop())
	gui.updateQueueView()
}

func (gui *Gui) play(track *sp.Track) {
	gui.currentTrack = track
	gui.events.Play(gui.currentTrack)
}

func getNextTrack(playlist *sp.Playlist) int {
	if state.currentIndexTrack >= playlist.Tracks()-1 {
		return 0
	}
	return state.currentIndexTrack + 1
}

func getRandomNextTrack(playlist *sp.Playlist) int {
	return rand.Intn(playlist.Tracks())
}

func getRandomNextPlaylistAndTrack() (string, int) {
	index := rand.Intn(len(playlists))
	count := 0
	var playlist *sp.Playlist
	var newPlaylistName string
	for key, value := range playlists {
		if index == count {
			newPlaylistName = key
			playlist = value
			break
		}
		count++
	}
	return newPlaylistName, rand.Intn(playlist.Tracks())
}

func getCurrentSelectedTrack() *sp.Track {
	var errPlaylist error
	state.currentPlaylist, errPlaylist = gui.getSelectedPlaylist()
	currentTrack, errTrack := gui.getSelectedTrack()
	if errPlaylist == nil && errTrack == nil && playlists != nil {
		playlist := playlists[state.currentPlaylist]

		if playlist != nil {
			playlist.Wait()
			currentTrack = currentTrack[0:strings.Index(currentTrack, ".")]
			converted, _ := strconv.Atoi(currentTrack)
			state.currentIndexTrack = converted - 1
			playlistTrack := playlist.Track(state.currentIndexTrack)
			track := playlistTrack.Track()
			track.Wait()
			return track
		}
	}
	return nil
}

func keybindings() error {
	if err := gui.g.SetKeybinding("main", gocui.KeySpace, 0, playCurrentSelectedTrack); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", 'p', 0, pauseCurrentSelectedTrack); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", 'r', 0, setRandomMode); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", 'R', 0, setAllRandomMode); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", '>', 0, nextCommand); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", 'u', 0, queueCommand); err != nil {
		return err
	}

	if err := gui.g.SetKeybinding("", gocui.KeyHome, 0, cursorHome); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", gocui.KeyEnd, 0, cursorEnd); err != nil {
		return err
	}

	if err := gui.g.SetKeybinding("", gocui.KeyPgup, 0, cursorPgup); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", gocui.KeyPgdn, 0, cursorPgdn); err != nil {
		return err
	}

	if err := gui.g.SetKeybinding("", gocui.KeyArrowDown, 0, cursorDown); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", gocui.KeyArrowUp, 0, cursorUp); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("main", gocui.KeyArrowLeft, 0, nextView); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("side", gocui.KeyArrowRight, 0, nextView); err != nil {
		return err
	}

	// vi navigation
	if err := gui.g.SetKeybinding("", 'j', 0, cursorDown); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", 'k', 0, cursorUp); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("main", 'h', 0, nextView); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("side", 'l', 0, nextView); err != nil {
		return err
	}

	if err := gui.g.SetKeybinding("", gocui.KeyCtrlC, 0, quit); err != nil {
		return err
	}
	if err := gui.g.SetKeybinding("", 'q', 0, quit); err != nil {
		return err
	}

	return nil
}

func (gui *Gui) updateTracksView() {
	gui.tracksView.Clear()
	gui.tracksView.SetCursor(0, 0)
	gui.tracksView.SetOrigin(0, 0)
	currentPlaylist, err := gui.getSelectedPlaylist()
	if err == nil && playlists != nil {
		playlist := playlists[currentPlaylist]

		if playlist != nil {
			playlist.Wait()
			for i := 0; i < playlist.Tracks(); i++ {
				playlistTrack := playlist.Track(i)
				track := playlistTrack.Track()
				track.Wait()
				fmt.Fprintf(gui.tracksView, "%v. %v - %v", (i + 1), track.Artist(0).Name(), track.Name())
			}
		}
	}
}

func (gui *Gui) updatePlaylistsView() {
	gui.playlistsView.Clear()
	if playlists != nil {
		keys := make([]string, len(playlists))
		i := 0
		for k, _ := range playlists {
			keys[i] = k
			i++
		}
		sort.Strings(keys)
		for _, key := range keys {
			fmt.Fprintln(gui.playlistsView, key)
		}
	}
}

func (gui *Gui) updateQueueView() {
	gui.queueView.Clear()
	if !queue.isEmpty() {
		for _, track := range queue.Contents() {
			fmt.Fprintf(gui.queueView, "%v - %v", track.Artist(0).Name(), track.Name())
		}
	}
}

func layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	if v, err := g.SetView("side", -1, -1, 25, maxY-2); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		gui.playlistsView = v
		gui.playlistsView.Highlight = true

		gui.updatePlaylistsView()

		if err := g.SetCurrentView("side"); err != nil {
			return err
		}
	}
	if v, err := g.SetView("main", 25, -1, maxX-50, maxY-2); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		gui.tracksView = v

		gui.updateTracksView()
	}
	if v, err := g.SetView("queue", maxX-50, -1, maxX, maxY-2); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		gui.queueView = v
	}
	if v, err := g.SetView("status", -1, maxY-2, maxX, maxY); err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		gui.statusView = v
	}
	return nil
}
<|endoftext|>


<commit_before>/*
 * src/go/dnsresolvd.go
 * ============================================================================
 * DNS Resolver Daemon (dnsresolvd). Version 0.1
 * ============================================================================
 * A daemon that performs DNS lookups for the given hostname
 * passed in an HTTP request, with the focus on its implementation
 * using various programming languages. (net/http-boosted impl.)
 * ============================================================================
 * Copyright (C) 2017-2020 Radislav (Radicchio) Golubtsov
 *
 * (See the LICENSE file at the top of the source tree.)
 */

package main

import (
    "os"
    "strconv"
    "strings"
    "fmt"
    "log/syslog"
    "path/filepath"
    "net/http"
    "io/ioutil"
    "net"
)

// The daemon entry point.
func main() {
    var ret int = _EXIT_SUCCESS

    var argc uint = uint(len(os.Args) - 1)

        daemon_name := os.Args[0]
    var port_number uint

    var print_banner_opt string = _EMPTY_STRING

    if (argc > 0) {
        port_number_, e := strconv.Atoi(os.Args[1])

        if (e == nil) { port_number = uint(port_number_) }

        if (argc > 1) {
            print_banner_opt = strings.ToUpper(os.Args[2])
        }
    } else {
        port_number = 0
    }

    if (print_banner_opt == _PRINT_BANNER_OPT) {
        _separator_draw(_DMN_DESCRIPTION)

        fmt.Printf(_DMN_NAME   + _COMMA_SPACE_SEP                             +
              _DMN_VERSION_S__ + _ONE_SPACE_STRING + _DMN_VERSION + _NEW_LINE +
              _DMN_DESCRIPTION +                                    _NEW_LINE +
              _DMN_COPYRIGHT__ + _ONE_SPACE_STRING + _DMN_AUTHOR  + _NEW_LINE)

        _separator_draw(_DMN_DESCRIPTION)
    }

    // Opening the system logger.
    log, _ := syslog.Dial(_EMPTY_STRING,   _EMPTY_STRING,
                          syslog.LOG_ERR | syslog.LOG_DAEMON,
                          filepath.Base(daemon_name))

    // Checking for args presence.
    if (argc == 0) {
        ret = _EXIT_FAILURE

        var argc_str string = strconv.Itoa(int(argc))

        fmt.Fprintf(os.Stderr, daemon_name      +
                    _ERR_MUST_BE_ONE_TWO_ARGS_1 + argc_str  +
                    _ERR_MUST_BE_ONE_TWO_ARGS_2 + _NEW_LINE + _NEW_LINE)

        log.Err(               daemon_name      +
                    _ERR_MUST_BE_ONE_TWO_ARGS_1 + argc_str  +
                    _ERR_MUST_BE_ONE_TWO_ARGS_2 + _NEW_LINE)

        fmt.Fprintf(os.Stderr, _MSG_USAGE_TEMPLATE_1 + daemon_name +
                               _MSG_USAGE_TEMPLATE_2 + _NEW_LINE   + _NEW_LINE)

        _cleanups_fixate(log)

        os.Exit(ret)
    }

    // Checking for port correctness.
    if ((port_number < _MIN_PORT) || (port_number > _MAX_PORT)) {
        ret = _EXIT_FAILURE

        fmt.Fprintf(os.Stderr, daemon_name         +
                    _ERR_PORT_MUST_BE_POSITIVE_INT + _NEW_LINE + _NEW_LINE)

        log.Err(               daemon_name         +
                    _ERR_PORT_MUST_BE_POSITIVE_INT + _NEW_LINE)

        fmt.Fprintf(os.Stderr, _MSG_USAGE_TEMPLATE_1 + daemon_name +
                               _MSG_USAGE_TEMPLATE_2 + _NEW_LINE   + _NEW_LINE)

        _cleanups_fixate(log)

        os.Exit(ret)
    }

    var port_number_str string = strconv.Itoa(int(port_number))

    fmt.Printf(_MSG_SERVER_STARTED_1 + port_number_str + _NEW_LINE +
               _MSG_SERVER_STARTED_2 +                   _NEW_LINE)

    log.Info(  _MSG_SERVER_STARTED_1 + port_number_str + _NEW_LINE +
               _MSG_SERVER_STARTED_2)

    // Defining the default request handler.
    _request_handler := func(resp http.ResponseWriter, req *http.Request) {
        var mtd string = req.Method

        var params []string

        var resp_buffer string = _EMPTY_STRING

               if (mtd == http.MethodGet ) {
            params = strings.Split(req.URL.RawQuery, _AMPER)
        } else if (mtd == http.MethodPost) {
            req_body, _ := ioutil.ReadAll(req.Body)
            params = strings.Split(string(req_body), _AMPER)
        }

        // Parsing and validating request params.
        hostname, frt := _parse_and_validate(params)

        // Performing DNS lookup for the given hostname.
        addr, ver := dns_lookup(hostname)

               if (frt == _PRM_FMT_HTML) {
            resp_buffer = "<!DOCTYPE html>"                                                   + _NEW_LINE +
""                                                       + _NEW_LINE +
""                                                                                + _NEW_LINE +
"<meta http-equiv=\"" + _HDR_CONTENT_TYPE_N      +                     "\"    content=\"" +
                        _HDR_CONTENT_TYPE_V_HTML +                     "\"           />"  + _NEW_LINE +
"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"                            />"  + _NEW_LINE +
"<meta       name=\"viewport\"        content=\"width=device-width,initial-scale=1\" />"  + _NEW_LINE +
"<title>" + _DMN_NAME + "</title>"                                                        + _NEW_LINE +
""                                                                                + _NEW_LINE +
""                                                                                + _NEW_LINE +
"
"   +  hostname + _ONE_SPACE_STRING
        } else if (frt == _PRM_FMT_JSON) {
        }

               if (frt == _PRM_FMT_HTML) {
            resp_buffer += addr              +
                           _ONE_SPACE_STRING +
                           _DAT_VERSION_V    +
                           strconv.Itoa(int(ver))
        } else if (frt == _PRM_FMT_JSON) {
        }

               if (frt == _PRM_FMT_HTML) {
            resp_buffer += "
"  + _NEW_LINE +
                           "" + _NEW_LINE +
                           "" + _NEW_LINE
        } else if (frt == _PRM_FMT_JSON) {
        }

        fmt.Fprintf(resp, resp_buffer)
    }

    /*
     * Attaching HTTP request handlers to process incoming requests
     * and producing the response.
     */
    http.HandleFunc("/", _request_handler)

    // Starting up the HTTP listener on <port_number>.
    e := http.ListenAndServe(_COLON + port_number_str, nil)

    // Handling errors during start up of the listener.
    if (e != nil) {
        ret = _EXIT_FAILURE

        if (strings.Contains(e.Error(), _ERR_ADDR_ALREADY_IN_USE)) {
            fmt.Fprintf(os.Stderr, daemon_name   +
                        _ERR_CANNOT_START_SERVER +
                        _ERR_SRV_PORT_IS_IN_USE  + _NEW_LINE + _NEW_LINE)

            log.Err(               daemon_name   +
                        _ERR_CANNOT_START_SERVER +
                        _ERR_SRV_PORT_IS_IN_USE  + _NEW_LINE)
        } else {
            fmt.Fprintf(os.Stderr, daemon_name   +
                        _ERR_CANNOT_START_SERVER +
                        _ERR_SRV_UNKNOWN_REASON  + _NEW_LINE + _NEW_LINE)

            log.Err(               daemon_name   +
                        _ERR_CANNOT_START_SERVER +
                        _ERR_SRV_UNKNOWN_REASON  + _NEW_LINE)
        }

        _cleanups_fixate(log)

        os.Exit(ret)
    }

    // Making final cleanups.
    _cleanups_fixate(log)

    os.Exit(ret)
}

// Parses and validates request params.
func _parse_and_validate(params []string) (string, string) {
    var hostname, frt string

    const _h string = "h="
    const _f string = "f="

    // ------------------------------------------------------------------------
    // --- Parsing and validating request params - Begin ----------------------
    // ------------------------------------------------------------------------
    for i := 0; i < len(params); i++ {
               if (strings.HasPrefix(     params[i], _h)) {
            hostname = strings.TrimPrefix(params[i], _h) // <-----------+
        } else if (strings.HasPrefix(     params[i], _f)) { //          |
            frt      = strings.TrimPrefix(params[i], _f) // <-------+   |
        }                                                   //      |   |
    } /*                                    +-----------------------+---+
                                            |                       |   |
                                            |            +----------+   |
                                            |            |          |   |
    $ curl 'http://localhost:<port-number>/?h=<hostname>&f=<fmt>'   |   |
    $ curl -d 'h=<hostname>&f=<fmt>' http://localhost:<port_number> |   |
               |            |                                       |   |
               |            +---------------------------------------+   |
               |                                                        |
               +--------------------------------------------------------+ */

    if (hostname == _EMPTY_STRING) {
        hostname  = _DEF_HOSTNAME
    }

    if (frt      == _EMPTY_STRING) {
        frt       = _PRM_FMT_JSON
    } else {
        frt       = strings.ToLower(frt)

        frt_ := []string {
            _PRM_FMT_HTML,
            _PRM_FMT_JSON,
        }

        var _frt bool = false

        for i := 0; i < len(frt_); i++ {
            if (frt == frt_[i]) {
               _frt = true; break
            }
        }

        if (!_frt) {
            frt   = _PRM_FMT_JSON
        }
    }
    // ------------------------------------------------------------------------
    // --- Parsing and validating request params - End ------------------------
    // ------------------------------------------------------------------------

    return hostname, frt
}

/**
 * Performs DNS lookup action for the given hostname,
 * i.e. (in this case) IP address retrieval by hostname.
 *
 * @param hostname The effective hostname to look up for.
 *
 * @return The IP address of the analyzing host/service
 *         and the corresponding IP version (family)
 *         used to look up in DNS:
 *         4 for IPv4-only hosts,
 *         6 for IPv6-capable hosts.
 */
func dns_lookup(hostname string) (string, uint) {
    var addr string
    var ver  uint

    addrs, e := net.LookupHost(hostname)

    if ((e == nil) || (len(addrs) > 0)) {
        addr = addrs[0]

        if (strings.Contains(addr, _COLON)) {
            ver = 6
        } else {
            ver = 4
        }
    } else {
        addr = _ERR_PREFIX
        ver  = 0
    }

    return addr, ver
}

// vim:set nu et ts=4 sw=4:
<commit_msg>net/http/go: Make commentary adjustments and corrections.<commit_after>/*
 * src/go/dnsresolvd.go
 * ============================================================================
 * DNS Resolver Daemon (dnsresolvd). Version 0.1
 * ============================================================================
 * A daemon that performs DNS lookups for the given hostname
 * passed in an HTTP request, with the focus on its implementation
 * using various programming languages. (net/http-boosted impl.)
 * ============================================================================
 * Copyright (C) 2017-2020 Radislav (Radicchio) Golubtsov
 *
 * (See the LICENSE file at the top of the source tree.)
 */

package main

import (
    "os"
    "strconv"
    "strings"
    "fmt"
    "log/syslog"
    "path/filepath"
    "net/http"
    "io/ioutil"
    "net"
)

// The daemon entry point.
func main() {
    var ret int = _EXIT_SUCCESS

    var argc uint = uint(len(os.Args) - 1)

        daemon_name := os.Args[0]
    var port_number uint

    var print_banner_opt string = _EMPTY_STRING

    if (argc > 0) {
        port_number_, e := strconv.Atoi(os.Args[1])

        if (e == nil) { port_number = uint(port_number_) }

        if (argc > 1) {
            print_banner_opt = strings.ToUpper(os.Args[2])
        }
    } else {
        port_number = 0
    }

    if (print_banner_opt == _PRINT_BANNER_OPT) {
        _separator_draw(_DMN_DESCRIPTION)

        fmt.Printf(_DMN_NAME   + _COMMA_SPACE_SEP                             +
              _DMN_VERSION_S__ + _ONE_SPACE_STRING + _DMN_VERSION + _NEW_LINE +
              _DMN_DESCRIPTION +                                    _NEW_LINE +
              _DMN_COPYRIGHT__ + _ONE_SPACE_STRING + _DMN_AUTHOR  + _NEW_LINE)

        _separator_draw(_DMN_DESCRIPTION)
    }

    // Opening the system logger.
    log, _ := syslog.Dial(_EMPTY_STRING,   _EMPTY_STRING,
                          syslog.LOG_ERR | syslog.LOG_DAEMON,
                          filepath.Base(daemon_name))

    // Checking for args presence.
    if (argc == 0) {
        ret = _EXIT_FAILURE

        var argc_str string = strconv.Itoa(int(argc))

        fmt.Fprintf(os.Stderr, daemon_name      +
                    _ERR_MUST_BE_ONE_TWO_ARGS_1 + argc_str  +
                    _ERR_MUST_BE_ONE_TWO_ARGS_2 + _NEW_LINE + _NEW_LINE)

        log.Err(               daemon_name      +
                    _ERR_MUST_BE_ONE_TWO_ARGS_1 + argc_str  +
                    _ERR_MUST_BE_ONE_TWO_ARGS_2 + _NEW_LINE)

        fmt.Fprintf(os.Stderr, _MSG_USAGE_TEMPLATE_1 + daemon_name +
                               _MSG_USAGE_TEMPLATE_2 + _NEW_LINE   + _NEW_LINE)

        _cleanups_fixate(log)

        os.Exit(ret)
    }

    // Checking for port correctness.
    if ((port_number < _MIN_PORT) || (port_number > _MAX_PORT)) {
        ret = _EXIT_FAILURE

        fmt.Fprintf(os.Stderr, daemon_name         +
                    _ERR_PORT_MUST_BE_POSITIVE_INT + _NEW_LINE + _NEW_LINE)

        log.Err(               daemon_name         +
                    _ERR_PORT_MUST_BE_POSITIVE_INT + _NEW_LINE)

        fmt.Fprintf(os.Stderr, _MSG_USAGE_TEMPLATE_1 + daemon_name +
                               _MSG_USAGE_TEMPLATE_2 + _NEW_LINE   + _NEW_LINE)

        _cleanups_fixate(log)

        os.Exit(ret)
    }

    var port_number_str string = strconv.Itoa(int(port_number))

    fmt.Printf(_MSG_SERVER_STARTED_1 + port_number_str + _NEW_LINE +
               _MSG_SERVER_STARTED_2 +                   _NEW_LINE)

    log.Info(  _MSG_SERVER_STARTED_1 + port_number_str + _NEW_LINE +
               _MSG_SERVER_STARTED_2)

    // Defining the default request handler.
    _request_handler := func(resp http.ResponseWriter, req *http.Request) {
        var mtd string = req.Method

        var params []string

        var resp_buffer string = _EMPTY_STRING

               if (mtd == http.MethodGet ) {
            params = strings.Split(req.URL.RawQuery, _AMPER)
        } else if (mtd == http.MethodPost) {
            req_body, _ := ioutil.ReadAll(req.Body)
            params = strings.Split(string(req_body), _AMPER)
        }

        // Parsing and validating request params.
        hostname, frt := _parse_and_validate(params)

        // Performing DNS lookup for the given hostname.
        addr, ver := dns_lookup(hostname)

               if (frt == _PRM_FMT_HTML) {
            resp_buffer = "<!DOCTYPE html>"                                                   + _NEW_LINE +
""                                                       + _NEW_LINE +
""                                                                                + _NEW_LINE +
"<meta http-equiv=\"" + _HDR_CONTENT_TYPE_N      +                     "\"    content=\"" +
                        _HDR_CONTENT_TYPE_V_HTML +                     "\"           />"  + _NEW_LINE +
"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"                            />"  + _NEW_LINE +
"<meta       name=\"viewport\"        content=\"width=device-width,initial-scale=1\" />"  + _NEW_LINE +
"<title>" + _DMN_NAME + "</title>"                                                        + _NEW_LINE +
""                                                                                + _NEW_LINE +
""                                                                                + _NEW_LINE +
"
"   +  hostname + _ONE_SPACE_STRING
        } else if (frt == _PRM_FMT_JSON) {
        }

               if (frt == _PRM_FMT_HTML) {
            resp_buffer += addr              +
                           _ONE_SPACE_STRING +
                           _DAT_VERSION_V    +
                           strconv.Itoa(int(ver))
        } else if (frt == _PRM_FMT_JSON) {
        }

               if (frt == _PRM_FMT_HTML) {
            resp_buffer += "
"  + _NEW_LINE +
                           "" + _NEW_LINE +
                           "" + _NEW_LINE
        } else if (frt == _PRM_FMT_JSON) {
        }

        fmt.Fprintf(resp, resp_buffer)
    }

    /*
     * Attaching HTTP request handlers to process incoming requests
     * and producing the response.
     */
    http.HandleFunc("/", _request_handler)

    // Starting up the HTTP listener on <port_number>.
    e := http.ListenAndServe(_COLON + port_number_str, nil)

    // Handling errors during start up of the listener.
    if (e != nil) {
        ret = _EXIT_FAILURE

        if (strings.Contains(e.Error(), _ERR_ADDR_ALREADY_IN_USE)) {
            fmt.Fprintf(os.Stderr, daemon_name   +
                        _ERR_CANNOT_START_SERVER +
                        _ERR_SRV_PORT_IS_IN_USE  + _NEW_LINE + _NEW_LINE)

            log.Err(               daemon_name   +
                        _ERR_CANNOT_START_SERVER +
                        _ERR_SRV_PORT_IS_IN_USE  + _NEW_LINE)
        } else {
            fmt.Fprintf(os.Stderr, daemon_name   +
                        _ERR_CANNOT_START_SERVER +
                        _ERR_SRV_UNKNOWN_REASON  + _NEW_LINE + _NEW_LINE)

            log.Err(               daemon_name   +
                        _ERR_CANNOT_START_SERVER +
                        _ERR_SRV_UNKNOWN_REASON  + _NEW_LINE)
        }

        _cleanups_fixate(log)

        os.Exit(ret)
    }

    // Making final cleanups.
    _cleanups_fixate(log)

    os.Exit(ret)
}

// Helper function. Parses and validates request params.
func _parse_and_validate(params []string) (string, string) {
    var hostname, frt string

    const _h string = "h="
    const _f string = "f="

    // ------------------------------------------------------------------------
    // --- Parsing and validating request params - Begin ----------------------
    // ------------------------------------------------------------------------
    for i := 0; i < len(params); i++ {
               if (strings.HasPrefix(     params[i], _h)) {
            hostname = strings.TrimPrefix(params[i], _h) // <-----------+
        } else if (strings.HasPrefix(     params[i], _f)) { //          |
            frt      = strings.TrimPrefix(params[i], _f) // <-------+   |
        }                                                   //      |   |
    }   /*                                  +-----------------------+---+
                                            |                       |   |
                                            |            +----------+   |
                                            |            |          |   |
    $ curl 'http://localhost:<port-number>/?h=<hostname>&f=<fmt>'   |   |
    $ curl -d 'h=<hostname>&f=<fmt>' http://localhost:<port_number> |   |
               |            |                                       |   |
               |            +---------------------------------------+   |
               |                                                        |
               +--------------------------------------------------------+ */

    if (hostname == _EMPTY_STRING) {
        hostname  = _DEF_HOSTNAME
    }

    if (frt      == _EMPTY_STRING) {
        frt       = _PRM_FMT_JSON
    } else {
        frt       = strings.ToLower(frt)

        frt_ := []string {
            _PRM_FMT_HTML,
            _PRM_FMT_JSON,
        }

        var _frt bool = false

        for i := 0; i < len(frt_); i++ {
            if (frt == frt_[i]) {
               _frt = true; break
            }
        }

        if (!_frt) {
            frt   = _PRM_FMT_JSON
        }
    }
    // ------------------------------------------------------------------------
    // --- Parsing and validating request params - End ------------------------
    // ------------------------------------------------------------------------

    return hostname, frt
}

/**
 * Performs DNS lookup action for the given hostname,
 * i.e. (in this case) IP address retrieval by hostname.
 *
 * @param hostname The effective hostname to look up for.
 *
 * @return The IP address of the analyzing host/service
 *         and the corresponding IP version (family)
 *         used to look up in DNS:
 *         4 for IPv4-only hosts,
 *         6 for IPv6-capable hosts.
 */
func dns_lookup(hostname string) (string, uint) {
    var addr string
    var ver  uint

    addrs, e := net.LookupHost(hostname)

    if ((e == nil) || (len(addrs) > 0)) {
        addr = addrs[0]

        if (strings.Contains(addr, _COLON)) {
            ver = 6
        } else {
            ver = 4
        }
    } else {
        addr = _ERR_PREFIX
        ver  = 0
    }

    return addr, ver
}

// vim:set nu et ts=4 sw=4:
<|endoftext|>


<commit_before>// Copyright 2016 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 stackdriver

import (
	"context"
	"crypto/tls"
	"fmt"
	"strconv"
	"sync"
	"time"

	monitoring "google.golang.org/genproto/googleapis/monitoring/v3"
	"google.golang.org/grpc"
	"google.golang.org/grpc/balancer/roundrobin"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/credentials/oauth"
	"google.golang.org/grpc/status"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/prometheus/common/model"

	config_util "github.com/prometheus/common/config"
)

const (
	// TODO(jkohen): Use a custom prefix specific to Prometheus.
	metricsPrefix             = "custom.googleapis.com"
	maxTimeseriesesPerRequest = 200
	MonitoringWriteScope      = "https://www.googleapis.com/auth/monitoring.write"
)

// Client allows reading and writing from/to a remote gRPC endpoint. The
// implementation may hit a single backend, so the application should create a
// number of these clients.
type Client struct {
	index     int // Used to differentiate clients in metrics.
	logger    log.Logger
	projectId string
	url       *config_util.URL
	timeout   time.Duration

	conn *grpc.ClientConn
}

// ClientConfig configures a Client.
type ClientConfig struct {
	Logger    log.Logger
	ProjectId string // The Stackdriver project id in "projects/name-or-number" format.
	URL       *config_util.URL
	Timeout   model.Duration
}

// NewClient creates a new Client.
func NewClient(index int, conf *ClientConfig) *Client {
	logger := conf.Logger
	if logger == nil {
		logger = log.NewNopLogger()
	}
	return &Client{
		index:     index,
		logger:    logger,
		projectId: conf.ProjectId,
		url:       conf.URL,
		timeout:   time.Duration(conf.Timeout),
	}
}

type recoverableError struct {
	error
}

func (c *Client) getConnection(ctx context.Context) (*grpc.ClientConn, error) {
	if c.conn != nil {
		return c.conn, nil
	}

	useAuth, err := strconv.ParseBool(c.url.Query().Get("auth"))
	if err != nil {
		useAuth = true // Default to auth enabled.
	}
	level.Debug(c.logger).Log(
		"msg", "is auth enabled",
		"auth", useAuth,
		"url", c.url.String())
	// Google APIs currently return a single IP for the whole service.  gRPC
	// client-side load-balancing won't spread the load across backends
	// while that's true, but it also doesn't hurt.
	dopts := []grpc.DialOption{
		grpc.WithBalancerName(roundrobin.Name),
		grpc.WithBlock(), // Wait for the connection to be established before using it.
	}
	if useAuth {
		rpcCreds, err := oauth.NewApplicationDefault(context.Background(), MonitoringWriteScope)
		if err != nil {
			return nil, err
		}
		tlsCreds := credentials.NewTLS(&tls.Config{})
		dopts = append(dopts,
			grpc.WithTransportCredentials(tlsCreds),
			grpc.WithPerRPCCredentials(rpcCreds))
	} else {
		dopts = append(dopts, grpc.WithInsecure())
	}
	address := c.url.Hostname()
	if len(c.url.Port()) > 0 {
		address = fmt.Sprintf("%s:%s", address, c.url.Port())
	}
	conn, err := grpc.DialContext(ctx, address, dopts...)
	c.conn = conn
	return conn, err
}

// Store sends a batch of samples to the HTTP endpoint.
func (c *Client) Store(req *monitoring.CreateTimeSeriesRequest) error {
	tss := req.TimeSeries
	if len(tss) == 0 {
		// Nothing to do, return silently.
		return nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()

	conn, err := c.getConnection(ctx)
	if err != nil {
		return err
	}

	level.Debug(c.logger).Log("msg", "sending request to Stackdriver")
	service := monitoring.NewMetricServiceClient(conn)

	errors := make(chan error, len(tss)/maxTimeseriesesPerRequest+1)
	var wg sync.WaitGroup
	for i := 0; i < len(tss); i += maxTimeseriesesPerRequest {
		end := i + maxTimeseriesesPerRequest
		if end > len(tss) {
			end = len(tss)
		}
		wg.Add(1)
		go func(begin int, end int) {
			defer wg.Done()
			req_copy := &monitoring.CreateTimeSeriesRequest{
				Name:       c.projectId,
				TimeSeries: req.TimeSeries[begin:end],
			}
			_, err := service.CreateTimeSeries(ctx, req_copy)
			if err != nil {
				level.Debug(c.logger).Log(
					"msg", "Partial failure calling CreateTimeSeries",
					"err", err,
					"req", req_copy.String())
				status, ok := status.FromError(err)
				if !ok {
					level.Warn(c.logger).Log("msg", "Unexpected error message type from Monitoring API", "err", err)
					errors <- err
					return
				}
				if status.Code() == codes.Unavailable {
					errors <- recoverableError{err}
				} else {
					errors <- err
				}
			}
		}(i, end)
	}
	wg.Wait()
	close(errors)
	if err, ok := <-errors; ok {
		return err
	}
	return nil
}

// Name identifies the client.
func (c Client) Name() string {
	return fmt.Sprintf("%d:%s", c.index, c.url)
}

func (c Client) Close() error {
	if c.conn == nil {
		return nil
	}
	return c.conn.Close()
}
<commit_msg>Set user-agent in Metrics API requests.<commit_after>// Copyright 2016 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 stackdriver

import (
	"context"
	"crypto/tls"
	"fmt"
	"strconv"
	"sync"
	"time"

	monitoring "google.golang.org/genproto/googleapis/monitoring/v3"
	"google.golang.org/grpc"
	"google.golang.org/grpc/balancer/roundrobin"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/credentials/oauth"
	"google.golang.org/grpc/status"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/prometheus/common/model"
	"github.com/prometheus/common/version"

	config_util "github.com/prometheus/common/config"
)

const (
	// TODO(jkohen): Use a custom prefix specific to Prometheus.
	metricsPrefix             = "custom.googleapis.com"
	maxTimeseriesesPerRequest = 200
	MonitoringWriteScope      = "https://www.googleapis.com/auth/monitoring.write"
)

// Client allows reading and writing from/to a remote gRPC endpoint. The
// implementation may hit a single backend, so the application should create a
// number of these clients.
type Client struct {
	index     int // Used to differentiate clients in metrics.
	logger    log.Logger
	projectId string
	url       *config_util.URL
	timeout   time.Duration

	conn *grpc.ClientConn
}

// ClientConfig configures a Client.
type ClientConfig struct {
	Logger    log.Logger
	ProjectId string // The Stackdriver project id in "projects/name-or-number" format.
	URL       *config_util.URL
	Timeout   model.Duration
}

// NewClient creates a new Client.
func NewClient(index int, conf *ClientConfig) *Client {
	logger := conf.Logger
	if logger == nil {
		logger = log.NewNopLogger()
	}
	return &Client{
		index:     index,
		logger:    logger,
		projectId: conf.ProjectId,
		url:       conf.URL,
		timeout:   time.Duration(conf.Timeout),
	}
}

type recoverableError struct {
	error
}

// version.* is populated for 'promu' builds, so this will look broken in unit tests.
var userAgent = fmt.Sprintf("StackdriverPrometheus/%s", version.Version)

func (c *Client) getConnection(ctx context.Context) (*grpc.ClientConn, error) {
	if c.conn != nil {
		return c.conn, nil
	}

	useAuth, err := strconv.ParseBool(c.url.Query().Get("auth"))
	if err != nil {
		useAuth = true // Default to auth enabled.
	}
	level.Debug(c.logger).Log(
		"msg", "is auth enabled",
		"auth", useAuth,
		"url", c.url.String())
	// Google APIs currently return a single IP for the whole service.  gRPC
	// client-side load-balancing won't spread the load across backends
	// while that's true, but it also doesn't hurt.
	dopts := []grpc.DialOption{
		grpc.WithBalancerName(roundrobin.Name),
		grpc.WithBlock(), // Wait for the connection to be established before using it.
		grpc.WithUserAgent(userAgent),
	}
	if useAuth {
		rpcCreds, err := oauth.NewApplicationDefault(context.Background(), MonitoringWriteScope)
		if err != nil {
			return nil, err
		}
		tlsCreds := credentials.NewTLS(&tls.Config{})
		dopts = append(dopts,
			grpc.WithTransportCredentials(tlsCreds),
			grpc.WithPerRPCCredentials(rpcCreds))
	} else {
		dopts = append(dopts, grpc.WithInsecure())
	}
	address := c.url.Hostname()
	if len(c.url.Port()) > 0 {
		address = fmt.Sprintf("%s:%s", address, c.url.Port())
	}
	conn, err := grpc.DialContext(ctx, address, dopts...)
	c.conn = conn
	return conn, err
}

// Store sends a batch of samples to the HTTP endpoint.
func (c *Client) Store(req *monitoring.CreateTimeSeriesRequest) error {
	tss := req.TimeSeries
	if len(tss) == 0 {
		// Nothing to do, return silently.
		return nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()

	conn, err := c.getConnection(ctx)
	if err != nil {
		return err
	}

	level.Debug(c.logger).Log("msg", "sending request to Stackdriver")
	service := monitoring.NewMetricServiceClient(conn)

	errors := make(chan error, len(tss)/maxTimeseriesesPerRequest+1)
	var wg sync.WaitGroup
	for i := 0; i < len(tss); i += maxTimeseriesesPerRequest {
		end := i + maxTimeseriesesPerRequest
		if end > len(tss) {
			end = len(tss)
		}
		wg.Add(1)
		go func(begin int, end int) {
			defer wg.Done()
			req_copy := &monitoring.CreateTimeSeriesRequest{
				Name:       c.projectId,
				TimeSeries: req.TimeSeries[begin:end],
			}
			_, err := service.CreateTimeSeries(ctx, req_copy)
			if err != nil {
				level.Debug(c.logger).Log(
					"msg", "Partial failure calling CreateTimeSeries",
					"err", err,
					"req", req_copy.String())
				status, ok := status.FromError(err)
				if !ok {
					level.Warn(c.logger).Log("msg", "Unexpected error message type from Monitoring API", "err", err)
					errors <- err
					return
				}
				if status.Code() == codes.Unavailable {
					errors <- recoverableError{err}
				} else {
					errors <- err
				}
			}
		}(i, end)
	}
	wg.Wait()
	close(errors)
	if err, ok := <-errors; ok {
		return err
	}
	return nil
}

// Name identifies the client.
func (c Client) Name() string {
	return fmt.Sprintf("%d:%s", c.index, c.url)
}

func (c Client) Close() error {
	if c.conn == nil {
		return nil
	}
	return c.conn.Close()
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/*
Script expects the following env variables:
 - UPDATE_TARGET=<string>: optional - if unset/absent, default option is "fs"; valid options are:
   - "fs"  - update only local filesystem repo files [default]
   - "gh"  - update only remote GitHub repo files and create PR (if one does not exist already)
   - "all" - update local and remote repo files and create PR (if one does not exist already)
 - GITHUB_TOKEN=<string>: GitHub [personal] access token
   - note: GITHUB_TOKEN is required if UPDATE_TARGET is "gh" or "all"
*/

package main

import (
	"context"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"k8s.io/klog/v2"

	"k8s.io/minikube/hack/update"
)

const (
	// default context timeout
	cxTimeout = 300 * time.Second
)

var (
	schema = map[string]update.Item{
		".github/workflows/build.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/master.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/pr.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/docs.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/time-to-k8s.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/translations.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/update_k8s_versions.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/pr_verified.yaml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".travis.yml": {
			Replace: map[string]string{
				`go:\n  - .*`: `go:{{printf "\n  - %s" .StableVersion}}`,
				`go: .*`:      `go: {{.StableVersion}}`,
			},
		},
		"go.mod": {
			Replace: map[string]string{
				`(?m)^go .*`: `go {{.StableVersionMM}}`,
			},
		},
		"hack/jenkins/common.sh": {
			Replace: map[string]string{
				`sudo \.\/installers\/check_install_golang\.sh \".*\" \"\/usr\/local\"`: `sudo ./installers/check_install_golang.sh "{{.StableVersion}}" "/usr/local"`,
			},
		},
		"Makefile": {
			Replace: map[string]string{
				`GO_VERSION \?= .*`: `GO_VERSION ?= {{.StableVersion}}`,
			},
		},
	}

	// PR data
	prBranchPrefix = "update-golang-version_" // will be appended with first 7 characters of the PR commit SHA
	prTitle        = `update_golang_version: {stable: "{{.StableVersion}}"}`
	prIssue        = 9264
)

// Data holds stable Golang version - in full and in <major>.<minor> format
type Data struct {
	StableVersion   string `json:"stableVersion"`
	StableVersionMM string `json:"stableVersionMM"` // go.mod wants go version in <major>.<minor> format
}

func main() {
	// set a context with defined timeout
	ctx, cancel := context.WithTimeout(context.Background(), cxTimeout)
	defer cancel()

	// get Golang stable version
	stable, stableMM, err := goVersions()
	if err != nil || stable == "" || stableMM == "" {
		klog.Fatalf("Unable to get Golang stable version: %v", err)
	}
	data := Data{StableVersion: stable, StableVersionMM: stableMM}
	klog.Infof("Golang stable version: %s", data.StableVersion)

	update.Apply(ctx, schema, data, prBranchPrefix, prTitle, prIssue)
}

// goVersion returns Golang stable version.
func goVersions() (stable, stableMM string, err error) {
	resp, err := http.Get("https://golang.org/VERSION?m=text")
	if err != nil {
		return "", "", err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", "", err
	}
	stable = strings.TrimPrefix(string(body), "go")
	mmp := strings.SplitN(stable, ".", 3)
	stableMM = strings.Join(mmp[0:2], ".") // <major>.<minor> version
	return stable, stableMM, nil
}
<commit_msg>Update golang version for leaderboard.yml.<commit_after>/*
Copyright 2020 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/*
Script expects the following env variables:
 - UPDATE_TARGET=<string>: optional - if unset/absent, default option is "fs"; valid options are:
   - "fs"  - update only local filesystem repo files [default]
   - "gh"  - update only remote GitHub repo files and create PR (if one does not exist already)
   - "all" - update local and remote repo files and create PR (if one does not exist already)
 - GITHUB_TOKEN=<string>: GitHub [personal] access token
   - note: GITHUB_TOKEN is required if UPDATE_TARGET is "gh" or "all"
*/

package main

import (
	"context"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"k8s.io/klog/v2"

	"k8s.io/minikube/hack/update"
)

const (
	// default context timeout
	cxTimeout = 300 * time.Second
)

var (
	schema = map[string]update.Item{
		".github/workflows/build.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/master.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/pr.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/docs.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/time-to-k8s.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/leaderboard.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/translations.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/update_k8s_versions.yml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".github/workflows/pr_verified.yaml": {
			Replace: map[string]string{
				`GO_VERSION: '.*`: `GO_VERSION: '{{.StableVersion}}'`,
			},
		},
		".travis.yml": {
			Replace: map[string]string{
				`go:\n  - .*`: `go:{{printf "\n  - %s" .StableVersion}}`,
				`go: .*`:      `go: {{.StableVersion}}`,
			},
		},
		"go.mod": {
			Replace: map[string]string{
				`(?m)^go .*`: `go {{.StableVersionMM}}`,
			},
		},
		"hack/jenkins/common.sh": {
			Replace: map[string]string{
				`sudo \.\/installers\/check_install_golang\.sh \".*\" \"\/usr\/local\"`: `sudo ./installers/check_install_golang.sh "{{.StableVersion}}" "/usr/local"`,
			},
		},
		"Makefile": {
			Replace: map[string]string{
				`GO_VERSION \?= .*`: `GO_VERSION ?= {{.StableVersion}}`,
			},
		},
	}

	// PR data
	prBranchPrefix = "update-golang-version_" // will be appended with first 7 characters of the PR commit SHA
	prTitle        = `update_golang_version: {stable: "{{.StableVersion}}"}`
	prIssue        = 9264
)

// Data holds stable Golang version - in full and in <major>.<minor> format
type Data struct {
	StableVersion   string `json:"stableVersion"`
	StableVersionMM string `json:"stableVersionMM"` // go.mod wants go version in <major>.<minor> format
}

func main() {
	// set a context with defined timeout
	ctx, cancel := context.WithTimeout(context.Background(), cxTimeout)
	defer cancel()

	// get Golang stable version
	stable, stableMM, err := goVersions()
	if err != nil || stable == "" || stableMM == "" {
		klog.Fatalf("Unable to get Golang stable version: %v", err)
	}
	data := Data{StableVersion: stable, StableVersionMM: stableMM}
	klog.Infof("Golang stable version: %s", data.StableVersion)

	update.Apply(ctx, schema, data, prBranchPrefix, prTitle, prIssue)
}

// goVersion returns Golang stable version.
func goVersions() (stable, stableMM string, err error) {
	resp, err := http.Get("https://golang.org/VERSION?m=text")
	if err != nil {
		return "", "", err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", "", err
	}
	stable = strings.TrimPrefix(string(body), "go")
	mmp := strings.SplitN(stable, ".", 3)
	stableMM = strings.Join(mmp[0:2], ".") // <major>.<minor> version
	return stable, stableMM, nil
}
<|endoftext|>