<commit_before>package nyb

import (
	"encoding/json"
	"fmt"
	"log"
	"sort"
	"sync"
	"time"

	"github.com/hako/durafmt"
	"github.com/ugjka/dumbirc"
)

//Settings for bot
type Settings struct {
	IrcNick    string
	IrcChans   []string
	IrcServer  string
	IrcTrigger string
	IrcUseTLS  bool
	Bot        *dumbirc.Connection
	LogChan    LogChan
	Stopper    chan bool
	Email      string
	Nominatim  string
	extra
}

type extra struct {
	zones           TZS
	last            TZ
	next            TZ
	nominatimResult NominatimResults
	//We close this when we get WELCOME msg on join in irc
	start chan bool
	//This is used to prevent sending ping before we
	//have response from previous ping (any activity on irc)
	//pingpong(pp) sends a signal to ping timer
	pp chan bool
	sync.Once
	sync.WaitGroup
}

//New creates new bot
func New(nick string, chans []string, trigger string, server string,
	tls bool, email string, nominatim string) *Settings {
	return &Settings{
		nick,
		chans,
		server,
		trigger,
		tls,
		dumbirc.New(nick, "nyebot", server, tls),
		newLogChan(),
		make(chan bool),
		email,
		nominatim,
		extra{
			start: make(chan bool),
			pp:    make(chan bool, 1),
		},
	}
}

var stFinished = "That's it, Year %d is here AoE"

//Start starts the bot
func (s *Settings) Start() {
	log.SetOutput(s.LogChan)
	log.Println("Starting the bot...")

	defer s.Wait()
	//
	//Set up irc
	//
	bot := s.Bot
	s.addCallbacks()
	s.addTriggers()

	s.Add(1)
	go s.ircControl()

	bot.Start()

	select {
	case <-s.start:
		log.Println("Got start...")
	case <-s.Stopper:
		return
	}
	if err := json.Unmarshal([]byte(Zones), &s.zones); err != nil {
		log.Fatal(err)
	}
	sort.Sort(sort.Reverse(s.zones))

	for {
		s.loopTimeZones()
		select {
		case <-s.Stopper:
			return
		default:
		}
		bot.PrivMsgBulk(s.IrcChans, fmt.Sprintf(stFinished, target.Year()))
		log.Println("All zones finished...")
		target = target.AddDate(1, 0, 0)
		log.Printf("Wrapping the target date around to %d\n", target.Year())
	}
}

//Stop stops the bot
func (s *Settings) Stop() {
	select {
	case <-s.Stopper:
		return
	default:
		close(s.Stopper)
	}
}

var reconnectInterval = time.Second * 30
var pingInterval = time.Minute * 1

func (s *Settings) ircControl() {
	bot := s.Bot
	defer s.Done()
	for {
		timer := time.NewTimer(pingInterval)
		select {
		case err := <-bot.Errchan:
			log.Println("Error:", err)
			log.Printf("Reconnecting to irc in %s...\n", reconnectInterval)
			time.AfterFunc(reconnectInterval, func() {
				select {
				case <-s.Stopper:
					return
				default:
					bot.Start()
				}
			})
		case <-s.Stopper:
			timer.Stop()
			log.Println("Stopping the bot...")
			log.Println("Disconnecting...")
			bot.Disconnect()
			return
		case <-timer.C:
			timer.Stop()
			//pingpong stuff
			select {
			case <-s.pp:
				log.Println("Sending PING...")
				bot.Ping()
			default:
				log.Println("Got no PONG...")
			}
		}

	}
}

var stNextNewYear = "Next New Year in %s in %s"
var stHappyNewYear = "Happy New Year in %s"

func (s *Settings) loopTimeZones() {
	zones := s.zones
	bot := s.Bot
	for i := 0; i < len(zones); i++ {
		dur, err := time.ParseDuration(zones[i].Offset + "h")
		if err != nil {
			log.Fatal(err)
		}
		s.next = zones[i]
		if i == 0 {
			s.last = zones[len(zones)-1]
		} else {
			s.last = zones[i-1]
		}
		if time.Now().UTC().Add(dur).Before(target) {
			time.Sleep(time.Second * 2)
			log.Println("Zone pending:", zones[i].Offset)
			humandur := durafmt.Parse(target.Sub(time.Now().UTC().Add(dur)))
			msg := fmt.Sprintf(stNextNewYear, removeMilliseconds(humandur), zones[i])
			bot.PrivMsgBulk(s.IrcChans, msg)
			//Wait till Target in Timezone
			timer := NewTimer(target.Sub(time.Now().UTC().Add(dur)))

			select {
			case <-timer.C:
				timer.Stop()
				msg = fmt.Sprintf(stHappyNewYear, zones[i])
				bot.PrivMsgBulk(s.IrcChans, msg)
				log.Println("Announcing zone:", zones[i].Offset)
			case <-s.Stopper:
				timer.Stop()
				return
			}
		}
	}
}
<commit_msg>move wait to goroutine<commit_after>package nyb

import (
	"encoding/json"
	"fmt"
	"log"
	"sort"
	"sync"
	"time"

	"github.com/hako/durafmt"
	"github.com/ugjka/dumbirc"
)

//Settings for bot
type Settings struct {
	IrcNick    string
	IrcChans   []string
	IrcServer  string
	IrcTrigger string
	IrcUseTLS  bool
	Bot        *dumbirc.Connection
	LogChan    LogChan
	Stopper    chan bool
	Email      string
	Nominatim  string
	extra
}

type extra struct {
	zones           TZS
	last            TZ
	next            TZ
	nominatimResult NominatimResults
	//We close this when we get WELCOME msg on join in irc
	start chan bool
	//This is used to prevent sending ping before we
	//have response from previous ping (any activity on irc)
	//pingpong(pp) sends a signal to ping timer
	pp chan bool
	sync.Once
	sync.WaitGroup
}

//New creates new bot
func New(nick string, chans []string, trigger string, server string,
	tls bool, email string, nominatim string) *Settings {
	return &Settings{
		nick,
		chans,
		server,
		trigger,
		tls,
		dumbirc.New(nick, "nyebot", server, tls),
		newLogChan(),
		make(chan bool),
		email,
		nominatim,
		extra{
			start: make(chan bool),
			pp:    make(chan bool, 1),
		},
	}
}

var stFinished = "That's it, Year %d is here AoE"

//Start starts the bot
func (s *Settings) Start() {
	log.SetOutput(s.LogChan)
	log.Println("Starting the bot...")

	defer s.Wait()
	//
	//Set up irc
	//
	bot := s.Bot
	s.addCallbacks()
	s.addTriggers()

	go s.ircControl()

	bot.Start()

	select {
	case <-s.start:
		log.Println("Got start...")
	case <-s.Stopper:
		return
	}
	if err := json.Unmarshal([]byte(Zones), &s.zones); err != nil {
		log.Fatal(err)
	}
	sort.Sort(sort.Reverse(s.zones))

	for {
		s.loopTimeZones()
		select {
		case <-s.Stopper:
			return
		default:
		}
		bot.PrivMsgBulk(s.IrcChans, fmt.Sprintf(stFinished, target.Year()))
		log.Println("All zones finished...")
		target = target.AddDate(1, 0, 0)
		log.Printf("Wrapping the target date around to %d\n", target.Year())
	}
}

//Stop stops the bot
func (s *Settings) Stop() {
	select {
	case <-s.Stopper:
		return
	default:
		close(s.Stopper)
	}
}

var reconnectInterval = time.Second * 30
var pingInterval = time.Minute * 1

func (s *Settings) ircControl() {
	s.Add(1)
	defer s.Done()
	bot := s.Bot
	for {
		timer := time.NewTimer(pingInterval)
		select {
		case err := <-bot.Errchan:
			log.Println("Error:", err)
			log.Printf("Reconnecting to irc in %s...\n", reconnectInterval)
			time.AfterFunc(reconnectInterval, func() {
				select {
				case <-s.Stopper:
					return
				default:
					bot.Start()
				}
			})
		case <-s.Stopper:
			timer.Stop()
			log.Println("Stopping the bot...")
			log.Println("Disconnecting...")
			bot.Disconnect()
			return
		case <-timer.C:
			timer.Stop()
			//pingpong stuff
			select {
			case <-s.pp:
				log.Println("Sending PING...")
				bot.Ping()
			default:
				log.Println("Got no PONG...")
			}
		}

	}
}

var stNextNewYear = "Next New Year in %s in %s"
var stHappyNewYear = "Happy New Year in %s"

func (s *Settings) loopTimeZones() {
	zones := s.zones
	bot := s.Bot
	for i := 0; i < len(zones); i++ {
		dur, err := time.ParseDuration(zones[i].Offset + "h")
		if err != nil {
			log.Fatal(err)
		}
		s.next = zones[i]
		if i == 0 {
			s.last = zones[len(zones)-1]
		} else {
			s.last = zones[i-1]
		}
		if time.Now().UTC().Add(dur).Before(target) {
			time.Sleep(time.Second * 2)
			log.Println("Zone pending:", zones[i].Offset)
			humandur := durafmt.Parse(target.Sub(time.Now().UTC().Add(dur)))
			msg := fmt.Sprintf(stNextNewYear, removeMilliseconds(humandur), zones[i])
			bot.PrivMsgBulk(s.IrcChans, msg)
			//Wait till Target in Timezone
			timer := NewTimer(target.Sub(time.Now().UTC().Add(dur)))

			select {
			case <-timer.C:
				timer.Stop()
				msg = fmt.Sprintf(stHappyNewYear, zones[i])
				bot.PrivMsgBulk(s.IrcChans, msg)
				log.Println("Announcing zone:", zones[i].Offset)
			case <-s.Stopper:
				timer.Stop()
				return
			}
		}
	}
}
<|endoftext|>


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

package profiles

import (
	"archive/zip"
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"v.io/jiri/jiri"
	"v.io/jiri/runutil"
	"v.io/jiri/tool"
)

const (
	DefaultDirPerm  = os.FileMode(0755)
	DefaultFilePerm = os.FileMode(0644)
	targetDefValue  = "<runtime.GOARCH>-<runtime.GOOS>"
)

// RegisterTargetFlag registers the commonly used --target flag with
// the supplied FlagSet.
func RegisterTargetFlag(flags *flag.FlagSet, target *Target) {
	*target = DefaultTarget()
	flags.Var(target, "target", target.Usage())
	flags.Lookup("target").DefValue = targetDefValue
}

// RegisterTargetAndEnvFlags registers the commonly used --target and --env
// flags with the supplied FlagSet
func RegisterTargetAndEnvFlags(flags *flag.FlagSet, target *Target) {
	*target = DefaultTarget()
	flags.Var(target, "target", target.Usage())
	flags.Lookup("target").DefValue = targetDefValue
	flags.Var(&target.commandLineEnv, "env", target.commandLineEnv.Usage())
}

// RegisterManifestFlag registers the commonly used --profiles-manifest
// flag with the supplied FlagSet.
func RegisterManifestFlag(flags *flag.FlagSet, manifest *string, defaultManifest string) {
	root := jiri.FindRoot()
	flags.StringVar(manifest, "profiles-manifest", filepath.Join(root, defaultManifest), "specify the profiles XML manifest filename.")
	flags.Lookup("profiles-manifest").DefValue = filepath.Join("$JIRI_ROOT", defaultManifest)
}

// RegisterProfileFlags registers the commonly used --profiles-manifest, --profiles,
// --target and --merge-policies flags with the supplied FlagSet.
func RegisterProfileFlags(flags *flag.FlagSet, profilesMode *ProfilesMode, manifest, profiles *string, defaultManifest string, policies *MergePolicies, target *Target) {
	flags.Var(profilesMode, "skip-profiles", "if set, no profiles will be used")
	RegisterProfilesFlag(flags, profiles)
	RegisterMergePoliciesFlag(flags, policies)
	RegisterManifestFlag(flags, manifest, defaultManifest)
	RegisterTargetFlag(flags, target)
}

// RegisterProfilesFlag registers the --profiles flag
func RegisterProfilesFlag(flags *flag.FlagSet, profiles *string) {
	flags.StringVar(profiles, "profiles", "base,jiri", "a comma separated list of profiles to use")
}

// RegisterMergePoliciesFlag registers the --merge-policies flag
func RegisterMergePoliciesFlag(flags *flag.FlagSet, policies *MergePolicies) {
	flags.Var(policies, "merge-policies", "specify policies for merging environment variables")
}

type AppendJiriProfileMode bool

const (
	AppendJiriProfile      AppendJiriProfileMode = true
	DoNotAppendJiriProfile                       = false
)

// InitProfilesFromFlag splits a comma separated list of profile names into
// a slice and optionally appends the 'jiri' profile if it's not already
// present.
func InitProfilesFromFlag(flag string, appendJiriProfile AppendJiriProfileMode) []string {
	n := strings.Split(flag, ",")
	if appendJiriProfile == AppendJiriProfile && !strings.Contains(flag, "jiri") {
		n = append(n, "jiri")
	}
	return n
}

// AtomicAction performs an action 'atomically' by keeping track of successfully
// completed actions in the supplied completion log and re-running them if they
// are not successfully logged therein after deleting the entire contents of the
// dir parameter. Consequently it does not make sense to apply AtomicAction to
// the same directory in sequence.
func AtomicAction(jirix *jiri.X, installFn func() error, dir, message string) error {
	atomicFn := func() error {
		completionLogPath := filepath.Join(dir, ".complete")
		s := jirix.NewSeq()
		if dir != "" {
			if exists, _ := s.DirectoryExists(dir); exists {
				// If the dir exists but the completionLogPath doesn't, then it
				// means the previous action didn't finish.
				// Remove the dir so we can perform the action again.
				if exists, _ := s.FileExists(completionLogPath); !exists {
					s.RemoveAll(dir).Done()
				} else {
					if jirix.Verbose() {
						fmt.Fprintf(jirix.Stdout(), "AtomicAction: %s already completed in %s\n", message, dir)
					}
					return nil
				}
			}
		}
		if err := installFn(); err != nil {
			if dir != "" {
				s.RemoveAll(dir).Done()
			}
			return err
		}
		return s.WriteFile(completionLogPath, []byte("completed"), DefaultFilePerm).Done()
	}
	return jirix.NewSeq().Call(atomicFn, message).Done()
}

func brewList(jirix *jiri.X) (map[string]bool, error) {
	var out bytes.Buffer
	err := jirix.NewSeq().Capture(&out, &out).Last("brew", "list")
	if err != nil || tool.VerboseFlag {
		fmt.Fprintf(jirix.Stdout(), "%s", out.String())
	}
	scanner := bufio.NewScanner(&out)
	pkgs := map[string]bool{}
	for scanner.Scan() {
		pkgs[scanner.Text()] = true
	}
	return pkgs, err
}

// InstallPackages identifies the packages that need to be installed
// and installs them using the OS-specific package manager.
func InstallPackages(jirix *jiri.X, pkgs []string) error {
	installDepsFn := func() error {
		s := jirix.NewSeq()
		switch runtime.GOOS {
		case "linux":
			if runutil.IsFNLHost() {
				fmt.Fprintf(jirix.Stdout(), "skipping installation of %v on FNL host", pkgs)
				fmt.Fprintf(jirix.Stdout(), "success\n")
				break
			}
			installPkgs := []string{}
			for _, pkg := range pkgs {
				if err := s.Last("dpkg", "-L", pkg); err != nil {
					installPkgs = append(installPkgs, pkg)
				}
			}
			if len(installPkgs) > 0 {
				args := append([]string{"apt-get", "install", "-y"}, installPkgs...)
				fmt.Fprintf(jirix.Stdout(), "Running: sudo %s: ", strings.Join(args, " "))
				if err := s.Last("sudo", args...); err != nil {
					fmt.Fprintf(jirix.Stdout(), "%v\n", err)
					return err
				}
				fmt.Fprintf(jirix.Stdout(), "success\n")
			}
		case "darwin":
			installPkgs := []string{}
			installedPkgs, err := brewList(jirix)
			if err != nil {
				return err
			}
			for _, pkg := range pkgs {
				if !installedPkgs[pkg] {
					installPkgs = append(installPkgs, pkg)
				}
			}
			if len(installPkgs) > 0 {
				args := append([]string{"install"}, installPkgs...)
				fmt.Fprintf(jirix.Stdout(), "Running: brew %s: ", strings.Join(args, " "))
				if err := s.Last("brew", args...); err != nil {
					fmt.Fprintf(jirix.Stdout(), "%v\n", err)
					return err
				}
				fmt.Fprintf(jirix.Stdout(), "success\n")
			}
		}
		return nil
	}
	return jirix.NewSeq().Call(installDepsFn, "Install dependencies").Done()
}

// ensureAction ensures that the requested profile and target
// is installed/uninstalled, installing/uninstalling it if only if necessary.
func ensureAction(jirix *jiri.X, action Action, profile string, root RelativePath, target Target) error {
	verb := ""
	switch action {
	case Install:
		verb = "install"
	case Uninstall:
		verb = "uninstall"
	default:
		return fmt.Errorf("unrecognised action %v", action)
	}
	if t := LookupProfileTarget(profile, target); t != nil {
		if jirix.Verbose() {
			fmt.Fprintf(jirix.Stdout(), "%v %v is already %sed as %v\n", profile, target, verb, t)
		}
		return nil
	}
	mgr := LookupManager(profile)
	if mgr == nil {
		return fmt.Errorf("profile %v is not supported", profile)
	}
	version, err := mgr.VersionInfo().Select(target.Version())
	if err != nil {
		return err
	}
	target.SetVersion(version)
	if jirix.Verbose() || jirix.DryRun() {
		fmt.Fprintf(jirix.Stdout(), "%s %s %s\n", verb, profile, target.DebugString())
	}
	if action == Install {
		return mgr.Install(jirix, root, target)
	}
	return mgr.Uninstall(jirix, root, target)
}

// EnsureProfileTargetIsInstalled ensures that the requested profile and target
// is installed, installing it if only if necessary.
func EnsureProfileTargetIsInstalled(jirix *jiri.X, profile string, root RelativePath, target Target) error {
	return ensureAction(jirix, Install, profile, root, target)
}

// EnsureProfileTargetIsUninstalled ensures that the requested profile and target
// are no longer installed.
func EnsureProfileTargetIsUninstalled(jirix *jiri.X, profile string, root RelativePath, target Target) error {
	return ensureAction(jirix, Uninstall, profile, root, target)
}

// Fetch downloads the specified url and saves it to dst.
// TODO(nlacasse, cnicoloau): Move this to a package for profile-implementors
// so it does not pollute the profile package namespace.
func Fetch(jirix *jiri.X, dst, url string) error {
	s := jirix.NewSeq()
	s.Output([]string{"fetching " + url})
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("got non-200 status code while getting %v: %v", url, resp.StatusCode)
	}
	file, err := s.Create(dst)
	if err != nil {
		return err
	}
	_, err = s.Copy(file, resp.Body)
	return err
}

// Unzip unzips the file in srcFile and puts resulting files in directory dstDir.
// TODO(nlacasse, cnicoloau): Move this to a package for profile-implementors
// so it does not pollute the profile package namespace.
func Unzip(jirix *jiri.X, srcFile, dstDir string) error {
	r, err := zip.OpenReader(srcFile)
	if err != nil {
		return err
	}
	defer r.Close()

	unzipFn := func(zFile *zip.File) error {
		rc, err := zFile.Open()
		if err != nil {
			return err
		}
		defer rc.Close()

		s := jirix.NewSeq()
		fileDst := filepath.Join(dstDir, zFile.Name)
		if zFile.FileInfo().IsDir() {
			return s.MkdirAll(fileDst, zFile.Mode()).Done()
		}

		// Make sure the parent directory exists.  Note that sometimes files
		// can appear in a zip file before their directory.
		if err := s.MkdirAll(filepath.Dir(fileDst), zFile.Mode()).Done(); err != nil {
			return err
		}

		file, err := s.OpenFile(fileDst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, zFile.Mode())
		if err != nil {
			return err
		}
		defer file.Close()
		_, err = s.Copy(file, rc)
		return err
	}
	s := jirix.NewSeq()
	s.Output([]string{"unzipping " + srcFile})
	for _, zFile := range r.File {
		s.Output([]string{"extracting " + zFile.Name})
		s.Call(func() error { return unzipFn(zFile) }, "unzipFn(%s)", zFile.Name)
	}
	return s.Done()
}
<commit_msg>TBR: v.io/jiri: add additional error info<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package profiles

import (
	"archive/zip"
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"v.io/jiri/jiri"
	"v.io/jiri/runutil"
	"v.io/jiri/tool"
)

const (
	DefaultDirPerm  = os.FileMode(0755)
	DefaultFilePerm = os.FileMode(0644)
	targetDefValue  = "<runtime.GOARCH>-<runtime.GOOS>"
)

// RegisterTargetFlag registers the commonly used --target flag with
// the supplied FlagSet.
func RegisterTargetFlag(flags *flag.FlagSet, target *Target) {
	*target = DefaultTarget()
	flags.Var(target, "target", target.Usage())
	flags.Lookup("target").DefValue = targetDefValue
}

// RegisterTargetAndEnvFlags registers the commonly used --target and --env
// flags with the supplied FlagSet
func RegisterTargetAndEnvFlags(flags *flag.FlagSet, target *Target) {
	*target = DefaultTarget()
	flags.Var(target, "target", target.Usage())
	flags.Lookup("target").DefValue = targetDefValue
	flags.Var(&target.commandLineEnv, "env", target.commandLineEnv.Usage())
}

// RegisterManifestFlag registers the commonly used --profiles-manifest
// flag with the supplied FlagSet.
func RegisterManifestFlag(flags *flag.FlagSet, manifest *string, defaultManifest string) {
	root := jiri.FindRoot()
	flags.StringVar(manifest, "profiles-manifest", filepath.Join(root, defaultManifest), "specify the profiles XML manifest filename.")
	flags.Lookup("profiles-manifest").DefValue = filepath.Join("$JIRI_ROOT", defaultManifest)
}

// RegisterProfileFlags registers the commonly used --profiles-manifest, --profiles,
// --target and --merge-policies flags with the supplied FlagSet.
func RegisterProfileFlags(flags *flag.FlagSet, profilesMode *ProfilesMode, manifest, profiles *string, defaultManifest string, policies *MergePolicies, target *Target) {
	flags.Var(profilesMode, "skip-profiles", "if set, no profiles will be used")
	RegisterProfilesFlag(flags, profiles)
	RegisterMergePoliciesFlag(flags, policies)
	RegisterManifestFlag(flags, manifest, defaultManifest)
	RegisterTargetFlag(flags, target)
}

// RegisterProfilesFlag registers the --profiles flag
func RegisterProfilesFlag(flags *flag.FlagSet, profiles *string) {
	flags.StringVar(profiles, "profiles", "base,jiri", "a comma separated list of profiles to use")
}

// RegisterMergePoliciesFlag registers the --merge-policies flag
func RegisterMergePoliciesFlag(flags *flag.FlagSet, policies *MergePolicies) {
	flags.Var(policies, "merge-policies", "specify policies for merging environment variables")
}

type AppendJiriProfileMode bool

const (
	AppendJiriProfile      AppendJiriProfileMode = true
	DoNotAppendJiriProfile                       = false
)

// InitProfilesFromFlag splits a comma separated list of profile names into
// a slice and optionally appends the 'jiri' profile if it's not already
// present.
func InitProfilesFromFlag(flag string, appendJiriProfile AppendJiriProfileMode) []string {
	n := strings.Split(flag, ",")
	if appendJiriProfile == AppendJiriProfile && !strings.Contains(flag, "jiri") {
		n = append(n, "jiri")
	}
	return n
}

// AtomicAction performs an action 'atomically' by keeping track of successfully
// completed actions in the supplied completion log and re-running them if they
// are not successfully logged therein after deleting the entire contents of the
// dir parameter. Consequently it does not make sense to apply AtomicAction to
// the same directory in sequence.
func AtomicAction(jirix *jiri.X, installFn func() error, dir, message string) error {
	atomicFn := func() error {
		completionLogPath := filepath.Join(dir, ".complete")
		s := jirix.NewSeq()
		if dir != "" {
			if exists, _ := s.DirectoryExists(dir); exists {
				// If the dir exists but the completionLogPath doesn't, then it
				// means the previous action didn't finish.
				// Remove the dir so we can perform the action again.
				if exists, _ := s.FileExists(completionLogPath); !exists {
					s.RemoveAll(dir).Done()
				} else {
					if jirix.Verbose() {
						fmt.Fprintf(jirix.Stdout(), "AtomicAction: %s already completed in %s\n", message, dir)
					}
					return nil
				}
			}
		}
		if err := installFn(); err != nil {
			if dir != "" {
				s.RemoveAll(dir).Done()
			}
			return err
		}
		return s.WriteFile(completionLogPath, []byte("completed"), DefaultFilePerm).Done()
	}
	return jirix.NewSeq().Call(atomicFn, message).Done()
}

func brewList(jirix *jiri.X) (map[string]bool, error) {
	var out bytes.Buffer
	err := jirix.NewSeq().Capture(&out, &out).Last("brew", "list")
	if err != nil || tool.VerboseFlag {
		fmt.Fprintf(jirix.Stdout(), "%s", out.String())
	}
	scanner := bufio.NewScanner(&out)
	pkgs := map[string]bool{}
	for scanner.Scan() {
		pkgs[scanner.Text()] = true
	}
	return pkgs, err
}

// InstallPackages identifies the packages that need to be installed
// and installs them using the OS-specific package manager.
func InstallPackages(jirix *jiri.X, pkgs []string) error {
	installDepsFn := func() error {
		s := jirix.NewSeq()
		switch runtime.GOOS {
		case "linux":
			if runutil.IsFNLHost() {
				fmt.Fprintf(jirix.Stdout(), "skipping installation of %v on FNL host", pkgs)
				fmt.Fprintf(jirix.Stdout(), "success\n")
				break
			}
			installPkgs := []string{}
			for _, pkg := range pkgs {
				if err := s.Last("dpkg", "-L", pkg); err != nil {
					installPkgs = append(installPkgs, pkg)
				}
			}
			if len(installPkgs) > 0 {
				args := append([]string{"apt-get", "install", "-y"}, installPkgs...)
				fmt.Fprintf(jirix.Stdout(), "Running: sudo %s: ", strings.Join(args, " "))
				if err := s.Last("sudo", args...); err != nil {
					fmt.Fprintf(jirix.Stdout(), "%v\n", err)
					return err
				}
				fmt.Fprintf(jirix.Stdout(), "success\n")
			}
		case "darwin":
			installPkgs := []string{}
			installedPkgs, err := brewList(jirix)
			if err != nil {
				return err
			}
			for _, pkg := range pkgs {
				if !installedPkgs[pkg] {
					installPkgs = append(installPkgs, pkg)
				}
			}
			if len(installPkgs) > 0 {
				args := append([]string{"install"}, installPkgs...)
				fmt.Fprintf(jirix.Stdout(), "Running: brew %s: ", strings.Join(args, " "))
				if err := s.Last("brew", args...); err != nil {
					fmt.Fprintf(jirix.Stdout(), "%v\n", err)
					return err
				}
				fmt.Fprintf(jirix.Stdout(), "success\n")
			}
		}
		return nil
	}
	return jirix.NewSeq().Call(installDepsFn, "Install dependencies: "+strings.Join(pkgs, ",")).Done()
}

// ensureAction ensures that the requested profile and target
// is installed/uninstalled, installing/uninstalling it if and only if necessary.
func ensureAction(jirix *jiri.X, action Action, profile string, root RelativePath, target Target) error {
	verb := ""
	switch action {
	case Install:
		verb = "install"
	case Uninstall:
		verb = "uninstall"
	default:
		return fmt.Errorf("unrecognised action %v", action)
	}
	if jirix.Verbose() || jirix.DryRun() {
		fmt.Fprintf(jirix.Stdout(), "%s %v %s\n", verb, action, target)
	}
	if t := LookupProfileTarget(profile, target); t != nil {
		if jirix.Verbose() {
			fmt.Fprintf(jirix.Stdout(), "%v %v is already %sed as %v\n", profile, target, verb, t)
		}
		return nil
	}
	mgr := LookupManager(profile)
	if mgr == nil {
		return fmt.Errorf("profile %v is not supported", profile)
	}
	version, err := mgr.VersionInfo().Select(target.Version())
	if err != nil {
		return err
	}
	target.SetVersion(version)
	if jirix.Verbose() || jirix.DryRun() {
		fmt.Fprintf(jirix.Stdout(), "%s %s %s\n", verb, profile, target.DebugString())
	}
	if action == Install {
		return mgr.Install(jirix, root, target)
	}
	return mgr.Uninstall(jirix, root, target)
}

// EnsureProfileTargetIsInstalled ensures that the requested profile and target
// is installed, installing it if only if necessary.
func EnsureProfileTargetIsInstalled(jirix *jiri.X, profile string, root RelativePath, target Target) error {
	return ensureAction(jirix, Install, profile, root, target)
}

// EnsureProfileTargetIsUninstalled ensures that the requested profile and target
// are no longer installed.
func EnsureProfileTargetIsUninstalled(jirix *jiri.X, profile string, root RelativePath, target Target) error {
	return ensureAction(jirix, Uninstall, profile, root, target)
}

// Fetch downloads the specified url and saves it to dst.
// TODO(nlacasse, cnicoloau): Move this to a package for profile-implementors
// so it does not pollute the profile package namespace.
func Fetch(jirix *jiri.X, dst, url string) error {
	s := jirix.NewSeq()
	s.Output([]string{"fetching " + url})
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("got non-200 status code while getting %v: %v", url, resp.StatusCode)
	}
	file, err := s.Create(dst)
	if err != nil {
		return err
	}
	_, err = s.Copy(file, resp.Body)
	return err
}

// Unzip unzips the file in srcFile and puts resulting files in directory dstDir.
// TODO(nlacasse, cnicoloau): Move this to a package for profile-implementors
// so it does not pollute the profile package namespace.
func Unzip(jirix *jiri.X, srcFile, dstDir string) error {
	r, err := zip.OpenReader(srcFile)
	if err != nil {
		return err
	}
	defer r.Close()

	unzipFn := func(zFile *zip.File) error {
		rc, err := zFile.Open()
		if err != nil {
			return err
		}
		defer rc.Close()

		s := jirix.NewSeq()
		fileDst := filepath.Join(dstDir, zFile.Name)
		if zFile.FileInfo().IsDir() {
			return s.MkdirAll(fileDst, zFile.Mode()).Done()
		}

		// Make sure the parent directory exists.  Note that sometimes files
		// can appear in a zip file before their directory.
		if err := s.MkdirAll(filepath.Dir(fileDst), zFile.Mode()).Done(); err != nil {
			return err
		}

		file, err := s.OpenFile(fileDst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, zFile.Mode())
		if err != nil {
			return err
		}
		defer file.Close()
		_, err = s.Copy(file, rc)
		return err
	}
	s := jirix.NewSeq()
	s.Output([]string{"unzipping " + srcFile})
	for _, zFile := range r.File {
		s.Output([]string{"extracting " + zFile.Name})
		s.Call(func() error { return unzipFn(zFile) }, "unzipFn(%s)", zFile.Name)
	}
	return s.Done()
}
<|endoftext|>


<commit_before>// Title：表格，及其下属控件
//
// Description:
//
// Author:black
//
// Createtime:2013-08-08 09:29
//
// Version:1.0
//
// 修改历史:版本号 修改日期 修改人 修改说明
//
// 1.0 2013-08-08 09:29 black 创建文档
package lessgo

import (
	"bytes"
	"text/template"
)

type gridPanel struct {
	Entity       string       `xml:"entity,attr"`
	PageSize     int          `xml:"pageSize,attr"`
	LoadUrl      string       `xml:"loadUrl,attr"`
	Id           string       `xml:"id,attr"`
	PageId       string       `xml:"pageId,attr"`
	Title        string       `xml:"title,attr"`
	Width        string       `xml:"width,attr"`
	Height       string       `xml:"height,attr"`
	MutiSelect   string       `xml:"mutiSelect,attr"`
	Columns      []column     `xml:"column"`
	Actions      []action     `xml:"action"`
	Searchs      []search     `xml:"search"`
	Checkboxtool checkboxtool `xml:"checkboxtool"`
	ToolActions  []toolaction `xml:"toolaction"`
	BeforeRender string       `xml:"beforeRender"`
	AfterRender  string       `xml:"afterRender"`
	AfterLoad  string          `xml:"afterLoad"`
	CustomSearch string       `xml:"customSearch"`
	ActionWidth  string       `xml:"actionWidth,attr"`
}

//link目前可以支持，直接跳转，打开浏览器新窗口跳转，iframe弹窗，询问提示窗
//linkType=currentPage，
//以下为通用配置
//url 必填
//iconUrl 选填 如果有配置iconUrl，则会生成一个可点击的图标
//loadParamName 选填，不填就不带参数
//loadParamValue 如果loadParamName有值，则此配置必填，可取值为id 或者 this
type column struct {
	Field     string `xml:"field,attr"`
	Desc      string `xml:"desc,attr"`
	Width     string `xml:"width,attr"`
	Hidden    string `xml:"hidden,attr"`
	LoadUrl   string `xml:"loadUrl,attr"`
	Formatter string `xml:"formatter"`
	align 	  string `xml:"align,attr"`
}

type action struct {
	Desc         string `xml:"desc,attr"`
	Url          string `xml:"url,attr"`
	ActionParams string `xml:"actionParams,attr"`
	LinkType     string `xml:"linkType,attr"`
	ConfirmMsg   string `xml:"confirmMsg,attr"`
}

type toolaction struct {
	Desc       string `xml:"desc,attr"`
	Url        string `xml:"url,attr"`
	LinkType   string `xml:"linkType,attr"`
	ColorClass string `xml:"colorClass,attr"`
	LoadUrl    string `xml:"loadUrl,attr"`

	//for mutiSelect
	ConfirmMsg string `xml:"confirmMsg,attr"`
	Params     string `xml:"params,attr"`
	Callback   string `xml:"callback"`
	Roles	   string `xml:"roles,attr"`

	//for addToCheckBox
	CheckboxDesc string `xml:"checkboxDesc,attr"`
}

type checkboxtool struct {
	Desc     string `xml:"desc,attr"`
	LoadUrl  string `xml:"loadUrl,attr"`
	SaveUrl  string `xml:"saveUrl,attr"`
	HideSave string `xml:"hideSave,attr"`
}

type search struct {
	Field      string `xml:"field,attr"`
	SearchType string `xml:"searchType,attr"`
	InputType  string `xml:"inputType,attr"`
	LocalData  string `xml:"localData,attr"`
	Desc       string `xml:"desc,attr"`
	Url        string `xml:"url,attr"`
	ValueField string `xml:"valueField,attr"`
	DescField  string `xml:"descField,attr"`
	//存储实际的搜索值
	Value  string
	Char14 string `xml:"char14,attr"` //for 时间戳控件
	Char8  string `xml:"char8,attr"`  //for 时间日控件

	ParentSelect string `xml:"parentSelect,attr"` //for remoteSelect
	Params       string `xml:"params,attr"`       //for remoteSelect
}

func (gridpanel gridPanel) generate(entity Entity, terminal, packageName string, employee Employee) []byte {

	var t *template.Template

	var buf bytes.Buffer

	gridpanel.Id = packageName + "." + gridpanel.Id

	runtimeComponentContain[gridpanel.Id] = gridpanel

	t = template.New("gridpanel.html")

	t = t.Funcs(template.FuncMap{
		"getComponentId": getComponentId,
		"compareInt":     CompareInt,
		"compareString":  CompareString,
	})

	t, err := t.ParseFiles("../lessgo/template/component/" + terminal + "/gridpanel.html")

	if err != nil {
		Log.Error(err.Error())
		return []byte{}
	}

	data := make(map[string]interface{})

	data["Gridpanel"] = gridpanel
	data["Entity"] = entity
	data["Terminal"] = terminal
	data["SearchLength"] = len(gridpanel.Searchs)
	data["ActionLength"] = len(gridpanel.Actions)
	data["Employee"] = employee

	err = t.Execute(&buf, data)

	if err != nil {
		Log.Error(err.Error())
		return []byte{}
	}

	return buf.Bytes()

}
<commit_msg>表格支持对齐方式<commit_after>// Title：表格，及其下属控件
//
// Description:
//
// Author:black
//
// Createtime:2013-08-08 09:29
//
// Version:1.0
//
// 修改历史:版本号 修改日期 修改人 修改说明
//
// 1.0 2013-08-08 09:29 black 创建文档
package lessgo

import (
	"bytes"
	"text/template"
)

type gridPanel struct {
	Entity       string       `xml:"entity,attr"`
	PageSize     int          `xml:"pageSize,attr"`
	LoadUrl      string       `xml:"loadUrl,attr"`
	Id           string       `xml:"id,attr"`
	PageId       string       `xml:"pageId,attr"`
	Title        string       `xml:"title,attr"`
	Width        string       `xml:"width,attr"`
	Height       string       `xml:"height,attr"`
	MutiSelect   string       `xml:"mutiSelect,attr"`
	Columns      []column     `xml:"column"`
	Actions      []action     `xml:"action"`
	Searchs      []search     `xml:"search"`
	Checkboxtool checkboxtool `xml:"checkboxtool"`
	ToolActions  []toolaction `xml:"toolaction"`
	BeforeRender string       `xml:"beforeRender"`
	AfterRender  string       `xml:"afterRender"`
	AfterLoad  string          `xml:"afterLoad"`
	CustomSearch string       `xml:"customSearch"`
	ActionWidth  string       `xml:"actionWidth,attr"`
}

//link目前可以支持，直接跳转，打开浏览器新窗口跳转，iframe弹窗，询问提示窗
//linkType=currentPage，
//以下为通用配置
//url 必填
//iconUrl 选填 如果有配置iconUrl，则会生成一个可点击的图标
//loadParamName 选填，不填就不带参数
//loadParamValue 如果loadParamName有值，则此配置必填，可取值为id 或者 this
type column struct {
	Field     string `xml:"field,attr"`
	Desc      string `xml:"desc,attr"`
	Width     string `xml:"width,attr"`
	Hidden    string `xml:"hidden,attr"`
	LoadUrl   string `xml:"loadUrl,attr"`
	Formatter string `xml:"formatter"`
	Align 	  string `xml:"align,attr"`
}

type action struct {
	Desc         string `xml:"desc,attr"`
	Url          string `xml:"url,attr"`
	ActionParams string `xml:"actionParams,attr"`
	LinkType     string `xml:"linkType,attr"`
	ConfirmMsg   string `xml:"confirmMsg,attr"`
}

type toolaction struct {
	Desc       string `xml:"desc,attr"`
	Url        string `xml:"url,attr"`
	LinkType   string `xml:"linkType,attr"`
	ColorClass string `xml:"colorClass,attr"`
	LoadUrl    string `xml:"loadUrl,attr"`

	//for mutiSelect
	ConfirmMsg string `xml:"confirmMsg,attr"`
	Params     string `xml:"params,attr"`
	Callback   string `xml:"callback"`
	Roles	   string `xml:"roles,attr"`

	//for addToCheckBox
	CheckboxDesc string `xml:"checkboxDesc,attr"`
}

type checkboxtool struct {
	Desc     string `xml:"desc,attr"`
	LoadUrl  string `xml:"loadUrl,attr"`
	SaveUrl  string `xml:"saveUrl,attr"`
	HideSave string `xml:"hideSave,attr"`
}

type search struct {
	Field      string `xml:"field,attr"`
	SearchType string `xml:"searchType,attr"`
	InputType  string `xml:"inputType,attr"`
	LocalData  string `xml:"localData,attr"`
	Desc       string `xml:"desc,attr"`
	Url        string `xml:"url,attr"`
	ValueField string `xml:"valueField,attr"`
	DescField  string `xml:"descField,attr"`
	//存储实际的搜索值
	Value  string
	Char14 string `xml:"char14,attr"` //for 时间戳控件
	Char8  string `xml:"char8,attr"`  //for 时间日控件

	ParentSelect string `xml:"parentSelect,attr"` //for remoteSelect
	Params       string `xml:"params,attr"`       //for remoteSelect
}

func (gridpanel gridPanel) generate(entity Entity, terminal, packageName string, employee Employee) []byte {

	var t *template.Template

	var buf bytes.Buffer

	gridpanel.Id = packageName + "." + gridpanel.Id

	runtimeComponentContain[gridpanel.Id] = gridpanel

	t = template.New("gridpanel.html")

	t = t.Funcs(template.FuncMap{
		"getComponentId": getComponentId,
		"compareInt":     CompareInt,
		"compareString":  CompareString,
	})

	t, err := t.ParseFiles("../lessgo/template/component/" + terminal + "/gridpanel.html")

	if err != nil {
		Log.Error(err.Error())
		return []byte{}
	}

	data := make(map[string]interface{})

	data["Gridpanel"] = gridpanel
	data["Entity"] = entity
	data["Terminal"] = terminal
	data["SearchLength"] = len(gridpanel.Searchs)
	data["ActionLength"] = len(gridpanel.Actions)
	data["Employee"] = employee

	err = t.Execute(&buf, data)

	if err != nil {
		Log.Error(err.Error())
		return []byte{}
	}

	return buf.Bytes()

}
<|endoftext|>


<commit_before>/*

package id implements a MongoDB ObjectId like object.

Cursor stored the timestamp and a hash.

	(<timestamp encoded in big endian uint32> 4 bytes) + (5 random bytes / 3 bytes counter starting with random value) = 12 bytes

	24 bytes hex-encoded
*/
package id

import (
	"crypto/rand"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"sync"
)

var mutex = &sync.Mutex{}
var counter = []byte("")

func init() {
	counter = make([]byte, 3)
	if _, err := rand.Read(counter); err != nil {
		panic(err)
	}
}

func nextCounter() []byte {
	mutex.Lock()
	i := len(counter)
	for i > 0 {
		i--
		counter[i]++
		if counter[i] != 0 {
			break
		}
	}
	mutex.Unlock()
	out := make([]byte, 3)
	copy(out[:], counter[:])
	return out

}

// Cursor hold a hex/byte representation of a timestamp and a hash
type ID struct {
	data []byte
	hash string // The hash is not part of the ID but it can be attached to the ID
}

func New(ts int) (*ID, error) {
	b := make([]byte, 12)
	binary.BigEndian.PutUint32(b[:], uint32(ts))
	randomCompoment := make([]byte, 5)
	if _, err := rand.Read(randomCompoment); err != nil {
		return nil, err
	}
	copy(b[4:], randomCompoment[:])
	copy(b[9:], nextCounter())
	return &ID{data: b}, nil
}

func (id *ID) SetHash(hash string) {
	id.hash = hash
}

// Raw returns the raw cursor
func (id *ID) Hash() string {
	return id.hash
}

// Raw returns the raw cursor
func (id *ID) Raw() []byte {
	return id.data
}

// String implements Stringer interface
func (id *ID) String() string {
	return hex.EncodeToString(id.data)
}

// Ts returns the timestamp component
func (id *ID) Ts() int {
	return int(binary.BigEndian.Uint32(id.data[0:4]))
}

func (id *ID) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf(`"%v"`, hex.EncodeToString(id.data))), nil
}

// FIXME(ts) finish the port from uuid to hash
func (id *ID) UnmarshalJSON(data []byte) error {
	if len(data) != 26 {
		return fmt.Errorf("invalid Cursor data: %v", string(data))
	}
	b := make([]byte, 24)
	if _, err := hex.Decode(b, data[1:13]); err != nil {
		return fmt.Errorf("invalid Cursor data: %v", string(data))
	}
	*id = ID{data: b}
	return nil
}

// FromHex build an `ID` from an hex encoded string
func FromHex(data string) (*ID, error) {
	if len(data) != 24 {
		return nil, fmt.Errorf("invalid Cursor data: %v", string(data))
	}
	b, err := hex.DecodeString(data)
	if err != nil {
		return nil, fmt.Errorf("invalid Cursor data: %v", string(data))
	}
	return &ID{data: b}, err
}
<commit_msg>docstore: simplied ID generation<commit_after>/*

package id implements a MongoDB ObjectId like object.

Cursor stored the timestamp and a hash.

	(<timestamp encoded in big endian uint32> 4 bytes) + (5 random bytes / 3 bytes counter starting with random value) = 12 bytes

	24 bytes hex-encoded
*/
package id

import (
	"crypto/rand"
	"encoding/binary"
	"encoding/hex"
	"fmt"
)

// Cursor hold a hex/byte representation of a timestamp and a hash
type ID struct {
	data []byte
	hash string // The hash is not part of the ID but it can be attached to the ID
	flag byte   // Same here, not part of the ID but can be attched to it for convenience
}

func New(ts int) (*ID, error) {
	b := make([]byte, 12)
	binary.BigEndian.PutUint32(b[:], uint32(ts))
	randomCompoment := make([]byte, 8)
	if _, err := rand.Read(randomCompoment); err != nil {
		return nil, err
	}
	copy(b[4:], randomCompoment[:])
	return &ID{data: b}, nil
}

func (id *ID) SetFlag(flag byte) {
	id.flag = flag
}

func (id *ID) Flag() byte {
	return id.flag
}

func (id *ID) SetHash(hash string) {
	id.hash = hash
}

func (id *ID) Hash() string {
	return id.hash
}

// Raw returns the raw cursor
func (id *ID) Raw() []byte {
	return id.data
}

// String implements Stringer interface
func (id *ID) String() string {
	return hex.EncodeToString(id.data)
}

// Ts returns the timestamp component
func (id *ID) Ts() int {
	return int(binary.BigEndian.Uint32(id.data[0:4]))
}

func (id *ID) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf(`"%v"`, hex.EncodeToString(id.data))), nil
}

// FIXME(ts) finish the port from uuid to hash
func (id *ID) UnmarshalJSON(data []byte) error {
	if len(data) != 26 {
		return fmt.Errorf("invalid Cursor data: %v", string(data))
	}
	b := make([]byte, 24)
	if _, err := hex.Decode(b, data[1:13]); err != nil {
		return fmt.Errorf("invalid Cursor data: %v", string(data))
	}
	*id = ID{data: b}
	return nil
}

// FromHex build an `ID` from an hex encoded string
func FromHex(data string) (*ID, error) {
	if len(data) != 24 {
		return nil, fmt.Errorf("invalid Cursor data: %v", string(data))
	}
	b, err := hex.DecodeString(data)
	if err != nil {
		return nil, fmt.Errorf("invalid Cursor data: %v", string(data))
	}
	return &ID{data: b}, err
}
<|endoftext|>


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

package cluster

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/tsuru/tsuru/log"
	"github.com/tsuru/tsuru/servicemanager"
)

var (
	desc        = prometheus.NewDesc("tsuru_cluster_info", "Basic information about existing clusters", []string{"provisioner", "name"}, nil)
	poolsDesc   = prometheus.NewDesc("tsuru_cluster_pool", "information about related pool that are inside the cluster", []string{"name", "pool"}, nil)
	failureDesc = prometheus.NewDesc("tsuru_cluster_fetch_fail", "indicates whether failed to get clusters", []string{}, nil)
)

func init() {
	prometheus.MustRegister(&clustersMetricCollector{})
}

type clustersMetricCollector struct{}

func (c *clustersMetricCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- desc
}

func (c *clustersMetricCollector) Collect(ch chan<- prometheus.Metric) {
	clusters, err := servicemanager.Cluster.List()
	failureValue := float64(0)
	if err != nil {
		failureValue = float64(1)
		log.Errorf("Could not get clusters: %s", err.Error())
	}

	ch <- prometheus.MustNewConstMetric(failureDesc, prometheus.GaugeValue, failureValue)

	if failureValue > 0 {
		return
	}

	for _, cluster := range clusters {
		ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(1), cluster.Provisioner, cluster.Name)

		for _, pool := range cluster.Pools {
			ch <- prometheus.MustNewConstMetric(poolsDesc, prometheus.GaugeValue, float64(1), cluster.Name, pool)
		}
	}
}
<commit_msg>provision/cluster/metrics: Add missing descriptions on collector<commit_after>// Copyright 2020 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cluster

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/tsuru/tsuru/log"
	"github.com/tsuru/tsuru/servicemanager"
)

var (
	desc        = prometheus.NewDesc("tsuru_cluster_info", "Basic information about existing clusters", []string{"provisioner", "name"}, nil)
	poolsDesc   = prometheus.NewDesc("tsuru_cluster_pool", "information about related pool that are inside the cluster", []string{"name", "pool"}, nil)
	failureDesc = prometheus.NewDesc("tsuru_cluster_fetch_fail", "indicates whether failed to get clusters", []string{}, nil)
)

func init() {
	prometheus.MustRegister(&clustersMetricCollector{})
}

type clustersMetricCollector struct{}

func (c *clustersMetricCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- desc
	ch <- poolsDesc
	ch <- failureDesc
}

func (c *clustersMetricCollector) Collect(ch chan<- prometheus.Metric) {
	clusters, err := servicemanager.Cluster.List()
	failureValue := float64(0)
	if err != nil {
		failureValue = float64(1)
		log.Errorf("Could not get clusters: %s", err.Error())
	}

	ch <- prometheus.MustNewConstMetric(failureDesc, prometheus.GaugeValue, failureValue)

	if failureValue > 0 {
		return
	}

	for _, cluster := range clusters {
		ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(1), cluster.Provisioner, cluster.Name)

		for _, pool := range cluster.Pools {
			ch <- prometheus.MustNewConstMetric(poolsDesc, prometheus.GaugeValue, float64(1), cluster.Name, pool)
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes 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 main

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/url"
	"path"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"cloud.google.com/go/storage"
	"github.com/sirupsen/logrus"
	"google.golang.org/api/iterator"
	"k8s.io/test-infra/prow/config"
	"k8s.io/test-infra/prow/deck/jobs"
)

const (
	resultsPerPage  = 20
	idParam         = "buildId"
	latestBuildFile = "latest-build.txt"

	// ** Job history assumes the GCS layout specified here:
	// https://github.com/kubernetes/test-infra/tree/master/gubernator#gcs-bucket-layout
	logsPrefix     = "logs"
	symLinkPrefix  = "pr-logs/directory"
	spyglassPrefix = "/view/gcs"
	emptyID        = int64(-1) // indicates no build id was specified
)

var (
	prefixRe = regexp.MustCompile("gs://.*?/")
	linkRe   = regexp.MustCompile("/([0-9]+)\\.txt$")
)

type buildData struct {
	index        int
	SpyglassLink string
	ID           string
	Started      time.Time
	Duration     time.Duration
	Result       string
}

type jobHistoryTemplate struct {
	OlderLink    string
	NewerLink    string
	LatestLink   string
	Name         string
	ResultsShown int
	ResultsTotal int
	Builds       []buildData
}

func readObject(obj *storage.ObjectHandle) ([]byte, error) {
	rc, err := obj.NewReader(context.Background())
	if err != nil {
		return []byte{}, fmt.Errorf("failed to get reader for GCS object: %v", err)
	}
	return ioutil.ReadAll(rc)
}

func readLatestBuild(bkt *storage.BucketHandle, root string) (int64, error) {
	path := path.Join(root, latestBuildFile)
	data, err := readObject(bkt.Object(path))
	if err != nil {
		return -1, fmt.Errorf("failed to read %s: %v", path, err)
	}
	n, err := strconv.ParseInt(string(data), 10, 64)
	if err != nil {
		return -1, fmt.Errorf("failed to parse %s: %v", path, err)
	}
	return n, nil
}

// resolve sym links into the actual log directory for a particular test run
func resolveSymLink(bkt *storage.BucketHandle, symLink string) (string, error) {
	data, err := readObject(bkt.Object(symLink))
	if err != nil {
		return "", fmt.Errorf("failed to read %s: %v", symLink, err)
	}
	// strip gs://<bucket-name> from global address `u`
	u := string(data)
	return prefixRe.ReplaceAllString(u, ""), nil
}

func spyglassLink(bkt *storage.BucketHandle, root, id string) (string, error) {
	bAttrs, err := bkt.Attrs(context.Background())
	if err != nil {
		return "", fmt.Errorf("failed to get bucket name: %v", err)
	}
	bktName := bAttrs.Name
	p, err := getPath(bkt, root, id, "")
	if err != nil {
		return "", fmt.Errorf("failed to get path: %v", err)
	}
	return path.Join(spyglassPrefix, bktName, p), nil
}

func getPath(bkt *storage.BucketHandle, root, id, fname string) (string, error) {
	if strings.HasPrefix(root, logsPrefix) {
		return path.Join(root, id, fname), nil
	}
	symLink := path.Join(root, id+".txt")
	dir, err := resolveSymLink(bkt, symLink)
	if err != nil {
		return "", fmt.Errorf("failed to resolve sym link: %v", err)
	}
	return path.Join(dir, fname), nil
}

// reads specified JSON file in to `data`
func readJSON(bkt *storage.BucketHandle, root, id, fname string, data interface{}) error {
	p, err := getPath(bkt, root, id, fname)
	if err != nil {
		return fmt.Errorf("failed to get path: %v", err)
	}
	rawData, err := readObject(bkt.Object(p))
	if err != nil {
		return fmt.Errorf("failed to read %s for build %s: %v", fname, id, err)
	}
	err = json.Unmarshal(rawData, &data)
	if err != nil {
		return fmt.Errorf("failed to parse %s for build %s: %v", fname, id, err)
	}
	return nil
}

// Lists the GCS "directory paths" immediately under prefix.
func listSubDirs(bkt *storage.BucketHandle, prefix string) ([]string, error) {
	if !strings.HasSuffix(prefix, "/") {
		prefix += "/"
	}
	dirs := []string{}
	it := bkt.Objects(context.Background(), &storage.Query{
		Prefix:    prefix,
		Delimiter: "/",
	})
	for {
		attrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return dirs, err
		}
		if attrs.Prefix != "" {
			dirs = append(dirs, attrs.Prefix)
		}
	}
	return dirs, nil
}

// Lists all GCS keys with given prefix.
func listAll(bkt *storage.BucketHandle, prefix string) ([]string, error) {
	keys := []string{}
	it := bkt.Objects(context.Background(), &storage.Query{
		Prefix: prefix,
	})
	for {
		attrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return keys, err
		}
		keys = append(keys, attrs.Name)
	}
	return keys, nil
}

// Gets all build ids for a job.
func listBuildIDs(bkt *storage.BucketHandle, root string) ([]int64, error) {
	ids := []int64{}
	if strings.HasPrefix(root, logsPrefix) {
		dirs, err := listSubDirs(bkt, root)
		if err != nil {
			return ids, fmt.Errorf("failed to list GCS directories: %v", err)
		}
		for _, dir := range dirs {
			i, err := strconv.ParseInt(path.Base(dir), 10, 64)
			if err == nil {
				ids = append(ids, i)
			} else {
				logrus.Warningf("unrecognized directory name (expected int64): %s", dir)
			}
		}
	} else {
		keys, err := listAll(bkt, root)
		if err != nil {
			return ids, fmt.Errorf("failed to list GCS keys: %v", err)
		}
		for _, key := range keys {
			matches := linkRe.FindStringSubmatch(key)
			if len(matches) == 2 {
				i, err := strconv.ParseInt(matches[1], 10, 64)
				if err == nil {
					ids = append(ids, i)
				} else {
					logrus.Warningf("unrecognized directory name (expected int64): %s", matches[1])
				}
			}
		}
	}
	return ids, nil
}

func jobHistURL(url *url.URL) (string, string, int64, error) {
	p := strings.TrimPrefix(url.Path, "/job-history/")
	s := strings.SplitN(p, "/", 2)
	if len(s) < 2 {
		return "", "", emptyID, fmt.Errorf("invalid path (expected /job-history/<gcs-path>): %v", url.Path)
	}
	bucketName := s[0]
	root := s[1]
	if bucketName == "" {
		return bucketName, root, emptyID, fmt.Errorf("missing GCS bucket name: %v", url.Path)
	}
	if root == "" {
		return bucketName, root, emptyID, fmt.Errorf("invalid GCS path for job: %v", url.Path)
	}

	buildID := emptyID
	if idVals := url.Query()[idParam]; len(idVals) >= 1 && idVals[0] != "" {
		var err error
		buildID, err = strconv.ParseInt(idVals[0], 10, 64)
		if err != nil {
			return bucketName, root, buildID, fmt.Errorf("invalid value for %s: %v", idParam, err)
		}
		if buildID < 0 {
			return bucketName, root, buildID, fmt.Errorf("invalid value %s = %d", idParam, buildID)
		}
	}

	return bucketName, root, buildID, nil
}

func linkID(url *url.URL, id int64) string {
	u := *url
	q := u.Query()
	var val string
	if id != emptyID {
		val = strconv.FormatInt(id, 10)
	}
	q.Set(idParam, val)
	u.RawQuery = q.Encode()
	return u.String()
}

func getBuildData(bkt *storage.BucketHandle, root string, buildID int64, index int) (buildData, error) {
	b := buildData{
		index:  index,
		ID:     strconv.FormatInt(buildID, 10),
		Result: "Unknown",
	}
	link, err := spyglassLink(bkt, root, b.ID)
	if err != nil {
		return b, fmt.Errorf("failed to get spyglass link: %v", err)
	}
	b.SpyglassLink = link
	started := jobs.Started{}
	err = readJSON(bkt, root, b.ID, "started.json", &started)
	if err != nil {
		return b, fmt.Errorf("failed to get job metadata: %v", err)
	}
	b.Result = "Unfinished"
	b.Started = time.Unix(started.Timestamp, 0)
	finished := jobs.Finished{}
	readJSON(bkt, root, b.ID, "finished.json", &finished)
	if finished.Timestamp != 0 {
		b.Duration = time.Unix(finished.Timestamp, 0).Sub(b.Started)
	}
	if finished.Result != "" {
		b.Result = finished.Result
	}
	return b, nil
}

// assumes a to be sorted in descending order
// returns a subslice of a along with its indices (inclusive)
func cropResults(a []int64, max int64) ([]int64, int, int) {
	res := []int64{}
	firstIndex := -1
	lastIndex := 0
	for i, v := range a {
		if v <= max {
			res = append(res, v)
			if firstIndex == -1 {
				firstIndex = i
			}
			lastIndex = i
			if len(res) >= resultsPerPage {
				break
			}
		}
	}
	return res, firstIndex, lastIndex
}

// golang <3
type int64slice []int64

func (a int64slice) Len() int           { return len(a) }
func (a int64slice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a int64slice) Less(i, j int) bool { return a[i] < a[j] }

// Gets job history from the GCS bucket specified in config.
func getJobHistory(url *url.URL, config *config.Config, gcsClient *storage.Client) (jobHistoryTemplate, error) {
	start := time.Now()
	tmpl := jobHistoryTemplate{}

	bucketName, root, top, err := jobHistURL(url)
	if err != nil {
		return tmpl, fmt.Errorf("invalid url %s: %v", url.String(), err)
	}
	tmpl.Name = root
	bkt := gcsClient.Bucket(bucketName)

	latest, err := readLatestBuild(bkt, root)
	if err != nil {
		return tmpl, fmt.Errorf("failed to locate build data: %v", err)
	}
	if top == emptyID || top > latest {
		top = latest
	}
	if top != latest {
		tmpl.LatestLink = linkID(url, emptyID)
	}

	buildIDs, err := listBuildIDs(bkt, root)
	if err != nil {
		return tmpl, fmt.Errorf("failed to get build ids: %v", err)
	}
	sort.Sort(sort.Reverse(int64slice(buildIDs)))

	shownIDs, firstIndex, lastIndex := cropResults(buildIDs, top)
	if firstIndex > 0 {
		nextIndex := firstIndex - resultsPerPage
		// here emptyID indicates the most recent build, which will not necessarily be buildIDs[0]
		next := emptyID
		if nextIndex >= 0 {
			next = buildIDs[nextIndex]
		}
		tmpl.NewerLink = linkID(url, next)
	}
	if lastIndex < len(buildIDs)-1 {
		tmpl.OlderLink = linkID(url, buildIDs[lastIndex+1])
	}

	tmpl.Builds = make([]buildData, len(shownIDs))
	tmpl.ResultsShown = len(shownIDs)
	tmpl.ResultsTotal = len(buildIDs)

	bch := make(chan buildData)
	for i, buildID := range shownIDs {
		go func(i int, buildID int64) {
			bd, err := getBuildData(bkt, root, buildID, i)
			if err != nil {
				logrus.Warningf("build %d information incomplete: %v", buildID, err)
			}
			bch <- bd
		}(i, buildID)
	}
	for i := 0; i < len(shownIDs); i++ {
		b := <-bch
		tmpl.Builds[b.index] = b
	}

	elapsed := time.Now().Sub(start)
	logrus.Warningf("loaded %s in %v", url.Path, elapsed)
	return tmpl, nil
}
<commit_msg>improve error handling<commit_after>/*
Copyright 2018 The Kubernetes 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 main

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/url"
	"path"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"cloud.google.com/go/storage"
	"github.com/sirupsen/logrus"
	"google.golang.org/api/iterator"
	"k8s.io/test-infra/prow/config"
	"k8s.io/test-infra/prow/deck/jobs"
)

const (
	resultsPerPage  = 20
	idParam         = "buildId"
	latestBuildFile = "latest-build.txt"

	// ** Job history assumes the GCS layout specified here:
	// https://github.com/kubernetes/test-infra/tree/master/gubernator#gcs-bucket-layout
	logsPrefix     = "logs"
	symLinkPrefix  = "pr-logs/directory"
	spyglassPrefix = "/view/gcs"
	emptyID        = int64(-1) // indicates no build id was specified
)

var (
	prefixRe = regexp.MustCompile("gs://.*?/")
	linkRe   = regexp.MustCompile("/([0-9]+)\\.txt$")
)

type buildData struct {
	index        int
	SpyglassLink string
	ID           string
	Started      time.Time
	Duration     time.Duration
	Result       string
}

type jobHistoryTemplate struct {
	OlderLink    string
	NewerLink    string
	LatestLink   string
	Name         string
	ResultsShown int
	ResultsTotal int
	Builds       []buildData
}

func readObject(obj *storage.ObjectHandle) ([]byte, error) {
	rc, err := obj.NewReader(context.Background())
	if err != nil {
		return []byte{}, fmt.Errorf("failed to get reader for GCS object: %v", err)
	}
	return ioutil.ReadAll(rc)
}

func readLatestBuild(bkt *storage.BucketHandle, root string) (int64, error) {
	path := path.Join(root, latestBuildFile)
	data, err := readObject(bkt.Object(path))
	if err != nil {
		return -1, fmt.Errorf("failed to read %s: %v", path, err)
	}
	n, err := strconv.ParseInt(string(data), 10, 64)
	if err != nil {
		return -1, fmt.Errorf("failed to parse %s: %v", path, err)
	}
	return n, nil
}

// resolve sym links into the actual log directory for a particular test run
func resolveSymLink(bkt *storage.BucketHandle, symLink string) (string, error) {
	data, err := readObject(bkt.Object(symLink))
	if err != nil {
		return "", fmt.Errorf("failed to read %s: %v", symLink, err)
	}
	// strip gs://<bucket-name> from global address `u`
	u := string(data)
	return prefixRe.ReplaceAllString(u, ""), nil
}

func spyglassLink(bkt *storage.BucketHandle, root, id string) (string, error) {
	bAttrs, err := bkt.Attrs(context.Background())
	if err != nil {
		return "", fmt.Errorf("failed to get bucket name: %v", err)
	}
	bktName := bAttrs.Name
	p, err := getPath(bkt, root, id, "")
	if err != nil {
		return "", fmt.Errorf("failed to get path: %v", err)
	}
	return path.Join(spyglassPrefix, bktName, p), nil
}

func getPath(bkt *storage.BucketHandle, root, id, fname string) (string, error) {
	if strings.HasPrefix(root, logsPrefix) {
		return path.Join(root, id, fname), nil
	}
	symLink := path.Join(root, id+".txt")
	dir, err := resolveSymLink(bkt, symLink)
	if err != nil {
		return "", fmt.Errorf("failed to resolve sym link: %v", err)
	}
	return path.Join(dir, fname), nil
}

// reads specified JSON file in to `data`
func readJSON(bkt *storage.BucketHandle, root, id, fname string, data interface{}) error {
	p, err := getPath(bkt, root, id, fname)
	if err != nil {
		return fmt.Errorf("failed to get path: %v", err)
	}
	rawData, err := readObject(bkt.Object(p))
	if err != nil {
		return fmt.Errorf("failed to read %s for build %s: %v", fname, id, err)
	}
	err = json.Unmarshal(rawData, &data)
	if err != nil {
		return fmt.Errorf("failed to parse %s for build %s: %v", fname, id, err)
	}
	return nil
}

// Lists the GCS "directory paths" immediately under prefix.
func listSubDirs(bkt *storage.BucketHandle, prefix string) ([]string, error) {
	if !strings.HasSuffix(prefix, "/") {
		prefix += "/"
	}
	dirs := []string{}
	it := bkt.Objects(context.Background(), &storage.Query{
		Prefix:    prefix,
		Delimiter: "/",
	})
	for {
		attrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return dirs, err
		}
		if attrs.Prefix != "" {
			dirs = append(dirs, attrs.Prefix)
		}
	}
	return dirs, nil
}

// Lists all GCS keys with given prefix.
func listAll(bkt *storage.BucketHandle, prefix string) ([]string, error) {
	keys := []string{}
	it := bkt.Objects(context.Background(), &storage.Query{
		Prefix: prefix,
	})
	for {
		attrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return keys, err
		}
		keys = append(keys, attrs.Name)
	}
	return keys, nil
}

// Gets all build ids for a job.
func listBuildIDs(bkt *storage.BucketHandle, root string) ([]int64, error) {
	ids := []int64{}
	if strings.HasPrefix(root, logsPrefix) {
		dirs, err := listSubDirs(bkt, root)
		if err != nil {
			return ids, fmt.Errorf("failed to list GCS directories: %v", err)
		}
		for _, dir := range dirs {
			i, err := strconv.ParseInt(path.Base(dir), 10, 64)
			if err == nil {
				ids = append(ids, i)
			} else {
				logrus.Warningf("unrecognized directory name (expected int64): %s", dir)
			}
		}
	} else {
		keys, err := listAll(bkt, root)
		if err != nil {
			return ids, fmt.Errorf("failed to list GCS keys: %v", err)
		}
		for _, key := range keys {
			matches := linkRe.FindStringSubmatch(key)
			if len(matches) == 2 {
				i, err := strconv.ParseInt(matches[1], 10, 64)
				if err == nil {
					ids = append(ids, i)
				} else {
					logrus.Warningf("unrecognized file name (expected <int64>.txt): %s", key)
				}
			}
		}
	}
	return ids, nil
}

func jobHistURL(url *url.URL) (string, string, int64, error) {
	p := strings.TrimPrefix(url.Path, "/job-history/")
	s := strings.SplitN(p, "/", 2)
	if len(s) < 2 {
		return "", "", emptyID, fmt.Errorf("invalid path (expected /job-history/<gcs-path>): %v", url.Path)
	}
	bucketName := s[0]
	root := s[1]
	if bucketName == "" {
		return bucketName, root, emptyID, fmt.Errorf("missing GCS bucket name: %v", url.Path)
	}
	if root == "" {
		return bucketName, root, emptyID, fmt.Errorf("invalid GCS path for job: %v", url.Path)
	}

	buildID := emptyID
	if idVals := url.Query()[idParam]; len(idVals) >= 1 && idVals[0] != "" {
		var err error
		buildID, err = strconv.ParseInt(idVals[0], 10, 64)
		if err != nil {
			return bucketName, root, buildID, fmt.Errorf("invalid value for %s: %v", idParam, err)
		}
		if buildID < 0 {
			return bucketName, root, buildID, fmt.Errorf("invalid value %s = %d", idParam, buildID)
		}
	}

	return bucketName, root, buildID, nil
}

func linkID(url *url.URL, id int64) string {
	u := *url
	q := u.Query()
	var val string
	if id != emptyID {
		val = strconv.FormatInt(id, 10)
	}
	q.Set(idParam, val)
	u.RawQuery = q.Encode()
	return u.String()
}

func getBuildData(bkt *storage.BucketHandle, root string, buildID int64, index int) (buildData, error) {
	b := buildData{
		index:  index,
		ID:     strconv.FormatInt(buildID, 10),
		Result: "Unknown",
	}
	link, err := spyglassLink(bkt, root, b.ID)
	if err != nil {
		return b, fmt.Errorf("failed to get spyglass link: %v", err)
	}
	b.SpyglassLink = link
	started := jobs.Started{}
	err = readJSON(bkt, root, b.ID, "started.json", &started)
	if err != nil {
		return b, fmt.Errorf("failed to get job metadata: %v", err)
	}
	b.Result = "Unfinished"
	b.Started = time.Unix(started.Timestamp, 0)
	finished := jobs.Finished{}
	err = readJSON(bkt, root, b.ID, "finished.json", &finished)
	if err != nil {
		logrus.Warningf("failed to read finished.json (job might be unfinished): %v", err)
	}
	if finished.Timestamp != 0 {
		b.Duration = time.Unix(finished.Timestamp, 0).Sub(b.Started)
	}
	if finished.Result != "" {
		b.Result = finished.Result
	}
	return b, nil
}

// assumes a to be sorted in descending order
// returns a subslice of a along with its indices (inclusive)
func cropResults(a []int64, max int64) ([]int64, int, int) {
	res := []int64{}
	firstIndex := -1
	lastIndex := 0
	for i, v := range a {
		if v <= max {
			res = append(res, v)
			if firstIndex == -1 {
				firstIndex = i
			}
			lastIndex = i
			if len(res) >= resultsPerPage {
				break
			}
		}
	}
	return res, firstIndex, lastIndex
}

// golang <3
type int64slice []int64

func (a int64slice) Len() int           { return len(a) }
func (a int64slice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a int64slice) Less(i, j int) bool { return a[i] < a[j] }

// Gets job history from the GCS bucket specified in config.
func getJobHistory(url *url.URL, config *config.Config, gcsClient *storage.Client) (jobHistoryTemplate, error) {
	start := time.Now()
	tmpl := jobHistoryTemplate{}

	bucketName, root, top, err := jobHistURL(url)
	if err != nil {
		return tmpl, fmt.Errorf("invalid url %s: %v", url.String(), err)
	}
	tmpl.Name = root
	bkt := gcsClient.Bucket(bucketName)

	latest, err := readLatestBuild(bkt, root)
	if err != nil {
		return tmpl, fmt.Errorf("failed to locate build data: %v", err)
	}
	if top == emptyID || top > latest {
		top = latest
	}
	if top != latest {
		tmpl.LatestLink = linkID(url, emptyID)
	}

	buildIDs, err := listBuildIDs(bkt, root)
	if err != nil {
		return tmpl, fmt.Errorf("failed to get build ids: %v", err)
	}
	sort.Sort(sort.Reverse(int64slice(buildIDs)))

	shownIDs, firstIndex, lastIndex := cropResults(buildIDs, top)
	if firstIndex > 0 {
		nextIndex := firstIndex - resultsPerPage
		// here emptyID indicates the most recent build, which will not necessarily be buildIDs[0]
		next := emptyID
		if nextIndex >= 0 {
			next = buildIDs[nextIndex]
		}
		tmpl.NewerLink = linkID(url, next)
	}
	if lastIndex < len(buildIDs)-1 {
		tmpl.OlderLink = linkID(url, buildIDs[lastIndex+1])
	}

	tmpl.Builds = make([]buildData, len(shownIDs))
	tmpl.ResultsShown = len(shownIDs)
	tmpl.ResultsTotal = len(buildIDs)

	bch := make(chan buildData)
	for i, buildID := range shownIDs {
		go func(i int, buildID int64) {
			bd, err := getBuildData(bkt, root, buildID, i)
			if err != nil {
				logrus.Warningf("build %d information incomplete: %v", buildID, err)
			}
			bch <- bd
		}(i, buildID)
	}
	for i := 0; i < len(shownIDs); i++ {
		b := <-bch
		tmpl.Builds[b.index] = b
	}

	elapsed := time.Now().Sub(start)
	logrus.Infof("loaded %s in %v", url.Path, elapsed)
	return tmpl, nil
}
<|endoftext|>


<commit_before>package prompt

type Render struct {
	out            ConsoleWriter
	prefix         string
	title          string
	row            uint16
	col            uint16
	maxCompletions uint16
	// colors
	prefixColor                 Color
	textColor                   Color
	outputTextColor             Color
	completedTextColor          Color
	completionTextColor         Color
	completionBGColor           Color
	selectedCompletionTextColor Color
	selectedCompletionBGColor   Color
}

func (r *Render) Setup() {
	if r.title != "" {
		r.out.SetTitle(r.title)
	}
	r.renderPrefix()
	r.out.Flush()
}

func (r *Render) renderPrefix() {
	r.out.SetColor(r.prefixColor, DefaultColor)
	r.out.WriteStr(r.prefix)
	r.out.SetColor(DefaultColor, DefaultColor)
}

func (r *Render) TearDown() {
	r.out.ClearTitle()
	r.out.EraseDown()
	r.out.Flush()
}

func (r *Render) prepareArea(lines int) {
	for i := 0; i < lines; i++ {
		r.out.ScrollDown()
	}
	for i := 0; i < lines; i++ {
		r.out.ScrollUp()
	}
	return
}

func (r *Render) UpdateWinSize(ws *WinSize) {
	r.row = ws.Row
	r.col = ws.Col
	return
}

func (r *Render) renderCompletion(buf *Buffer, words []string, chosen int) {
	if l := len(words); l == 0 {
		return
	} else if l > int(r.maxCompletions) - 2 || l >= int(r.row) - 2 {
		if r.maxCompletions > r.row {
			words = words[:int(r.row) - 2]
		} else {
			words = words[:int(r.maxCompletions) - 2]
		}
	}

	formatted, width := formatCompletions(words, int(r.col) - len(r.prefix) - 3)
	l := len(formatted)
	r.prepareArea(l)

	d := (len(r.prefix) + len(buf.Document().TextBeforeCursor())) % int(r.col)
	if d + width + 3 > int(r.col) {
		r.out.CursorBackward(d + width + 3 - int(r.col))
	}

	r.out.SetColor(White, Cyan)
	for i := 0; i < l; i++ {
		r.out.CursorDown(1)
		if i == chosen {
			r.out.SetColor(r.selectedCompletionTextColor, r.selectedCompletionBGColor)
		} else {
			r.out.SetColor(r.completionTextColor, r.completionBGColor)
		}
		r.out.WriteStr(" " + formatted[i] + " ")
		r.out.SetColor(White, DarkGray)
		r.out.Write([]byte(" "))
		r.out.CursorBackward(width + 3)
	}
	if d + width + 3 > int(r.col) {
		r.out.CursorForward(d + width + 3 - int(r.col))
	}

	r.out.CursorUp(l)
	r.out.SetColor(DefaultColor, DefaultColor)
	return
}

func (r *Render) Erase(buffer *Buffer) {
	r.out.CursorBackward(int(r.col))
	r.out.EraseDown()
	r.renderPrefix()
	r.out.Flush()
	return
}

func (r *Render) Render(buffer *Buffer, completions []string, chosen int) {
	line := buffer.Document().CurrentLine()
	r.out.SetColor(r.textColor, DefaultColor)
	r.out.WriteStr(line)
	r.out.SetColor(DefaultColor, DefaultColor)
	r.out.CursorBackward(len(line) - buffer.CursorPosition)
	r.renderCompletion(buffer, completions, chosen)
	if chosen != -1 {
		c := completions[chosen]
		r.out.CursorBackward(len([]rune(buffer.Document().GetWordBeforeCursor())))
		r.out.SetColor(r.completedTextColor, DefaultColor)
		r.out.WriteStr(c)
		r.out.SetColor(DefaultColor, DefaultColor)
	}
	r.out.Flush()
}

func (r *Render) BreakLine(buffer *Buffer, result string) {
	r.out.SetColor(r.textColor, DefaultColor)
	r.out.WriteStr(buffer.Document().Text + "\n")
	r.out.SetColor(r.outputTextColor, DefaultColor)
	r.out.WriteStr(result + "\n")
	r.out.SetColor(DefaultColor, DefaultColor)
	r.renderPrefix()
}

func formatCompletions(words []string, max int) (new []string, width int) {
	num := len(words)
	new = make([]string, num)
	width = 0

	for i := 0; i < num; i++ {
		if width < len([]rune(words[i])) {
			width = len([]rune(words[i]))
		}
	}

	if width > max {
		width = max
	}

	for i := 0; i < num; i++ {
		if l := len(words[i]); l > width {
			new[i] = words[i][:width - 3] + "..."
		} else if l < width  {
			spaces := width - len([]rune(words[i]))
			new[i] = words[i]
			for j := 0; j < spaces; j++ {
				new[i] += " "
			}
		} else {
			new[i] = words[i]
		}
	}
	return
}
<commit_msg>Fix max completions<commit_after>package prompt

type Render struct {
	out            ConsoleWriter
	prefix         string
	title          string
	row            uint16
	col            uint16
	maxCompletions uint16
	// colors
	prefixColor                 Color
	textColor                   Color
	outputTextColor             Color
	completedTextColor          Color
	completionTextColor         Color
	completionBGColor           Color
	selectedCompletionTextColor Color
	selectedCompletionBGColor   Color
}

func (r *Render) Setup() {
	if r.title != "" {
		r.out.SetTitle(r.title)
	}
	r.renderPrefix()
	r.out.Flush()
}

func (r *Render) renderPrefix() {
	r.out.SetColor(r.prefixColor, DefaultColor)
	r.out.WriteStr(r.prefix)
	r.out.SetColor(DefaultColor, DefaultColor)
}

func (r *Render) TearDown() {
	r.out.ClearTitle()
	r.out.EraseDown()
	r.out.Flush()
}

func (r *Render) prepareArea(lines int) {
	for i := 0; i < lines; i++ {
		r.out.ScrollDown()
	}
	for i := 0; i < lines; i++ {
		r.out.ScrollUp()
	}
	return
}

func (r *Render) UpdateWinSize(ws *WinSize) {
	r.row = ws.Row
	r.col = ws.Col
	return
}

func (r *Render) renderCompletion(buf *Buffer, words []string, chosen int) {
	max := int(r.maxCompletions)
	if r.maxCompletions > r.row {
		max = int(r.row)
	}

	if l := len(words); l == 0 {
		return
	} else if l > max {
		words = words[:max]
	}

	formatted, width := formatCompletions(words, int(r.col) - len(r.prefix) - 3)
	l := len(formatted)
	r.prepareArea(l)

	d := (len(r.prefix) + len(buf.Document().TextBeforeCursor())) % int(r.col)
	if d + width + 3 > int(r.col) {
		r.out.CursorBackward(d + width + 3 - int(r.col))
	}

	r.out.SetColor(White, Cyan)
	for i := 0; i < l; i++ {
		r.out.CursorDown(1)
		if i == chosen {
			r.out.SetColor(r.selectedCompletionTextColor, r.selectedCompletionBGColor)
		} else {
			r.out.SetColor(r.completionTextColor, r.completionBGColor)
		}
		r.out.WriteStr(" " + formatted[i] + " ")
		r.out.SetColor(White, DarkGray)
		r.out.Write([]byte(" "))
		r.out.CursorBackward(width + 3)
	}
	if d + width + 3 > int(r.col) {
		r.out.CursorForward(d + width + 3 - int(r.col))
	}

	r.out.CursorUp(l)
	r.out.SetColor(DefaultColor, DefaultColor)
	return
}

func (r *Render) Erase(buffer *Buffer) {
	r.out.CursorBackward(int(r.col))
	r.out.EraseDown()
	r.renderPrefix()
	r.out.Flush()
	return
}

func (r *Render) Render(buffer *Buffer, completions []string, chosen int) {
	line := buffer.Document().CurrentLine()
	r.out.SetColor(r.textColor, DefaultColor)
	r.out.WriteStr(line)
	r.out.SetColor(DefaultColor, DefaultColor)
	r.out.CursorBackward(len(line) - buffer.CursorPosition)
	r.renderCompletion(buffer, completions, chosen)
	if chosen != -1 {
		c := completions[chosen]
		r.out.CursorBackward(len([]rune(buffer.Document().GetWordBeforeCursor())))
		r.out.SetColor(r.completedTextColor, DefaultColor)
		r.out.WriteStr(c)
		r.out.SetColor(DefaultColor, DefaultColor)
	}
	r.out.Flush()
}

func (r *Render) BreakLine(buffer *Buffer, result string) {
	r.out.SetColor(r.textColor, DefaultColor)
	r.out.WriteStr(buffer.Document().Text + "\n")
	r.out.SetColor(r.outputTextColor, DefaultColor)
	r.out.WriteStr(result + "\n")
	r.out.SetColor(DefaultColor, DefaultColor)
	r.renderPrefix()
}

func formatCompletions(words []string, max int) (new []string, width int) {
	num := len(words)
	new = make([]string, num)
	width = 0

	for i := 0; i < num; i++ {
		if width < len([]rune(words[i])) {
			width = len([]rune(words[i]))
		}
	}

	if width > max {
		width = max
	}

	for i := 0; i < num; i++ {
		if l := len(words[i]); l > width {
			new[i] = words[i][:width - 3] + "..."
		} else if l < width  {
			spaces := width - len([]rune(words[i]))
			new[i] = words[i]
			for j := 0; j < spaces; j++ {
				new[i] += " "
			}
		} else {
			new[i] = words[i]
		}
	}
	return
}
<|endoftext|>


<commit_before>package controller

import (
	"fmt"
	"github.com/ninjasphere/app-scheduler/model"
	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/logger"
	"time"
)

var log = logger.GetLogger("")

type startRequest struct {
	model       *model.Task
	reply       chan error
	updateModel bool
}

type cancelRequest struct {
	id    string
	reply chan error
}

type actuationRequest struct {
	action *action
	reply  chan error
}

// Scheduler is a controller that coordinates the execution of the tasks specified by a model schedule.
type Scheduler struct {
	conn        *ninja.Connection
	thingClient *ninja.ServiceClient
	siteClient  *ninja.ServiceClient
	timeout     time.Duration
	dirty       bool
	siteID      string
	model       *model.Schedule
	started     map[string]*task
	stopped     chan struct{}
	shutdown    chan bool
	tasks       chan startRequest
	cancels     chan cancelRequest
	actuations  chan actuationRequest
	flush       chan struct{}
	configStore func(m *model.Schedule)
}

func (s *Scheduler) flushModel() {
	if s.dirty {
		s.configStore(s.model)
		s.dirty = false
	}
}

// The control loop of the scheduler. It is responsible for admitting
// new tasks, reaping completed tasks, cancelling running tasks.
func (s *Scheduler) loop() {
	var quit = false

	reap := make(chan string)

	for !quit || len(s.started) > 0 {
		select {
		case quit = <-s.shutdown:
			for taskID, t := range s.started {
				log.Debugf("signaled %s", taskID)
				t.quit <- struct{}{}
			}

		case _ = <-s.flush:
			s.flushModel()

		case taskID := <-reap:
			log.Debugf("reaped %s", taskID)
			delete(s.started, taskID)

		case startReq := <-s.tasks:
			taskID := startReq.model.ID
			runner := &task{}
			err := runner.init(startReq.model, s.actuations)
			if err == nil {
				s.started[taskID] = runner
				if startReq.updateModel {
					s.dirty = true
					s.model.Tasks = append(s.model.Tasks, startReq.model)
				}
				go func() {
					defer func() {
						log.Debugf("exiting %s", taskID)
						reap <- taskID
					}()
					permanentlyClosed := runner.loop()
					if permanentlyClosed {
						reply := make(chan error, 1)
						s.cancels <- cancelRequest{taskID, reply}
						_ = <-reply
						s.flush <- struct{}{}
					}
				}()
				log.Debugf("started %s", taskID)
			}
			startReq.reply <- err

		case cancelReq := <-s.cancels:
			var err error

			var found = -1
			for i, m := range s.model.Tasks {
				if m.ID == cancelReq.id {
					found = i
					break
				}
			}
			if found < 0 {
				err = fmt.Errorf("The task (%s) does not exist", cancelReq.id)
			} else {
				s.model.Tasks = append(s.model.Tasks[0:found], s.model.Tasks[found+1:]...)
				s.dirty = true
			}

			if runner, ok := s.started[cancelReq.id]; ok {
				runner.quit <- struct{}{}
			} else {
				err = fmt.Errorf("task id (%s) does not exist", cancelReq.id)
			}
			cancelReq.reply <- err

		case actuationReq := <-s.actuations:
			err := actuationReq.action.actuate(s.conn, s.thingClient, s.timeout)
			actuationReq.reply <- err
		}

	}

	s.stopped <- struct{}{}

}

// Start the scheduler. Iterate over the model schedule, creating and starting tasks for each Task model found.
func (s *Scheduler) Start(m *model.Schedule) error {
	s.model = m
	s.dirty = true
	s.shutdown = make(chan bool)
	s.started = make(map[string]*task)
	s.stopped = make(chan struct{})
	s.tasks = make(chan startRequest)
	s.cancels = make(chan cancelRequest)
	s.actuations = make(chan actuationRequest)
	s.flush = make(chan struct{})

	var loc *time.Location
	var err error
	// set the timezone of the clock
	if loc, err = time.LoadLocation(s.model.TimeZone); err != nil {
		log.Warningf("error while setting timezone (%s): %s", s.model.TimeZone, err)
		loc, _ = time.LoadLocation("Local")
		s.model.TimeZone = "Local"
	}
	clock.ResetCoordinates()
	clock.SetLocation(loc)

	// set the coordinates of the clock
	if s.model.Location != nil {
		clock.SetCoordinates(s.model.Location.Latitude, s.model.Location.Longitude, s.model.Location.Altitude)
	}

	go s.loop()

	var errors []error

	for _, t := range m.Tasks {
		reply := make(chan error)
		s.tasks <- startRequest{t, reply, false}
		err := <-reply
		if err != nil {
			errors = append(errors, err)
		}
	}

	s.flush <- struct{}{}

	if len(errors) > 1 {
		return fmt.Errorf("errors %v", errors)
	} else if len(errors) == 1 {
		return errors[0]
	} else {
		return nil
	}
}

// Stop the scheduler.
func (s *Scheduler) Stop() error {
	s.shutdown <- true
	<-s.stopped
	return nil
}

// Schedule the specified task. Starts a task controller for the specified task model.
func (s *Scheduler) Schedule(m *model.Task) error {
	reply := make(chan error)
	s.tasks <- startRequest{m, reply, true}
	err := <-reply
	return err
}

// FlushModel ensures that any pending updates to the model have been flushed back to the
// application configuration.
func (s *Scheduler) FlushModel() {
	s.flush <- struct{}{}
}

// Cancel the specified task. Stops the task controller for the specified task.
func (s *Scheduler) Cancel(taskID string) error {
	reply := make(chan error)
	s.cancels <- cancelRequest{taskID, reply}
	err := <-reply
	return err
}

// SetLogger sets the logger to be used by the scheduler component.
func (s *Scheduler) SetLogger(logger *logger.Logger) {
	if logger != nil {
		log = logger
	}
}

// SetConnection configure's the scheduler's connection and the default timeout
// for requests sent on the connection.
func (s *Scheduler) SetConnection(conn *ninja.Connection, timeout time.Duration) {
	s.conn = conn
	s.timeout = timeout
	s.thingClient = s.conn.GetServiceClient("$home/services/ThingModel")
	s.siteClient = s.conn.GetServiceClient("$home/services/SiteModel")
}

// SetConfigStore sets the function used to write updates to the schedule
func (s *Scheduler) SetConfigStore(store func(m *model.Schedule)) {
	s.configStore = store
}

// SetSiteID sets the site identifier of the scheduler
func (s *Scheduler) SetSiteID(id string) {
	s.siteID = id
}
<commit_msg>Reset the timezone and location of the schedule to the current site timezone and location.<commit_after>package controller

import (
	"fmt"
	"github.com/ninjasphere/app-scheduler/model"
	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/logger"
	nmodel "github.com/ninjasphere/go-ninja/model"
	"time"
)

var log = logger.GetLogger("")

type startRequest struct {
	model       *model.Task
	reply       chan error
	updateModel bool
}

type cancelRequest struct {
	id    string
	reply chan error
}

type actuationRequest struct {
	action *action
	reply  chan error
}

// Scheduler is a controller that coordinates the execution of the tasks specified by a model schedule.
type Scheduler struct {
	conn        *ninja.Connection
	thingClient *ninja.ServiceClient
	siteClient  *ninja.ServiceClient
	timeout     time.Duration
	dirty       bool
	siteID      string
	model       *model.Schedule
	started     map[string]*task
	stopped     chan struct{}
	shutdown    chan bool
	tasks       chan startRequest
	cancels     chan cancelRequest
	actuations  chan actuationRequest
	flush       chan struct{}
	configStore func(m *model.Schedule)
}

func (s *Scheduler) flushModel() {
	if s.dirty {
		s.configStore(s.model)
		s.dirty = false
	}
}

// The control loop of the scheduler. It is responsible for admitting
// new tasks, reaping completed tasks, cancelling running tasks.
func (s *Scheduler) loop() {
	var quit = false

	reap := make(chan string)

	for !quit || len(s.started) > 0 {
		select {
		case quit = <-s.shutdown:
			for taskID, t := range s.started {
				log.Debugf("signaled %s", taskID)
				t.quit <- struct{}{}
			}

		case _ = <-s.flush:
			s.flushModel()

		case taskID := <-reap:
			log.Debugf("reaped %s", taskID)
			delete(s.started, taskID)

		case startReq := <-s.tasks:
			taskID := startReq.model.ID
			runner := &task{}
			err := runner.init(startReq.model, s.actuations)
			if err == nil {
				s.started[taskID] = runner
				if startReq.updateModel {
					s.dirty = true
					s.model.Tasks = append(s.model.Tasks, startReq.model)
				}
				go func() {
					defer func() {
						log.Debugf("exiting %s", taskID)
						reap <- taskID
					}()
					permanentlyClosed := runner.loop()
					if permanentlyClosed {
						reply := make(chan error, 1)
						s.cancels <- cancelRequest{taskID, reply}
						_ = <-reply
						s.flush <- struct{}{}
					}
				}()
				log.Debugf("started %s", taskID)
			}
			startReq.reply <- err

		case cancelReq := <-s.cancels:
			var err error

			var found = -1
			for i, m := range s.model.Tasks {
				if m.ID == cancelReq.id {
					found = i
					break
				}
			}
			if found < 0 {
				err = fmt.Errorf("The task (%s) does not exist", cancelReq.id)
			} else {
				s.model.Tasks = append(s.model.Tasks[0:found], s.model.Tasks[found+1:]...)
				s.dirty = true
			}

			if runner, ok := s.started[cancelReq.id]; ok {
				runner.quit <- struct{}{}
			} else {
				err = fmt.Errorf("task id (%s) does not exist", cancelReq.id)
			}
			cancelReq.reply <- err

		case actuationReq := <-s.actuations:
			err := actuationReq.action.actuate(s.conn, s.thingClient, s.timeout)
			actuationReq.reply <- err
		}

	}

	s.stopped <- struct{}{}

}

// Start the scheduler. Iterate over the model schedule, creating and starting tasks for each Task model found.
func (s *Scheduler) Start(m *model.Schedule) error {
	s.model = m
	s.dirty = true
	s.shutdown = make(chan bool)
	s.started = make(map[string]*task)
	s.stopped = make(chan struct{})
	s.tasks = make(chan startRequest)
	s.cancels = make(chan cancelRequest)
	s.actuations = make(chan actuationRequest)
	s.flush = make(chan struct{})

	var err error

	// update schedule model timezon and location paramters from the current site parameters provided that they
	// are not nil or empty.

	siteModel := &nmodel.Site{}
	if err = s.siteClient.Call("fetch", s.siteID, siteModel, s.timeout); err != nil {
		return fmt.Errorf("error while retrieving model site: %v", err)
	}

	if siteModel.TimeZoneID != nil && *siteModel.TimeZoneID != "" {
		s.model.TimeZone = *siteModel.TimeZoneID
	}

	if siteModel.Latitude != nil && siteModel.Longitude != nil {
		s.model.Location = &model.Location{}
		s.model.Location.Latitude = *siteModel.Latitude
		s.model.Location.Longitude = *siteModel.Longitude
		s.model.Location.Altitude = 0.0
	}

	var loc *time.Location
	// set the timezone of the clock
	if loc, err = time.LoadLocation(s.model.TimeZone); err != nil {
		log.Warningf("error while setting timezone (%s): %s", s.model.TimeZone, err)
		loc, _ = time.LoadLocation("Local")
		s.model.TimeZone = "Local"
	}
	clock.ResetCoordinates()
	clock.SetLocation(loc)

	// set the coordinates of the clock
	if s.model.Location != nil {
		clock.SetCoordinates(s.model.Location.Latitude, s.model.Location.Longitude, s.model.Location.Altitude)
	}

	go s.loop()

	var errors []error

	for _, t := range m.Tasks {
		reply := make(chan error)
		s.tasks <- startRequest{t, reply, false}
		err := <-reply
		if err != nil {
			errors = append(errors, err)
		}
	}

	s.flush <- struct{}{}

	if len(errors) > 1 {
		return fmt.Errorf("errors %v", errors)
	} else if len(errors) == 1 {
		return errors[0]
	} else {
		return nil
	}
}

// Stop the scheduler.
func (s *Scheduler) Stop() error {
	s.shutdown <- true
	<-s.stopped
	return nil
}

// Schedule the specified task. Starts a task controller for the specified task model.
func (s *Scheduler) Schedule(m *model.Task) error {
	reply := make(chan error)
	s.tasks <- startRequest{m, reply, true}
	err := <-reply
	return err
}

// FlushModel ensures that any pending updates to the model have been flushed back to the
// application configuration.
func (s *Scheduler) FlushModel() {
	s.flush <- struct{}{}
}

// Cancel the specified task. Stops the task controller for the specified task.
func (s *Scheduler) Cancel(taskID string) error {
	reply := make(chan error)
	s.cancels <- cancelRequest{taskID, reply}
	err := <-reply
	return err
}

// SetLogger sets the logger to be used by the scheduler component.
func (s *Scheduler) SetLogger(logger *logger.Logger) {
	if logger != nil {
		log = logger
	}
}

// SetConnection configure's the scheduler's connection and the default timeout
// for requests sent on the connection.
func (s *Scheduler) SetConnection(conn *ninja.Connection, timeout time.Duration) {
	s.conn = conn
	s.timeout = timeout
	s.thingClient = s.conn.GetServiceClient("$home/services/ThingModel")
	s.siteClient = s.conn.GetServiceClient("$home/services/SiteModel")
}

// SetConfigStore sets the function used to write updates to the schedule
func (s *Scheduler) SetConfigStore(store func(m *model.Schedule)) {
	s.configStore = store
}

// SetSiteID sets the site identifier of the scheduler
func (s *Scheduler) SetSiteID(id string) {
	s.siteID = id
}
<|endoftext|>


<commit_before>// Copyright 2017 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package factom_test

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	. "github.com/FactomProject/factom"
)

var ()

func TestAckStrings(t *testing.T) {
	status := new(EntryStatus)
	status.CommitTxID = "107c239ee41bb2b0cfa19d8760deb82c942f1bac8ad99516f2f801bf16ae2998"
	//status.EntryHash = "1b363e01af0c0e28f0acbc33bc816ec11f4b28680797e74e341476409dd52295"
	gtd := new(GeneralTransactionData)
	gtd.Status = "TransactionACK"
	gtd.TransactionDateString = "2017-02-15 13:01:41"

	status.CommitData = *gtd

	entryPrintout := status.String()
	//fmt.Println(entryPrintout)

	expectedString := `TxID: 107c239ee41bb2b0cfa19d8760deb82c942f1bac8ad99516f2f801bf16ae2998
Status: TransactionACK
Date: 2017-02-15 13:01:41
`
	if entryPrintout != expectedString {
		fmt.Println(entryPrintout)
		fmt.Println(expectedString)
		t.Fail()
	}

	txstatus := new(FactoidTxStatus)
	txstatus.TxID = "b8b12fba54bd1857b0262bba1b71dbeb4e17404570c2ebe50de0dabf061d575c"
	//gtdfct := new(GeneralTransactionData)
	txstatus.Status = "TransactionACK"
	txstatus.TransactionDateString = "2017-02-15 15:07:27"
	//txstatus.CommitData = *gtdfct
	fctPrintout := txstatus.String()
	//fmt.Println(fctPrintout)

	expectedfctString := `TxID: b8b12fba54bd1857b0262bba1b71dbeb4e17404570c2ebe50de0dabf061d575c
Status: TransactionACK
Date: 2017-02-15 15:07:27
`
	if fctPrintout != expectedfctString {
		fmt.Println(fctPrintout)
		fmt.Println(expectedfctString)
		t.Fail()
	}
}

func TestAckFct(t *testing.T) {

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintln(w, `{
   "jsonrpc":"2.0",
   "id":0,
   "result":{
      "txid":"f1d9919829fa71ce18caf1bd8659cce8a06c0026d3f3fffc61054ebb25ebeaa0",
      "transactiondate":1441138021975,
      "transactiondatestring":"2015-09-01 15:07:01",
      "blockdate":1441137600000,
      "blockdatestring":"2015-09-01 15:00:00",
      "status":"DBlockConfirmed"
   }
}`)
	}))
	defer ts.Close()

	url := ts.URL[7:]
	//fmt.Println("exposed URL:",url)
	SetFactomdServer(url)

	//tx := "02015a43cc6d37010100afd7c200031cce24bcc43b596af105167de2c03603c20ada3314a7cfb47befcad4883e6fafd6e4200ceb0a10711f9fb61bc983cb4761817e4b3ff6c31ab0d5da6afb03625e368859013b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29dcc6c027a9d321129381d2d8badb3ccd591fd8a515166ca09a8a72cbf3837916c8e4789b0452dffc708ccde097163a86fd0ac23b11416cebb7ccebcdadbba908"
	//txid := "d998c577a9da5dab3d5634753db3e377e392d72d0204d31bd922df483546da4d"
	tx := "dummy1"
	txid := "dummy2"

	txStatus, _ := FactoidACK(txid, tx)
	//fmt.Println(txStatus)

	expectedfctString := `TxID: f1d9919829fa71ce18caf1bd8659cce8a06c0026d3f3fffc61054ebb25ebeaa0
Status: DBlockConfirmed
Date: 2015-09-01 15:07:01
`
	if txStatus.String() != expectedfctString {
		fmt.Println(txStatus.String())
		fmt.Println(expectedfctString)
		t.Fail()
	}
}

func TestAckEntry(t *testing.T) {

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintln(w, `{
   "jsonrpc":"2.0",
   "id":0,
   "result":{
      "committxid":"e5b5be39a41df43a3c46beaa238dc5e6f7bb11115a8da1a9b45cd694e257935a",
      "entryhash":"9228b4b080b3cf94cceea866b74c48319f2093f56bd5a63465288e9a71437ee8",
      "commitdata":{
         "transactiondate":1449547801861,
         "transactiondatestring":"2015-12-07 22:10:01",
         "blockdate":1449547800000,
         "blockdatestring":"2015-12-07 22:10:00",
         "status":"DBlockConfirmed"
      },
      "entrydata":{
         "blockdate":1449547800000,
         "blockdatestring":"2015-12-07 22:10:00",
         "status":"DBlockConfirmed"
      }
   }
}`)
	}))
	defer ts.Close()

	url := ts.URL[7:]
	//fmt.Println("exposed URL:",url)
	SetFactomdServer(url)

	tx := "dummy1"
	txid := "dummy2"

	entryStatus, _ := EntryACK(txid, tx)
	//fmt.Println(entryStatus)

	expectedEntryString := `TxID: e5b5be39a41df43a3c46beaa238dc5e6f7bb11115a8da1a9b45cd694e257935a
Status: DBlockConfirmed
Date: 2015-12-07 22:10:01
`
	if entryStatus.String() != expectedEntryString {
		fmt.Println(entryStatus.String())
		fmt.Println(expectedEntryString)
		t.Fail()
	}
}
<commit_msg>fix ack test<commit_after>// Copyright 2017 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package factom_test

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	. "github.com/FactomProject/factom"
)

var ()

func TestAckStrings(t *testing.T) {
	status := new(EntryStatus)
	status.CommitTxID = "107c239ee41bb2b0cfa19d8760deb82c942f1bac8ad99516f2f801bf16ae2998"
	//status.EntryHash = "1b363e01af0c0e28f0acbc33bc816ec11f4b28680797e74e341476409dd52295"
	gtd := new(GeneralTransactionData)
	gtd.Status = "TransactionACK"
	gtd.TransactionDateString = "2017-02-15 13:01:41"

	status.CommitData = *gtd

	entryPrintout := status.String()
	//fmt.Println(entryPrintout)

	expectedString := `TxID: 107c239ee41bb2b0cfa19d8760deb82c942f1bac8ad99516f2f801bf16ae2998
Status: TransactionACK
Date: 2017-02-15 13:01:41
`
	if entryPrintout != expectedString {
		fmt.Println(entryPrintout)
		fmt.Println(expectedString)
		t.Fail()
	}

	txstatus := new(FactoidTxStatus)
	txstatus.TxID = "b8b12fba54bd1857b0262bba1b71dbeb4e17404570c2ebe50de0dabf061d575c"
	//gtdfct := new(GeneralTransactionData)
	txstatus.Status = "TransactionACK"
	txstatus.TransactionDateString = "2017-02-15 15:07:27"
	//txstatus.CommitData = *gtdfct
	fctPrintout := txstatus.String()
	//fmt.Println(fctPrintout)

	expectedfctString := `TxID: b8b12fba54bd1857b0262bba1b71dbeb4e17404570c2ebe50de0dabf061d575c
Status: TransactionACK
Date: 2017-02-15 15:07:27
`
	if fctPrintout != expectedfctString {
		fmt.Println(fctPrintout)
		fmt.Println(expectedfctString)
		t.Fail()
	}
}

func TestAckFct(t *testing.T) {

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintln(w, `{
   "jsonrpc":"2.0",
   "id":0,
   "result":{
      "txid":"f1d9919829fa71ce18caf1bd8659cce8a06c0026d3f3fffc61054ebb25ebeaa0",
      "transactiondate":1441138021975,
      "transactiondatestring":"2015-09-01 15:07:01",
      "blockdate":1441137600000,
      "blockdatestring":"2015-09-01 15:00:00",
      "status":"DBlockConfirmed"
   }
}`)
	}))
	defer ts.Close()

	url := ts.URL[7:]
	//fmt.Println("exposed URL:",url)
	SetFactomdServer(url)

	//tx := "02015a43cc6d37010100afd7c200031cce24bcc43b596af105167de2c03603c20ada3314a7cfb47befcad4883e6fafd6e4200ceb0a10711f9fb61bc983cb4761817e4b3ff6c31ab0d5da6afb03625e368859013b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29dcc6c027a9d321129381d2d8badb3ccd591fd8a515166ca09a8a72cbf3837916c8e4789b0452dffc708ccde097163a86fd0ac23b11416cebb7ccebcdadbba908"
	//txid := "d998c577a9da5dab3d5634753db3e377e392d72d0204d31bd922df483546da4d"
	tx := "dummy1"
	txid := "dummy2"

	txStatus, _ := FactoidACK(txid, tx)
	//fmt.Println(txStatus)

	expectedfctString := `TxID: f1d9919829fa71ce18caf1bd8659cce8a06c0026d3f3fffc61054ebb25ebeaa0
Status: DBlockConfirmed
Date: 2015-09-01 15:07:01
`
	if txStatus.String() != expectedfctString {
		fmt.Println(txStatus.String())
		fmt.Println(expectedfctString)
		t.Fail()
	}
}

func TestAckEntry(t *testing.T) {

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintln(w, `{
   "jsonrpc":"2.0",
   "id":0,
   "result":{
      "committxid":"e5b5be39a41df43a3c46beaa238dc5e6f7bb11115a8da1a9b45cd694e257935a",
      "entryhash":"9228b4b080b3cf94cceea866b74c48319f2093f56bd5a63465288e9a71437ee8",
      "commitdata":{
         "transactiondate":1449547801861,
         "transactiondatestring":"2015-12-07 22:10:01",
         "blockdate":1449547800000,
         "blockdatestring":"2015-12-07 22:10:00",
         "status":"DBlockConfirmed"
      },
      "entrydata":{
         "blockdate":1449547800000,
         "blockdatestring":"2015-12-07 22:10:00",
         "status":"DBlockConfirmed"
      }
   }
}`)
	}))
	defer ts.Close()

	url := ts.URL[7:]
	//fmt.Println("exposed URL:",url)
	SetFactomdServer(url)

	tx := "dummy1"
	txid := "dummy2"

	entryStatus, _ := EntryACK(txid, tx)
	//fmt.Println(entryStatus)

	expectedEntryString := `EntryHash: 9228b4b080b3cf94cceea866b74c48319f2093f56bd5a63465288e9a71437ee8
Status: DBlockConfirmed
Date:
TxID: e5b5be39a41df43a3c46beaa238dc5e6f7bb11115a8da1a9b45cd694e257935a
Status: DBlockConfirmed
Date: 2015-12-07 22:10:01
`
	if entryStatus.String() != expectedEntryString {
		fmt.Println(entryStatus.String())
		fmt.Println(expectedEntryString)
		t.Fail()
	}
}
<|endoftext|>