<commit_before>package main

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/spf13/cobra"

	"github.com/restic/restic/internal/checker"
	"github.com/restic/restic/internal/errors"
	"github.com/restic/restic/internal/restic"
)

var cmdCheck = &cobra.Command{
	Use:   "check [flags]",
	Short: "Check the repository for errors",
	Long: `
The "check" command tests the repository for errors and reports any errors it
finds. It can also be used to read all data and therefore simulate a restore.
`,
	DisableAutoGenTag: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runCheck(checkOptions, globalOptions, args)
	},
}

// CheckOptions bundles all options for the 'check' command.
type CheckOptions struct {
	ReadData    bool
	CheckUnused bool
}

var checkOptions CheckOptions

func init() {
	cmdRoot.AddCommand(cmdCheck)

	f := cmdCheck.Flags()
	f.BoolVar(&checkOptions.ReadData, "read-data", false, "read all data blobs")
	f.BoolVar(&checkOptions.CheckUnused, "check-unused", false, "find unused blobs")
}

func newReadProgress(gopts GlobalOptions, todo restic.Stat) *restic.Progress {
	if gopts.Quiet {
		return nil
	}

	readProgress := restic.NewProgress()

	readProgress.OnUpdate = func(s restic.Stat, d time.Duration, ticker bool) {
		status := fmt.Sprintf("[%s] %s  %d / %d items",
			formatDuration(d),
			formatPercent(s.Blobs, todo.Blobs),
			s.Blobs, todo.Blobs)

		if w := stdoutTerminalWidth(); w > 0 {
			if len(status) > w {
				max := w - len(status) - 4
				status = status[:max] + "... "
			}
		}

		PrintProgress("%s", status)
	}

	readProgress.OnDone = func(s restic.Stat, d time.Duration, ticker bool) {
		fmt.Printf("\nduration: %s\n", formatDuration(d))
	}

	return readProgress
}

func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
	if len(args) != 0 {
		return errors.Fatal("check has no arguments")
	}

	repo, err := OpenRepository(gopts)
	if err != nil {
		return err
	}

	if !gopts.NoLock {
		Verbosef("Create exclusive lock for repository\n")
		lock, err := lockRepoExclusive(repo)
		defer unlockRepo(lock)
		if err != nil {
			return err
		}
	}

	chkr := checker.New(repo)

	Verbosef("Load indexes\n")
	hints, errs := chkr.LoadIndex(context.TODO())

	dupFound := false
	for _, hint := range hints {
		Printf("%v\n", hint)
		if _, ok := hint.(checker.ErrDuplicatePacks); ok {
			dupFound = true
		}
	}

	if dupFound {
		Printf("\nrun `restic rebuild-index' to correct this\n")
	}

	if len(errs) > 0 {
		for _, err := range errs {
			Warnf("error: %v\n", err)
		}
		return errors.Fatal("LoadIndex returned errors")
	}

	errorsFound := false
	errChan := make(chan error)

	Verbosef("Check all packs\n")
	go chkr.Packs(context.TODO(), errChan)

	for err := range errChan {
		errorsFound = true
		fmt.Fprintf(os.Stderr, "%v\n", err)
	}

	Verbosef("Check snapshots, trees and blobs\n")
	errChan = make(chan error)
	go chkr.Structure(context.TODO(), errChan)

	for err := range errChan {
		errorsFound = true
		if e, ok := err.(checker.TreeError); ok {
			fmt.Fprintf(os.Stderr, "error for tree %v:\n", e.ID.Str())
			for _, treeErr := range e.Errors {
				fmt.Fprintf(os.Stderr, "  %v\n", treeErr)
			}
		} else {
			fmt.Fprintf(os.Stderr, "error: %v\n", err)
		}
	}

	if opts.CheckUnused {
		for _, id := range chkr.UnusedBlobs() {
			Verbosef("unused blob %v\n", id.Str())
			errorsFound = true
		}
	}

	if opts.ReadData {
		Verbosef("Read all data\n")

		p := newReadProgress(gopts, restic.Stat{Blobs: chkr.CountPacks()})
		errChan := make(chan error)

		go chkr.ReadData(context.TODO(), p, errChan)

		for err := range errChan {
			errorsFound = true
			fmt.Fprintf(os.Stderr, "%v\n", err)
		}
	}

	if errorsFound {
		return errors.Fatal("repository contains errors")
	}
	return nil
}
<commit_msg>check: Disable cache by default<commit_after>package main

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/spf13/cobra"

	"github.com/restic/restic/internal/checker"
	"github.com/restic/restic/internal/errors"
	"github.com/restic/restic/internal/restic"
)

var cmdCheck = &cobra.Command{
	Use:   "check [flags]",
	Short: "Check the repository for errors",
	Long: `
The "check" command tests the repository for errors and reports any errors it
finds. It can also be used to read all data and therefore simulate a restore.

By default, the "check" command will always load all data directly from the
repository and not use a local cache.
`,
	DisableAutoGenTag: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runCheck(checkOptions, globalOptions, args)
	},
}

// CheckOptions bundles all options for the 'check' command.
type CheckOptions struct {
	ReadData    bool
	CheckUnused bool
	WithCache   bool
}

var checkOptions CheckOptions

func init() {
	cmdRoot.AddCommand(cmdCheck)

	f := cmdCheck.Flags()
	f.BoolVar(&checkOptions.ReadData, "read-data", false, "read all data blobs")
	f.BoolVar(&checkOptions.CheckUnused, "check-unused", false, "find unused blobs")
	f.BoolVar(&checkOptions.WithCache, "with-cache", false, "use the cache")
}

func newReadProgress(gopts GlobalOptions, todo restic.Stat) *restic.Progress {
	if gopts.Quiet {
		return nil
	}

	readProgress := restic.NewProgress()

	readProgress.OnUpdate = func(s restic.Stat, d time.Duration, ticker bool) {
		status := fmt.Sprintf("[%s] %s  %d / %d items",
			formatDuration(d),
			formatPercent(s.Blobs, todo.Blobs),
			s.Blobs, todo.Blobs)

		if w := stdoutTerminalWidth(); w > 0 {
			if len(status) > w {
				max := w - len(status) - 4
				status = status[:max] + "... "
			}
		}

		PrintProgress("%s", status)
	}

	readProgress.OnDone = func(s restic.Stat, d time.Duration, ticker bool) {
		fmt.Printf("\nduration: %s\n", formatDuration(d))
	}

	return readProgress
}

func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
	if len(args) != 0 {
		return errors.Fatal("check has no arguments")
	}

	if !opts.WithCache {
		// do not use a cache for the checker
		gopts.NoCache = true
	}

	repo, err := OpenRepository(gopts)
	if err != nil {
		return err
	}

	if !gopts.NoLock {
		Verbosef("Create exclusive lock for repository\n")
		lock, err := lockRepoExclusive(repo)
		defer unlockRepo(lock)
		if err != nil {
			return err
		}
	}

	chkr := checker.New(repo)

	Verbosef("Load indexes\n")
	hints, errs := chkr.LoadIndex(context.TODO())

	dupFound := false
	for _, hint := range hints {
		Printf("%v\n", hint)
		if _, ok := hint.(checker.ErrDuplicatePacks); ok {
			dupFound = true
		}
	}

	if dupFound {
		Printf("\nrun `restic rebuild-index' to correct this\n")
	}

	if len(errs) > 0 {
		for _, err := range errs {
			Warnf("error: %v\n", err)
		}
		return errors.Fatal("LoadIndex returned errors")
	}

	errorsFound := false
	errChan := make(chan error)

	Verbosef("Check all packs\n")
	go chkr.Packs(context.TODO(), errChan)

	for err := range errChan {
		errorsFound = true
		fmt.Fprintf(os.Stderr, "%v\n", err)
	}

	Verbosef("Check snapshots, trees and blobs\n")
	errChan = make(chan error)
	go chkr.Structure(context.TODO(), errChan)

	for err := range errChan {
		errorsFound = true
		if e, ok := err.(checker.TreeError); ok {
			fmt.Fprintf(os.Stderr, "error for tree %v:\n", e.ID.Str())
			for _, treeErr := range e.Errors {
				fmt.Fprintf(os.Stderr, "  %v\n", treeErr)
			}
		} else {
			fmt.Fprintf(os.Stderr, "error: %v\n", err)
		}
	}

	if opts.CheckUnused {
		for _, id := range chkr.UnusedBlobs() {
			Verbosef("unused blob %v\n", id.Str())
			errorsFound = true
		}
	}

	if opts.ReadData {
		Verbosef("Read all data\n")

		p := newReadProgress(gopts, restic.Stat{Blobs: chkr.CountPacks()})
		errChan := make(chan error)

		go chkr.ReadData(context.TODO(), p, errChan)

		for err := range errChan {
			errorsFound = true
			fmt.Fprintf(os.Stderr, "%v\n", err)
		}
	}

	if errorsFound {
		return errors.Fatal("repository contains errors")
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"text/tabwriter"

	tcg "github.com/bluecmd/go-tcg-storage/pkg/core"
	"github.com/bluecmd/go-tcg-storage/pkg/drive"
)

var (
	outputFmt = flag.String("output", "table", "Output format; one of [table, json, openmetrics]")
	noHeader  = flag.Bool("no-header", false, "Supress the header in table format output")
)

type DeviceState struct {
	Device   string
	Identity *drive.Identity
	Level0   *tcg.Level0Discovery
}

type Devices []DeviceState

func main() {
	flag.Usage = func() {
		fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0])
		fmt.Println()
		flag.PrintDefaults()
		fmt.Println()
		fmt.Println("The following state flags might be shown:")
		fmt.Println("  L/l - Locking is supported and is enabled (L) or disabled (l)")
		fmt.Println("  M/m - MBR is enabled and is active (M) or hidden (m)")
		fmt.Println("  E   - The device has media encryption")
		fmt.Println("  P   - The Admin SP SID PIN is set to MSID [Block SID feature specific]")
		fmt.Println("  !   - Authentication to Admin SP is blocked [Block SID feature specific]")
		fmt.Println()
	}
	flag.Parse()

	sysblk, err := ioutil.ReadDir("/sys/class/block/")
	if err != nil {
		log.Printf("Failed to enumerate block devices: %v", err)
		return
	}

	var state Devices

	for _, fi := range sysblk {
		devname := fi.Name()
		if _, err := os.Stat(filepath.Join("/sys/class/block", devname, "device")); os.IsNotExist(err) {
			continue
		}
		devpath := filepath.Join("/dev", devname)
		if _, err := os.Stat(devpath); os.IsNotExist(err) {
			log.Printf("Failed to find device node %s", devpath)
			continue
		}

		d, err := drive.Open(devpath)
		if err != nil {
			log.Printf("drive.Open(%s): %v", devpath, err)
			continue
		}
		defer d.Close()
		identity, err := d.Identify()
		if err != nil {
			log.Printf("drive.Identify(%s): %v", devpath, err)
		}
		d0, err := tcg.Discovery0(d)
		if err != nil {
			if err != tcg.ErrNotSupported {
				log.Printf("tcg.Discovery0(%s): %v", devpath, err)
				continue
			}
			d0 = nil
		}
		state = append(state, DeviceState{
			Device:   devpath,
			Identity: identity,
			Level0:   d0,
		})
	}

	if *outputFmt == "json" {
		outputJSON(state)
	} else if *outputFmt == "openmetrics" {
		outputMetrics(state)
	} else {
		outputTable(state)
	}
}

func outputJSON(state Devices) {
	b, err := json.MarshalIndent(state, "", "  ")
	if err != nil {
		log.Fatalf("Failed to marshal JSON: %v", err)
	}
	os.Stdout.Write(b)
}

func sscFeatures(l0 *tcg.Level0Discovery) []string {
	feat := []string{}
	if l0.Enterprise != nil {
		feat = append(feat, "Enterprise")
	}
	if l0.OpalV1 != nil {
		feat = append(feat, "Opal 1")
	}
	if l0.OpalV2 != nil {
		feat = append(feat, "Opal 2")
	}
	if l0.Opalite != nil {
		feat = append(feat, "Opalite")
	}
	if l0.PyriteV1 != nil {
		feat = append(feat, "Pyrite 1")
	}
	if l0.PyriteV2 != nil {
		feat = append(feat, "Pyrite 2")
	}
	if l0.RubyV1 != nil {
		feat = append(feat, "Ruby 1")
	}
	return feat
}

func outputTable(state Devices) {
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
	if *noHeader == false {
		fmt.Fprintf(w, "DEVICE\tMODEL\tSERIAL\tFIRMWARE\tPROTOCOL\tSSC\tSTATE\n")
	}
	for _, s := range state {
		feat := []string{}
		state := ""
		if s.Level0 != nil {
			feat = sscFeatures(s.Level0)
			if l := s.Level0.Locking; l != nil {
				if l.LockingEnabled {
					state += "L"
				} else if l.LockingSupported {
					state += "l"
				}

				if l.MBREnabled {
					if l.MBRDone {
						state += "m"
					} else {
						state += "M"
					}
				}
				if l.MediaEncryption {
					state += "E"
				}
			}
			if b := s.Level0.BlockSID; b != nil {
				if !b.SIDValueState {
					state += "P"
				}
				if b.SIDAuthenticationBlockedState {
					state += "!"
				}
			}
		} else {
			state = "-"
			feat = []string{"-"}
		}

		fmt.Fprint(w,
			s.Device, "\t",
			s.Identity.Model, "\t",
			s.Identity.SerialNumber, "\t",
			s.Identity.Firmware, "\t",
			s.Identity.Protocol, "\t",
			strings.Join(feat, ","), "\t",
			state, "\t",
			"\n")
	}
	w.Flush()
}
<commit_msg>fix(tcgdiskstat): Error on invalid output format (#41)<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"text/tabwriter"

	tcg "github.com/bluecmd/go-tcg-storage/pkg/core"
	"github.com/bluecmd/go-tcg-storage/pkg/drive"
)

var (
	outputFmt = flag.String("output", "table", "Output format; one of [table, json, openmetrics]")
	noHeader  = flag.Bool("no-header", false, "Supress the header in table format output")
)

type DeviceState struct {
	Device   string
	Identity *drive.Identity
	Level0   *tcg.Level0Discovery
}

type Devices []DeviceState

func main() {
	flag.Usage = func() {
		fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0])
		fmt.Println()
		flag.PrintDefaults()
		fmt.Println()
		fmt.Println("The following state flags might be shown:")
		fmt.Println("  L/l - Locking is supported and is enabled (L) or disabled (l)")
		fmt.Println("  M/m - MBR is enabled and is active (M) or hidden (m)")
		fmt.Println("  E   - The device has media encryption")
		fmt.Println("  P   - The Admin SP SID PIN is set to MSID [Block SID feature specific]")
		fmt.Println("  !   - Authentication to Admin SP is blocked [Block SID feature specific]")
		fmt.Println()
	}
	flag.Parse()

	sysblk, err := ioutil.ReadDir("/sys/class/block/")
	if err != nil {
		log.Printf("Failed to enumerate block devices: %v", err)
		return
	}

	var state Devices

	for _, fi := range sysblk {
		devname := fi.Name()
		if _, err := os.Stat(filepath.Join("/sys/class/block", devname, "device")); os.IsNotExist(err) {
			continue
		}
		devpath := filepath.Join("/dev", devname)
		if _, err := os.Stat(devpath); os.IsNotExist(err) {
			log.Printf("Failed to find device node %s", devpath)
			continue
		}

		d, err := drive.Open(devpath)
		if err != nil {
			log.Printf("drive.Open(%s): %v", devpath, err)
			continue
		}
		defer d.Close()
		identity, err := d.Identify()
		if err != nil {
			log.Printf("drive.Identify(%s): %v", devpath, err)
		}
		d0, err := tcg.Discovery0(d)
		if err != nil {
			if err != tcg.ErrNotSupported {
				log.Printf("tcg.Discovery0(%s): %v", devpath, err)
				continue
			}
			d0 = nil
		}
		state = append(state, DeviceState{
			Device:   devpath,
			Identity: identity,
			Level0:   d0,
		})
	}

	if *outputFmt == "json" {
		outputJSON(state)
	} else if *outputFmt == "openmetrics" {
		outputMetrics(state)
	} else if *outputFmt == "table" {
		outputTable(state)
	} else {
		fmt.Printf("Unsupported output format %q\n", *outputFmt)
		flag.Usage()
		os.Exit(2)
	}
}

func outputJSON(state Devices) {
	b, err := json.MarshalIndent(state, "", "  ")
	if err != nil {
		log.Fatalf("Failed to marshal JSON: %v", err)
	}
	os.Stdout.Write(b)
}

func sscFeatures(l0 *tcg.Level0Discovery) []string {
	feat := []string{}
	if l0.Enterprise != nil {
		feat = append(feat, "Enterprise")
	}
	if l0.OpalV1 != nil {
		feat = append(feat, "Opal 1")
	}
	if l0.OpalV2 != nil {
		feat = append(feat, "Opal 2")
	}
	if l0.Opalite != nil {
		feat = append(feat, "Opalite")
	}
	if l0.PyriteV1 != nil {
		feat = append(feat, "Pyrite 1")
	}
	if l0.PyriteV2 != nil {
		feat = append(feat, "Pyrite 2")
	}
	if l0.RubyV1 != nil {
		feat = append(feat, "Ruby 1")
	}
	return feat
}

func outputTable(state Devices) {
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
	if *noHeader == false {
		fmt.Fprintf(w, "DEVICE\tMODEL\tSERIAL\tFIRMWARE\tPROTOCOL\tSSC\tSTATE\n")
	}
	for _, s := range state {
		feat := []string{}
		state := ""
		if s.Level0 != nil {
			feat = sscFeatures(s.Level0)
			if l := s.Level0.Locking; l != nil {
				if l.LockingEnabled {
					state += "L"
				} else if l.LockingSupported {
					state += "l"
				}

				if l.MBREnabled {
					if l.MBRDone {
						state += "m"
					} else {
						state += "M"
					}
				}
				if l.MediaEncryption {
					state += "E"
				}
			}
			if b := s.Level0.BlockSID; b != nil {
				if !b.SIDValueState {
					state += "P"
				}
				if b.SIDAuthenticationBlockedState {
					state += "!"
				}
			}
		} else {
			state = "-"
			feat = []string{"-"}
		}

		fmt.Fprint(w,
			s.Device, "\t",
			s.Identity.Model, "\t",
			s.Identity.SerialNumber, "\t",
			s.Identity.Firmware, "\t",
			s.Identity.Protocol, "\t",
			strings.Join(feat, ","), "\t",
			state, "\t",
			"\n")
	}
	w.Flush()
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/msoap/raphanus"
	tgbotapi "gopkg.in/telegram-bot-api.v2"
)

const (
	// Version - current version
	Version = "1.6"

	// DefaultBotTimeout - bot default timeout
	DefaultBotTimeout = 60

	// MessagesQueueSize - size of channel for bot messages
	MessagesQueueSize = 10

	// MaxMessageLength - max length of one bot message
	MaxMessageLength = 4096

	// SecondsForAutoSaveUsersToDB - save users to file every 1 min (if need)
	SecondsForAutoSaveUsersToDB = 60

	// DBFileName - DB json name
	DBFileName = "shell2telegram.json"

	// shell2telegram command name for get plain text without /command
	cmdPlainText = "/:plain_text"
)

// Command - one user command
type Command struct {
	shellCmd    string   // shell command
	description string   // command description for list in /help (/cmd:desc="Command name")
	vars        []string // environment vars for user text, split by `/s+` to vars (/cmd:vars=SUBCOMMAND,ARGS)
	isMarkdown  bool     // send message in markdown format
}

// Commands - list of all commands
type Commands map[string]Command

// Config - config struct
type Config struct {
	token                  string   // bot token
	botTimeout             int      // bot timeout
	predefinedAllowedUsers []string // telegram users who are allowed to chat with the bot
	predefinedRootUsers    []string // telegram users, who confirms new users in their private chat
	description            string   // description of bot
	usersDB                string   // file for store users
	shell                  string   // custom shell
	cache                  int      // caching command out (in seconds)
	shTimeout              int      // timeout for execute shell command (in seconds)
	addExit                bool     // adding /shell2telegram exit command
	allowAll               bool     // allow all user (DANGEROUS!)
	logCommands            bool     // logging all commands
	persistentUsers        bool     // load/save users from file
	isPublicBot            bool     // bot is public (dont add /auth* commands)
}

// message types
const (
	msgIsText int8 = iota
	msgIsPhoto
)

// BotMessage - record for send via channel for send message to telegram chat
type BotMessage struct {
	message     string
	fileName    string
	photo       []byte
	chatID      int
	messageType int8
	isMarkdown  bool
}

// ----------------------------------------------------------------------------
// get config
func getConfig() (commands Commands, appConfig Config, err error) {
	flag.StringVar(&appConfig.token, "tb-token", "", "setting bot token (or set TB_TOKEN variable)")
	flag.BoolVar(&appConfig.addExit, "add-exit", false, "adding \"/shell2telegram exit\" command for terminate bot (for roots only)")
	flag.IntVar(&appConfig.botTimeout, "timeout", DefaultBotTimeout, "setting timeout for bot")
	flag.BoolVar(&appConfig.allowAll, "allow-all", false, "allow all users (DANGEROUS!)")
	flag.BoolVar(&appConfig.logCommands, "log-commands", false, "logging all commands")
	flag.StringVar(&appConfig.description, "description", "", "setting description of bot")
	flag.BoolVar(&appConfig.persistentUsers, "persistent-users", false, "load/save users from file (default ~/.config/shell2telegram.json)")
	flag.StringVar(&appConfig.usersDB, "users-db", "", "file for store users")
	flag.IntVar(&appConfig.cache, "cache", 0, "caching command out (in seconds)")
	flag.BoolVar(&appConfig.isPublicBot, "public", false, "bot is public (dont add /auth* commands)")
	flag.IntVar(&appConfig.shTimeout, "sh-timeout", 0, "set timeout for execute shell command (in seconds)")
	flag.StringVar(&appConfig.shell, "shell", "sh", "custom shell or \"\" for execute without shell")
	logFilename := flag.String("log", "", "log filename, default - STDOUT")
	predefinedAllowedUsers := flag.String("allow-users", "", "telegram users who are allowed to chat with the bot (\"user1,user2\")")
	predefinedRootUsers := flag.String("root-users", "", "telegram users, who confirms new users in their private chat (\"user1,user2\")")
	version := flag.Bool("version", false, "get version")

	flag.Usage = func() {
		fmt.Printf("usage: %s [options] %s\n%s\n%s\n\noptions:\n",
			os.Args[0],
			`/chat_command "shell command" /chat_command2 "shell command2"`,
			"All text after /chat_command will be sent to STDIN of shell command.",
			"If chat command is /:plain_text - get user message without any /command (for private chats only)",
		)
		flag.PrintDefaults()
		os.Exit(0)
	}
	flag.Parse()

	if *version {
		fmt.Println(Version)
		os.Exit(0)
	}

	// setup log file
	if len(*logFilename) > 0 {
		fhLog, err := os.OpenFile(*logFilename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
		if err != nil {
			log.Fatalf("error opening log file: %v", err)
		}
		log.SetOutput(fhLog)
	}

	// setup users and roots
	if *predefinedAllowedUsers != "" {
		appConfig.predefinedAllowedUsers = strings.Split(*predefinedAllowedUsers, ",")
	}
	if *predefinedRootUsers != "" {
		appConfig.predefinedRootUsers = strings.Split(*predefinedRootUsers, ",")
	}

	commands = Commands{}
	// need >= 2 arguments and count of it must be even
	args := flag.Args()
	if len(args) < 2 || len(args)%2 == 1 {
		return commands, appConfig, fmt.Errorf("error: need pairs of /chat-command and shell-command")
	}

	for i := 0; i < len(args); i += 2 {
		path, command, err := parseBotCommand(args[i], args[i+1]) // (/path, shell_command)
		if err != nil {
			return commands, appConfig, err
		}
		commands[path] = command
	}

	if appConfig.token == "" {
		if appConfig.token = os.Getenv("TB_TOKEN"); appConfig.token == "" {
			return commands, appConfig, fmt.Errorf("TB_TOKEN environment var not found. See https://core.telegram.org/bots#botfather for more information")
		}
	}

	return commands, appConfig, nil
}

// ----------------------------------------------------------------------------
func sendMessage(messageSignal chan<- BotMessage, chatID int, message []byte, isMarkdown bool) {
	go func() {
		var fileName string
		fileType := http.DetectContentType(message)
		switch fileType {
		case "image/png":
			fileName = "file.png"
		case "image/jpeg":
			fileName = "file.jpeg"
		case "image/gif":
			fileName = "file.gif"
		case "image/bmp":
			fileName = "file.bmp"
		default:
			fileName = "message"
		}

		if fileName == "message" {

			// is text message
			messageString := string(message)
			var messagesList []string

			if len(messageString) <= MaxMessageLength {
				messagesList = []string{messageString}
			} else {
				messagesList = splitStringLinesBySize(messageString, MaxMessageLength)
			}

			for _, messageChunk := range messagesList {
				messageSignal <- BotMessage{
					chatID:      chatID,
					messageType: msgIsText,
					message:     messageChunk,
					isMarkdown:  isMarkdown,
				}
			}

		} else {
			// is image
			messageSignal <- BotMessage{
				chatID:      chatID,
				messageType: msgIsPhoto,
				fileName:    fileName,
				photo:       message,
			}
		}
	}()
}

// ----------------------------------------------------------------------------
func main() {
	commands, appConfig, err := getConfig()
	if err != nil {
		log.Fatal(err)
	}

	bot, err := tgbotapi.NewBotAPI(appConfig.token)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Authorized on bot account: @%s", bot.Self.UserName)

	tgbotConfig := tgbotapi.NewUpdate(0)
	tgbotConfig.Timeout = appConfig.botTimeout
	botUpdatesChan, err := bot.GetUpdatesChan(tgbotConfig)
	if err != nil {
		log.Fatal(err)
	}

	users := NewUsers(appConfig)
	messageSignal := make(chan BotMessage, MessagesQueueSize)
	vacuumTicker := time.Tick(SecondsForOldUsersBeforeVacuum * time.Second)
	saveToBDTicker := make(<-chan time.Time)
	exitSignal := make(chan struct{})
	systemExitSignal := make(chan os.Signal, 1)
	signal.Notify(systemExitSignal, os.Interrupt)

	if appConfig.persistentUsers {
		saveToBDTicker = time.Tick(SecondsForAutoSaveUsersToDB * time.Second)
	}

	var cache raphanus.DB
	if appConfig.cache > 0 {
		cache = raphanus.New("", 0)
	}

	// all /shell2telegram sub-commands handlers
	internalCommands := map[string]func(Ctx) string{
		"stat":              cmdShell2telegramStat,
		"ban":               cmdShell2telegramBan,
		"search":            cmdShell2telegramSearch,
		"desc":              cmdShell2telegramDesc,
		"rm":                cmdShell2telegramRm,
		"exit":              cmdShell2telegramExit,
		"version":           cmdShell2telegramVersion,
		"broadcast_to_root": cmdShell2telegramBroadcastToRoot,
		"message_to_user":   cmdShell2telegramMessageToUser,
	}

	doExit := false
	for !doExit {
		select {
		case telegramUpdate := <-botUpdatesChan:

			var messageCmd, messageArgs string
			allUserMessage := telegramUpdate.Message.Text
			if len(allUserMessage) > 0 && allUserMessage[0] == '/' {
				messageCmd, messageArgs = splitStringHalfBySpace(allUserMessage)
			} else {
				messageCmd, messageArgs = cmdPlainText, allUserMessage
			}

			allowPlainText := false
			if _, ok := commands[cmdPlainText]; ok {
				allowPlainText = true
			}

			replayMsg := ""

			if len(messageCmd) > 0 && (messageCmd != cmdPlainText || allowPlainText) {

				users.AddNew(telegramUpdate.Message)
				userID := telegramUpdate.Message.From.ID
				allowExec := appConfig.allowAll || users.IsAuthorized(userID)

				ctx := Ctx{
					appConfig:     &appConfig,
					users:         &users,
					commands:      commands,
					userID:        userID,
					allowExec:     allowExec,
					messageCmd:    messageCmd,
					messageArgs:   messageArgs,
					messageSignal: messageSignal,
					chatID:        telegramUpdate.Message.Chat.ID,
					exitSignal:    exitSignal,
					cache:         &cache,
				}

				switch {
				// commands .................................
				case !appConfig.isPublicBot && (messageCmd == "/auth" || messageCmd == "/authroot"):
					replayMsg = cmdAuth(ctx)

				case messageCmd == "/help":
					replayMsg = cmdHelp(ctx)

				case messageCmd == "/shell2telegram" && users.IsRoot(userID):
					var messageSubCmd string
					messageSubCmd, messageArgs = splitStringHalfBySpace(messageArgs)
					ctx.messageArgs = messageArgs
					if cmdHandler, ok := internalCommands[messageSubCmd]; ok {
						replayMsg = cmdHandler(ctx)
					} else {
						replayMsg = "Sub-command not found"
					}

				case allowExec && (allowPlainText && messageCmd == cmdPlainText || messageCmd[0] == '/'):
					cmdUser(ctx)

				} // switch for commands

				if appConfig.logCommands {
					log.Printf("%s: %s", users.String(userID), allUserMessage)
				}

				sendMessage(messageSignal, telegramUpdate.Message.Chat.ID, []byte(replayMsg), false)
			}

		case botMessage := <-messageSignal:
			switch {
			case botMessage.messageType == msgIsText && !stringIsEmpty(botMessage.message):
				messageConfig := tgbotapi.NewMessage(botMessage.chatID, botMessage.message)
				if botMessage.isMarkdown {
					messageConfig.ParseMode = tgbotapi.ModeMarkdown
				}
				_, err = bot.Send(messageConfig)
			case botMessage.messageType == msgIsPhoto && len(botMessage.photo) > 0:
				bytesPhoto := tgbotapi.FileBytes{Name: botMessage.fileName, Bytes: botMessage.photo}
				_, err = bot.Send(tgbotapi.NewPhotoUpload(botMessage.chatID, bytesPhoto))
			}

			if err != nil {
				log.Print("Bot send message error: ", err)
			}

		case <-saveToBDTicker:
			users.SaveToDB(appConfig.usersDB)

		case <-vacuumTicker:
			users.ClearOldUsers()

		case <-systemExitSignal:
			go func() {
				exitSignal <- struct{}{}
			}()

		case <-exitSignal:
			if appConfig.persistentUsers {
				users.needSaveDB = true
				users.SaveToDB(appConfig.usersDB)
			}
			doExit = true
		}
	}
}
<commit_msg>Use new New() method of raphanus cache<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/msoap/raphanus"
	tgbotapi "gopkg.in/telegram-bot-api.v2"
)

const (
	// Version - current version
	Version = "1.6"

	// DefaultBotTimeout - bot default timeout
	DefaultBotTimeout = 60

	// MessagesQueueSize - size of channel for bot messages
	MessagesQueueSize = 10

	// MaxMessageLength - max length of one bot message
	MaxMessageLength = 4096

	// SecondsForAutoSaveUsersToDB - save users to file every 1 min (if need)
	SecondsForAutoSaveUsersToDB = 60

	// DBFileName - DB json name
	DBFileName = "shell2telegram.json"

	// shell2telegram command name for get plain text without /command
	cmdPlainText = "/:plain_text"
)

// Command - one user command
type Command struct {
	shellCmd    string   // shell command
	description string   // command description for list in /help (/cmd:desc="Command name")
	vars        []string // environment vars for user text, split by `/s+` to vars (/cmd:vars=SUBCOMMAND,ARGS)
	isMarkdown  bool     // send message in markdown format
}

// Commands - list of all commands
type Commands map[string]Command

// Config - config struct
type Config struct {
	token                  string   // bot token
	botTimeout             int      // bot timeout
	predefinedAllowedUsers []string // telegram users who are allowed to chat with the bot
	predefinedRootUsers    []string // telegram users, who confirms new users in their private chat
	description            string   // description of bot
	usersDB                string   // file for store users
	shell                  string   // custom shell
	cache                  int      // caching command out (in seconds)
	shTimeout              int      // timeout for execute shell command (in seconds)
	addExit                bool     // adding /shell2telegram exit command
	allowAll               bool     // allow all user (DANGEROUS!)
	logCommands            bool     // logging all commands
	persistentUsers        bool     // load/save users from file
	isPublicBot            bool     // bot is public (dont add /auth* commands)
}

// message types
const (
	msgIsText int8 = iota
	msgIsPhoto
)

// BotMessage - record for send via channel for send message to telegram chat
type BotMessage struct {
	message     string
	fileName    string
	photo       []byte
	chatID      int
	messageType int8
	isMarkdown  bool
}

// ----------------------------------------------------------------------------
// get config
func getConfig() (commands Commands, appConfig Config, err error) {
	flag.StringVar(&appConfig.token, "tb-token", "", "setting bot token (or set TB_TOKEN variable)")
	flag.BoolVar(&appConfig.addExit, "add-exit", false, "adding \"/shell2telegram exit\" command for terminate bot (for roots only)")
	flag.IntVar(&appConfig.botTimeout, "timeout", DefaultBotTimeout, "setting timeout for bot")
	flag.BoolVar(&appConfig.allowAll, "allow-all", false, "allow all users (DANGEROUS!)")
	flag.BoolVar(&appConfig.logCommands, "log-commands", false, "logging all commands")
	flag.StringVar(&appConfig.description, "description", "", "setting description of bot")
	flag.BoolVar(&appConfig.persistentUsers, "persistent-users", false, "load/save users from file (default ~/.config/shell2telegram.json)")
	flag.StringVar(&appConfig.usersDB, "users-db", "", "file for store users")
	flag.IntVar(&appConfig.cache, "cache", 0, "caching command out (in seconds)")
	flag.BoolVar(&appConfig.isPublicBot, "public", false, "bot is public (dont add /auth* commands)")
	flag.IntVar(&appConfig.shTimeout, "sh-timeout", 0, "set timeout for execute shell command (in seconds)")
	flag.StringVar(&appConfig.shell, "shell", "sh", "custom shell or \"\" for execute without shell")
	logFilename := flag.String("log", "", "log filename, default - STDOUT")
	predefinedAllowedUsers := flag.String("allow-users", "", "telegram users who are allowed to chat with the bot (\"user1,user2\")")
	predefinedRootUsers := flag.String("root-users", "", "telegram users, who confirms new users in their private chat (\"user1,user2\")")
	version := flag.Bool("version", false, "get version")

	flag.Usage = func() {
		fmt.Printf("usage: %s [options] %s\n%s\n%s\n\noptions:\n",
			os.Args[0],
			`/chat_command "shell command" /chat_command2 "shell command2"`,
			"All text after /chat_command will be sent to STDIN of shell command.",
			"If chat command is /:plain_text - get user message without any /command (for private chats only)",
		)
		flag.PrintDefaults()
		os.Exit(0)
	}
	flag.Parse()

	if *version {
		fmt.Println(Version)
		os.Exit(0)
	}

	// setup log file
	if len(*logFilename) > 0 {
		fhLog, err := os.OpenFile(*logFilename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
		if err != nil {
			log.Fatalf("error opening log file: %v", err)
		}
		log.SetOutput(fhLog)
	}

	// setup users and roots
	if *predefinedAllowedUsers != "" {
		appConfig.predefinedAllowedUsers = strings.Split(*predefinedAllowedUsers, ",")
	}
	if *predefinedRootUsers != "" {
		appConfig.predefinedRootUsers = strings.Split(*predefinedRootUsers, ",")
	}

	commands = Commands{}
	// need >= 2 arguments and count of it must be even
	args := flag.Args()
	if len(args) < 2 || len(args)%2 == 1 {
		return commands, appConfig, fmt.Errorf("error: need pairs of /chat-command and shell-command")
	}

	for i := 0; i < len(args); i += 2 {
		path, command, err := parseBotCommand(args[i], args[i+1]) // (/path, shell_command)
		if err != nil {
			return commands, appConfig, err
		}
		commands[path] = command
	}

	if appConfig.token == "" {
		if appConfig.token = os.Getenv("TB_TOKEN"); appConfig.token == "" {
			return commands, appConfig, fmt.Errorf("TB_TOKEN environment var not found. See https://core.telegram.org/bots#botfather for more information")
		}
	}

	return commands, appConfig, nil
}

// ----------------------------------------------------------------------------
func sendMessage(messageSignal chan<- BotMessage, chatID int, message []byte, isMarkdown bool) {
	go func() {
		var fileName string
		fileType := http.DetectContentType(message)
		switch fileType {
		case "image/png":
			fileName = "file.png"
		case "image/jpeg":
			fileName = "file.jpeg"
		case "image/gif":
			fileName = "file.gif"
		case "image/bmp":
			fileName = "file.bmp"
		default:
			fileName = "message"
		}

		if fileName == "message" {

			// is text message
			messageString := string(message)
			var messagesList []string

			if len(messageString) <= MaxMessageLength {
				messagesList = []string{messageString}
			} else {
				messagesList = splitStringLinesBySize(messageString, MaxMessageLength)
			}

			for _, messageChunk := range messagesList {
				messageSignal <- BotMessage{
					chatID:      chatID,
					messageType: msgIsText,
					message:     messageChunk,
					isMarkdown:  isMarkdown,
				}
			}

		} else {
			// is image
			messageSignal <- BotMessage{
				chatID:      chatID,
				messageType: msgIsPhoto,
				fileName:    fileName,
				photo:       message,
			}
		}
	}()
}

// ----------------------------------------------------------------------------
func main() {
	commands, appConfig, err := getConfig()
	if err != nil {
		log.Fatal(err)
	}

	bot, err := tgbotapi.NewBotAPI(appConfig.token)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Authorized on bot account: @%s", bot.Self.UserName)

	tgbotConfig := tgbotapi.NewUpdate(0)
	tgbotConfig.Timeout = appConfig.botTimeout
	botUpdatesChan, err := bot.GetUpdatesChan(tgbotConfig)
	if err != nil {
		log.Fatal(err)
	}

	users := NewUsers(appConfig)
	messageSignal := make(chan BotMessage, MessagesQueueSize)
	vacuumTicker := time.Tick(SecondsForOldUsersBeforeVacuum * time.Second)
	saveToBDTicker := make(<-chan time.Time)
	exitSignal := make(chan struct{})
	systemExitSignal := make(chan os.Signal, 1)
	signal.Notify(systemExitSignal, os.Interrupt)

	if appConfig.persistentUsers {
		saveToBDTicker = time.Tick(SecondsForAutoSaveUsersToDB * time.Second)
	}

	var cache raphanus.DB
	if appConfig.cache > 0 {
		cache = raphanus.New()
	}

	// all /shell2telegram sub-commands handlers
	internalCommands := map[string]func(Ctx) string{
		"stat":              cmdShell2telegramStat,
		"ban":               cmdShell2telegramBan,
		"search":            cmdShell2telegramSearch,
		"desc":              cmdShell2telegramDesc,
		"rm":                cmdShell2telegramRm,
		"exit":              cmdShell2telegramExit,
		"version":           cmdShell2telegramVersion,
		"broadcast_to_root": cmdShell2telegramBroadcastToRoot,
		"message_to_user":   cmdShell2telegramMessageToUser,
	}

	doExit := false
	for !doExit {
		select {
		case telegramUpdate := <-botUpdatesChan:

			var messageCmd, messageArgs string
			allUserMessage := telegramUpdate.Message.Text
			if len(allUserMessage) > 0 && allUserMessage[0] == '/' {
				messageCmd, messageArgs = splitStringHalfBySpace(allUserMessage)
			} else {
				messageCmd, messageArgs = cmdPlainText, allUserMessage
			}

			allowPlainText := false
			if _, ok := commands[cmdPlainText]; ok {
				allowPlainText = true
			}

			replayMsg := ""

			if len(messageCmd) > 0 && (messageCmd != cmdPlainText || allowPlainText) {

				users.AddNew(telegramUpdate.Message)
				userID := telegramUpdate.Message.From.ID
				allowExec := appConfig.allowAll || users.IsAuthorized(userID)

				ctx := Ctx{
					appConfig:     &appConfig,
					users:         &users,
					commands:      commands,
					userID:        userID,
					allowExec:     allowExec,
					messageCmd:    messageCmd,
					messageArgs:   messageArgs,
					messageSignal: messageSignal,
					chatID:        telegramUpdate.Message.Chat.ID,
					exitSignal:    exitSignal,
					cache:         &cache,
				}

				switch {
				// commands .................................
				case !appConfig.isPublicBot && (messageCmd == "/auth" || messageCmd == "/authroot"):
					replayMsg = cmdAuth(ctx)

				case messageCmd == "/help":
					replayMsg = cmdHelp(ctx)

				case messageCmd == "/shell2telegram" && users.IsRoot(userID):
					var messageSubCmd string
					messageSubCmd, messageArgs = splitStringHalfBySpace(messageArgs)
					ctx.messageArgs = messageArgs
					if cmdHandler, ok := internalCommands[messageSubCmd]; ok {
						replayMsg = cmdHandler(ctx)
					} else {
						replayMsg = "Sub-command not found"
					}

				case allowExec && (allowPlainText && messageCmd == cmdPlainText || messageCmd[0] == '/'):
					cmdUser(ctx)

				} // switch for commands

				if appConfig.logCommands {
					log.Printf("%s: %s", users.String(userID), allUserMessage)
				}

				sendMessage(messageSignal, telegramUpdate.Message.Chat.ID, []byte(replayMsg), false)
			}

		case botMessage := <-messageSignal:
			switch {
			case botMessage.messageType == msgIsText && !stringIsEmpty(botMessage.message):
				messageConfig := tgbotapi.NewMessage(botMessage.chatID, botMessage.message)
				if botMessage.isMarkdown {
					messageConfig.ParseMode = tgbotapi.ModeMarkdown
				}
				_, err = bot.Send(messageConfig)
			case botMessage.messageType == msgIsPhoto && len(botMessage.photo) > 0:
				bytesPhoto := tgbotapi.FileBytes{Name: botMessage.fileName, Bytes: botMessage.photo}
				_, err = bot.Send(tgbotapi.NewPhotoUpload(botMessage.chatID, bytesPhoto))
			}

			if err != nil {
				log.Print("Bot send message error: ", err)
			}

		case <-saveToBDTicker:
			users.SaveToDB(appConfig.usersDB)

		case <-vacuumTicker:
			users.ClearOldUsers()

		case <-systemExitSignal:
			go func() {
				exitSignal <- struct{}{}
			}()

		case <-exitSignal:
			if appConfig.persistentUsers {
				users.needSaveDB = true
				users.SaveToDB(appConfig.usersDB)
			}
			doExit = true
		}
	}
}
<|endoftext|>


<commit_before>package network // import "collectd.org/network"

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"log"
	"time"

	"collectd.org/api"
	"collectd.org/cdtime"
)

// ErrInvalid is returned when parsing the network data was aborted due to
// illegal data format.
var ErrInvalid = errors.New("invalid data")

// ParseOpts holds confiruation options for "Parse()".
type ParseOpts struct {
	// PasswordLookup is used lookup passwords to verify signed data and
	// decrypt encrypted data.
	PasswordLookup PasswordLookup
	// SecurityLevel determines the minimum security level expected by the
	// caller. If set to "Sign", only signed and encrypted data is returned
	// by Parse(), if set to "Encrypt", only encrypted data is returned.
	SecurityLevel SecurityLevel
	// TypesDB for looking up DS names and verify data source types.
	TypesDB *api.TypesDB
}

// Parse parses the binary network format and returns a slice of ValueLists. If
// a parse error is encountered, all ValueLists parsed to this point are
// returned as well as the error. Unknown "parts" are silently ignored.
func Parse(b []byte, opts ParseOpts) ([]*api.ValueList, error) {
	return parse(b, None, opts)
}

func readUint16(buf *bytes.Buffer) (uint16, error) {
	read := buf.Next(2)
	if len(read) != 2 {
		return 0, ErrInvalid
	}
	return binary.BigEndian.Uint16(read), nil
}

func parse(b []byte, sl SecurityLevel, opts ParseOpts) ([]*api.ValueList, error) {
	var valueLists []*api.ValueList

	var state api.ValueList
	buf := bytes.NewBuffer(b)

	for buf.Len() > 0 {
		partType, err := readUint16(buf)
		if err != nil {
			return nil, ErrInvalid
		}
		partLengthUnsigned, err := readUint16(buf)
		if err != nil {
			return nil, ErrInvalid
		}
		partLength := int(partLengthUnsigned)

		if partLength < 5 || partLength-4 > buf.Len() {
			return valueLists, fmt.Errorf("invalid length %d", partLength)
		}

		// First 4 bytes were already read
		partLength -= 4

		payload := buf.Next(partLength)
		if len(payload) != partLength {
			return valueLists, fmt.Errorf("invalid length: want %d, got %d", partLength, len(payload))
		}

		switch partType {
		case typeHost, typePlugin, typePluginInstance, typeType, typeTypeInstance:
			if err := parseIdentifier(partType, payload, &state); err != nil {
				return valueLists, err
			}

		case typeInterval, typeIntervalHR, typeTime, typeTimeHR:
			if err := parseTime(partType, payload, &state); err != nil {
				return valueLists, err
			}

		case typeValues:
			v, err := parseValues(payload)
			if err != nil {
				return valueLists, err
			}

			vl := state
			vl.Values = v

			if opts.TypesDB != nil {
				ds, ok := opts.TypesDB.DataSet(state.Type)
				if !ok {
					log.Printf("unable to find %q in TypesDB", state.Type)
					continue
				}

				// convert []api.Value to []interface{}
				ifValues := make([]interface{}, len(vl.Values))
				for i, v := range vl.Values {
					ifValues[i] = v
				}

				// cast all values to the correct data source type.
				// Returns an error if the number of values is incorrect.
				v, err := ds.Values(ifValues...)
				if err != nil {
					log.Printf("unable to convert values according to TypesDB: %v", err)
					continue
				}
				vl.Values = v
				vl.DSNames = ds.Names()
			}

			if opts.SecurityLevel <= sl {
				valueLists = append(valueLists, &vl)
			}

		case typeSignSHA256:
			vls, err := parseSignSHA256(payload, buf.Bytes(), opts)
			if err != nil {
				return valueLists, err
			}
			valueLists = append(valueLists, vls...)

		case typeEncryptAES256:
			vls, err := parseEncryptAES256(payload, opts)
			if err != nil {
				return valueLists, err
			}
			valueLists = append(valueLists, vls...)

		default:
			log.Printf("ignoring field of type %#x", partType)
		}
	}

	return valueLists, nil
}

func parseIdentifier(partType uint16, payload []byte, state *api.ValueList) error {
	str, err := parseString(payload)
	if err != nil {
		return err
	}

	switch partType {
	case typeHost:
		state.Identifier.Host = str
	case typePlugin:
		state.Identifier.Plugin = str
	case typePluginInstance:
		state.Identifier.PluginInstance = str
	case typeType:
		state.Identifier.Type = str
	case typeTypeInstance:
		state.Identifier.TypeInstance = str
	}

	return nil
}

func parseTime(partType uint16, payload []byte, state *api.ValueList) error {
	v, err := parseInt(payload)
	if err != nil {
		return err
	}

	switch partType {
	case typeInterval:
		state.Interval = time.Duration(v) * time.Second
	case typeIntervalHR:
		state.Interval = cdtime.Time(v).Duration()
	case typeTime:
		state.Time = time.Unix(int64(v), 0)
	case typeTimeHR:
		state.Time = cdtime.Time(v).Time()
	}

	return nil
}

func parseValues(b []byte) ([]api.Value, error) {
	buffer := bytes.NewBuffer(b)

	var n uint16
	if err := binary.Read(buffer, binary.BigEndian, &n); err != nil {
		return nil, err
	}

	if int(n*9) != buffer.Len() {
		return nil, ErrInvalid
	}

	types := make([]byte, n)
	values := make([]api.Value, n)

	if _, err := buffer.Read(types); err != nil {
		return nil, err
	}

	for i, typ := range types {
		switch typ {
		case dsTypeGauge:
			var v float64
			if err := binary.Read(buffer, binary.LittleEndian, &v); err != nil {
				return nil, err
			}
			values[i] = api.Gauge(v)

		case dsTypeDerive:
			var v int64
			if err := binary.Read(buffer, binary.BigEndian, &v); err != nil {
				return nil, err
			}
			values[i] = api.Derive(v)

		case dsTypeCounter:
			var v uint64
			if err := binary.Read(buffer, binary.BigEndian, &v); err != nil {
				return nil, err
			}
			values[i] = api.Counter(v)

		default:
			return nil, ErrInvalid
		}
	}

	return values, nil
}

func parseSignSHA256(pkg, payload []byte, opts ParseOpts) ([]*api.ValueList, error) {
	ok, err := verifySHA256(pkg, payload, opts.PasswordLookup)
	if err != nil {
		return nil, err
	} else if !ok {
		return nil, errors.New("SHA256 verification failure")
	}

	return parse(payload, Sign, opts)
}

func parseEncryptAES256(payload []byte, opts ParseOpts) ([]*api.ValueList, error) {
	plaintext, err := decryptAES256(payload, opts.PasswordLookup)
	if err != nil {
		return nil, errors.New("AES256 decryption failure")
	}

	return parse(plaintext, Encrypt, opts)
}

func parseInt(b []byte) (uint64, error) {
	if len(b) != 8 {
		return 0, ErrInvalid
	}

	var i uint64
	buf := bytes.NewBuffer(b)
	if err := binary.Read(buf, binary.BigEndian, &i); err != nil {
		return 0, err
	}

	return i, nil
}

func parseString(b []byte) (string, error) {
	if b[len(b)-1] != 0 {
		return "", ErrInvalid
	}

	buf := bytes.NewBuffer(b[:len(b)-1])
	return buf.String(), nil
}
<commit_msg>Package network: Improve type checking error message.<commit_after>package network // import "collectd.org/network"

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"log"
	"time"

	"collectd.org/api"
	"collectd.org/cdtime"
)

// ErrInvalid is returned when parsing the network data was aborted due to
// illegal data format.
var ErrInvalid = errors.New("invalid data")

// ParseOpts holds confiruation options for "Parse()".
type ParseOpts struct {
	// PasswordLookup is used lookup passwords to verify signed data and
	// decrypt encrypted data.
	PasswordLookup PasswordLookup
	// SecurityLevel determines the minimum security level expected by the
	// caller. If set to "Sign", only signed and encrypted data is returned
	// by Parse(), if set to "Encrypt", only encrypted data is returned.
	SecurityLevel SecurityLevel
	// TypesDB for looking up DS names and verify data source types.
	TypesDB *api.TypesDB
}

// Parse parses the binary network format and returns a slice of ValueLists. If
// a parse error is encountered, all ValueLists parsed to this point are
// returned as well as the error. Unknown "parts" are silently ignored.
func Parse(b []byte, opts ParseOpts) ([]*api.ValueList, error) {
	return parse(b, None, opts)
}

func readUint16(buf *bytes.Buffer) (uint16, error) {
	read := buf.Next(2)
	if len(read) != 2 {
		return 0, ErrInvalid
	}
	return binary.BigEndian.Uint16(read), nil
}

func parse(b []byte, sl SecurityLevel, opts ParseOpts) ([]*api.ValueList, error) {
	var valueLists []*api.ValueList

	var state api.ValueList
	buf := bytes.NewBuffer(b)

	for buf.Len() > 0 {
		partType, err := readUint16(buf)
		if err != nil {
			return nil, ErrInvalid
		}
		partLengthUnsigned, err := readUint16(buf)
		if err != nil {
			return nil, ErrInvalid
		}
		partLength := int(partLengthUnsigned)

		if partLength < 5 || partLength-4 > buf.Len() {
			return valueLists, fmt.Errorf("invalid length %d", partLength)
		}

		// First 4 bytes were already read
		partLength -= 4

		payload := buf.Next(partLength)
		if len(payload) != partLength {
			return valueLists, fmt.Errorf("invalid length: want %d, got %d", partLength, len(payload))
		}

		switch partType {
		case typeHost, typePlugin, typePluginInstance, typeType, typeTypeInstance:
			if err := parseIdentifier(partType, payload, &state); err != nil {
				return valueLists, err
			}

		case typeInterval, typeIntervalHR, typeTime, typeTimeHR:
			if err := parseTime(partType, payload, &state); err != nil {
				return valueLists, err
			}

		case typeValues:
			v, err := parseValues(payload)
			if err != nil {
				return valueLists, err
			}

			vl := state
			vl.Values = v

			if opts.TypesDB != nil {
				ds, ok := opts.TypesDB.DataSet(state.Type)
				if !ok {
					log.Printf("unable to find %q in TypesDB", state.Type)
					continue
				}

				// convert []api.Value to []interface{}
				ifValues := make([]interface{}, len(vl.Values))
				for i, v := range vl.Values {
					ifValues[i] = v
				}

				// cast all values to the correct data source type.
				// Returns an error if the number of values is incorrect.
				v, err := ds.Values(ifValues...)
				if err != nil {
					log.Printf("unable to convert metric %q, values %v according to %v in TypesDB: %v", state, ifValues, ds, err)
					continue
				}
				vl.Values = v
				vl.DSNames = ds.Names()
			}

			if opts.SecurityLevel <= sl {
				valueLists = append(valueLists, &vl)
			}

		case typeSignSHA256:
			vls, err := parseSignSHA256(payload, buf.Bytes(), opts)
			if err != nil {
				return valueLists, err
			}
			valueLists = append(valueLists, vls...)

		case typeEncryptAES256:
			vls, err := parseEncryptAES256(payload, opts)
			if err != nil {
				return valueLists, err
			}
			valueLists = append(valueLists, vls...)

		default:
			log.Printf("ignoring field of type %#x", partType)
		}
	}

	return valueLists, nil
}

func parseIdentifier(partType uint16, payload []byte, state *api.ValueList) error {
	str, err := parseString(payload)
	if err != nil {
		return err
	}

	switch partType {
	case typeHost:
		state.Identifier.Host = str
	case typePlugin:
		state.Identifier.Plugin = str
	case typePluginInstance:
		state.Identifier.PluginInstance = str
	case typeType:
		state.Identifier.Type = str
	case typeTypeInstance:
		state.Identifier.TypeInstance = str
	}

	return nil
}

func parseTime(partType uint16, payload []byte, state *api.ValueList) error {
	v, err := parseInt(payload)
	if err != nil {
		return err
	}

	switch partType {
	case typeInterval:
		state.Interval = time.Duration(v) * time.Second
	case typeIntervalHR:
		state.Interval = cdtime.Time(v).Duration()
	case typeTime:
		state.Time = time.Unix(int64(v), 0)
	case typeTimeHR:
		state.Time = cdtime.Time(v).Time()
	}

	return nil
}

func parseValues(b []byte) ([]api.Value, error) {
	buffer := bytes.NewBuffer(b)

	var n uint16
	if err := binary.Read(buffer, binary.BigEndian, &n); err != nil {
		return nil, err
	}

	if int(n*9) != buffer.Len() {
		return nil, ErrInvalid
	}

	types := make([]byte, n)
	values := make([]api.Value, n)

	if _, err := buffer.Read(types); err != nil {
		return nil, err
	}

	for i, typ := range types {
		switch typ {
		case dsTypeGauge:
			var v float64
			if err := binary.Read(buffer, binary.LittleEndian, &v); err != nil {
				return nil, err
			}
			values[i] = api.Gauge(v)

		case dsTypeDerive:
			var v int64
			if err := binary.Read(buffer, binary.BigEndian, &v); err != nil {
				return nil, err
			}
			values[i] = api.Derive(v)

		case dsTypeCounter:
			var v uint64
			if err := binary.Read(buffer, binary.BigEndian, &v); err != nil {
				return nil, err
			}
			values[i] = api.Counter(v)

		default:
			return nil, ErrInvalid
		}
	}

	return values, nil
}

func parseSignSHA256(pkg, payload []byte, opts ParseOpts) ([]*api.ValueList, error) {
	ok, err := verifySHA256(pkg, payload, opts.PasswordLookup)
	if err != nil {
		return nil, err
	} else if !ok {
		return nil, errors.New("SHA256 verification failure")
	}

	return parse(payload, Sign, opts)
}

func parseEncryptAES256(payload []byte, opts ParseOpts) ([]*api.ValueList, error) {
	plaintext, err := decryptAES256(payload, opts.PasswordLookup)
	if err != nil {
		return nil, errors.New("AES256 decryption failure")
	}

	return parse(plaintext, Encrypt, opts)
}

func parseInt(b []byte) (uint64, error) {
	if len(b) != 8 {
		return 0, ErrInvalid
	}

	var i uint64
	buf := bytes.NewBuffer(b)
	if err := binary.Read(buf, binary.BigEndian, &i); err != nil {
		return 0, err
	}

	return i, nil
}

func parseString(b []byte) (string, error) {
	if b[len(b)-1] != 0 {
		return "", ErrInvalid
	}

	buf := bytes.NewBuffer(b[:len(b)-1])
	return buf.String(), nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math/big"
	"os"
	"text/tabwriter"
	"time"

	"github.com/spf13/cobra"

	"github.com/NebulousLabs/Sia/api"
	"github.com/NebulousLabs/Sia/modules"
)

const scanHistoryLen = 30

var (
	hostdbNumHosts int
	hostdbVerbose  bool
)

var (
	hostdbCmd = &cobra.Command{
		Use:   "hostdb",
		Short: "Interact with the renter's host database.",
		Long:  "View the list of active hosts, the list of all hosts, or query specific hosts.\nIf the '-v' flag is set, a list of recent scans will be provided, with the most\nrecent scan on the right. a '0' indicates that the host was offline, and a '1'\nindicates that the host was online.",
		Run:   wrap(hostdbcmd),
	}

	hostdbViewCmd = &cobra.Command{
		Use:   "view [pubkey]",
		Short: "View the full information for a host.",
		Long:  "View detailed information about a host, including things like a score breakdown.",
		Run:   wrap(hostdbviewcmd),
	}
)

func hostdbcmd() {
	if !hostdbVerbose {
		info := new(api.HostdbActiveGET)
		err := getAPI("/hostdb/active", info)
		if err != nil {
			die("Could not fetch host list:", err)
		}
		if len(info.Hosts) == 0 {
			fmt.Println("No known active hosts")
			return
		}

		// Strip down to the number of requested hosts.
		if hostdbNumHosts != 0 && hostdbNumHosts < len(info.Hosts) {
			info.Hosts = info.Hosts[len(info.Hosts)-hostdbNumHosts:]
		}

		fmt.Println(len(info.Hosts), "Active Hosts:")
		w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
		fmt.Fprintln(w, "\t\tAddress\tPrice (per TB per Mo)")
		for i, host := range info.Hosts {
			price := host.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte)
			fmt.Fprintf(w, "\t%v:\t%v\t%v\n", len(info.Hosts)-i, host.NetAddress, currencyUnits(price))
		}
		w.Flush()
	} else {
		info := new(api.HostdbAllGET)
		err := getAPI("/hostdb/all", info)
		if err != nil {
			die("Could not fetch host list:", err)
		}
		if len(info.Hosts) == 0 {
			fmt.Println("No known hosts")
			return
		}

		// Iterate through the hosts and divide by category.
		var activeHosts, inactiveHosts, offlineHosts []api.ExtendedHostDBEntry
		for _, host := range info.Hosts {
			if host.AcceptingContracts && len(host.ScanHistory) > 0 && host.ScanHistory[len(host.ScanHistory)-1].Success {
				activeHosts = append(activeHosts, host)
				continue
			}
			if len(host.ScanHistory) > 0 && host.ScanHistory[len(host.ScanHistory)-1].Success {
				inactiveHosts = append(inactiveHosts, host)
				continue
			}
			offlineHosts = append(offlineHosts, host)
		}

		if hostdbNumHosts > 0 && len(offlineHosts) > hostdbNumHosts {
			offlineHosts = offlineHosts[len(offlineHosts)-hostdbNumHosts:]
		}
		if hostdbNumHosts > 0 && len(inactiveHosts) > hostdbNumHosts {
			inactiveHosts = inactiveHosts[len(inactiveHosts)-hostdbNumHosts:]
		}
		if hostdbNumHosts > 0 && len(activeHosts) > hostdbNumHosts {
			activeHosts = activeHosts[len(activeHosts)-hostdbNumHosts:]
		}

		fmt.Println()
		fmt.Println(len(offlineHosts), "Offline Hosts:")
		w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
		fmt.Fprintln(w, "\t\tPubkey\tAddress\tUptime\tPrice (/ TB / Month)\tDownload Price (/ TB)\tRecent Scans")
		for i, host := range offlineHosts {
			// Compute the total measured uptime and total measured downtime for this
			// host.
			uptimeRatio := float64(0)
			if len(host.ScanHistory) > 1 {
				var uptime time.Duration
				var downtime time.Duration
				recentTime := host.ScanHistory[0].Timestamp
				recentSuccess := host.ScanHistory[0].Success
				for _, scan := range host.ScanHistory[1:] {
					if recentSuccess {
						uptime += scan.Timestamp.Sub(recentTime)
					} else {
						downtime += scan.Timestamp.Sub(recentTime)
					}
					recentTime = scan.Timestamp
					recentSuccess = scan.Success
				}
				uptimeRatio = float64(uptime) / float64(uptime+downtime)
			}

			// Get the scan history string.
			scanHistStr := ""
			displayScans := host.ScanHistory
			if len(host.ScanHistory) > scanHistoryLen {
				displayScans = host.ScanHistory[len(host.ScanHistory)-scanHistoryLen:]
			}
			for _, scan := range displayScans {
				if scan.Success {
					scanHistStr += "1"
				} else {
					scanHistStr += "0"
				}
			}

			// Get a string representation of the historic outcomes of the most
			// recent scans.
			price := host.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte)
			downloadBWPrice := host.StoragePrice.Mul(modules.BytesPerTerabyte)
			fmt.Fprintf(w, "\t%v:\t%v\t%v\t%v\t%v\t%.3f\t%s\n", len(offlineHosts)-i, host.PublicKeyString, host.NetAddress, currencyUnits(price), currencyUnits(downloadBWPrice), uptimeRatio, scanHistStr)
		}
		w.Flush()

		fmt.Println()
		fmt.Println(len(inactiveHosts), "Inactive Hosts:")
		w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
		fmt.Fprintln(w, "\t\tPubkey\tAddress\tPrice (/ TB / Month)\tDownload Price (/ TB)\tUptime\tRecent Scans")
		for i, host := range inactiveHosts {
			// Compute the total measured uptime and total measured downtime for this
			// host.
			uptimeRatio := float64(0)
			if len(host.ScanHistory) > 1 {
				var uptime time.Duration
				var downtime time.Duration
				recentTime := host.ScanHistory[0].Timestamp
				recentSuccess := host.ScanHistory[0].Success
				for _, scan := range host.ScanHistory[1:] {
					if recentSuccess {
						uptime += scan.Timestamp.Sub(recentTime)
					} else {
						downtime += scan.Timestamp.Sub(recentTime)
					}
					recentTime = scan.Timestamp
					recentSuccess = scan.Success
				}
				uptimeRatio = float64(uptime) / float64(uptime+downtime)
			}

			// Get a string representation of the historic outcomes of the most
			// recent scans.
			scanHistStr := ""
			displayScans := host.ScanHistory
			if len(host.ScanHistory) > scanHistoryLen {
				displayScans = host.ScanHistory[len(host.ScanHistory)-scanHistoryLen:]
			}
			for _, scan := range displayScans {
				if scan.Success {
					scanHistStr += "1"
				} else {
					scanHistStr += "0"
				}
			}

			price := host.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte)
			downloadBWPrice := host.DownloadBandwidthPrice.Mul(modules.BytesPerTerabyte)
			fmt.Fprintf(w, "\t%v:\t%v\t%v\t%v\t%v\t%.3f\t%s\n", len(inactiveHosts)-i, host.PublicKeyString, host.NetAddress, currencyUnits(price), currencyUnits(downloadBWPrice), uptimeRatio, scanHistStr)
		}
		w.Flush()

		fmt.Println()
		fmt.Println(len(activeHosts), "Active Hosts:")
		w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
		fmt.Fprintln(w, "\t\tPubkey\tAddress\tScore\tPrice (/ TB / Month)\tDownload Price (/TB)\tUptime\tRecent Scans")
		for i, host := range activeHosts {
			// Compute the total measured uptime and total measured downtime for this
			// host.
			uptimeRatio := float64(0)
			if len(host.ScanHistory) > 1 {
				var uptime time.Duration
				var downtime time.Duration
				recentTime := host.ScanHistory[0].Timestamp
				recentSuccess := host.ScanHistory[0].Success
				for _, scan := range host.ScanHistory[1:] {
					if recentSuccess {
						uptime += scan.Timestamp.Sub(recentTime)
					} else {
						downtime += scan.Timestamp.Sub(recentTime)
					}
					recentTime = scan.Timestamp
					recentSuccess = scan.Success
				}
				uptimeRatio = float64(uptime) / float64(uptime+downtime)
			}

			// Get a string representation of the historic outcomes of the most
			// recent scans.
			scanHistStr := ""
			displayScans := host.ScanHistory
			if len(host.ScanHistory) > scanHistoryLen {
				displayScans = host.ScanHistory[len(host.ScanHistory)-scanHistoryLen:]
			}
			for _, scan := range displayScans {
				if scan.Success {
					scanHistStr += "1"
				} else {
					scanHistStr += "0"
				}
			}

			// Grab the score information for the active hosts.
			hostInfo := new(api.HostdbHostsGET)
			err := getAPI("/hostdb/hosts/"+host.PublicKeyString, hostInfo)
			if err != nil {
				die("Could not fetch provided host:", err)
			}
			score, _ := big.NewFloat(1).SetInt(hostInfo.ScoreBreakdown.Score.Big()).Float64()
			score /= 1e70

			price := host.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte)
			downloadBWPrice := host.DownloadBandwidthPrice.Mul(modules.BytesPerTerabyte)
			fmt.Fprintf(w, "\t%v:\t%v\t%v\t%12.6g\t%v\t%v\t%.3f\t%s\n", len(activeHosts)-i, host.PublicKeyString, host.NetAddress, score, currencyUnits(price), currencyUnits(downloadBWPrice), uptimeRatio, scanHistStr)
		}
		w.Flush()
	}
}

func hostdbviewcmd(pubkey string) {
	info := new(api.HostdbHostsGET)
	err := getAPI("/hostdb/hosts/"+pubkey, info)
	if err != nil {
		die("Could not fetch provided host:", err)
	}

	fmt.Println("Host information:")

	fmt.Println("  Public Key:", info.Entry.PublicKeyString)
	fmt.Println("  Block First Seen:", info.Entry.FirstSeen)

	fmt.Println("\n  Host Settings:")
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	fmt.Fprintln(w, "\t\tAccepting Contracts:\t", info.Entry.AcceptingContracts)
	fmt.Fprintln(w, "\t\tTotal Storage:\t", info.Entry.TotalStorage/1e9, "GB")
	fmt.Fprintln(w, "\t\tRemaining Storage:\t", info.Entry.RemainingStorage/1e9, "GB")
	fmt.Fprintln(w, "\t\tOffered Collateral (TB / Mo):\t", currencyUnits(info.Entry.Collateral.Mul(modules.BlockBytesPerMonthTerabyte)))
	fmt.Fprintln(w, "\n\t\tContract Price:\t", currencyUnits(info.Entry.ContractPrice))
	fmt.Fprintln(w, "\t\tStorage Price (TB / Mo):\t", currencyUnits(info.Entry.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte)))
	fmt.Fprintln(w, "\t\tDownload Price (1 TB):\t", currencyUnits(info.Entry.DownloadBandwidthPrice.Mul(modules.BytesPerTerabyte)))
	fmt.Fprintln(w, "\t\tUpload Price (1 TB):\t", currencyUnits(info.Entry.UploadBandwidthPrice.Mul(modules.BytesPerTerabyte)))
	fmt.Fprintln(w, "\t\tVersion:\t", info.Entry.Version)
	w.Flush()

	fmt.Println("\n  Score Breakdown:")
	w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	total := info.ScoreBreakdown.Score
	fmt.Fprintf(w, "\t\tTotal Score:\t %v\n\n", total)
	fmt.Fprintf(w, "\t\tAge:\t %.3f\n", info.ScoreBreakdown.AgeAdjustment)
	fmt.Fprintf(w, "\t\tBurn:\t %.3f\n", info.ScoreBreakdown.BurnAdjustment)
	fmt.Fprintf(w, "\t\tCollateral:\t %.3f\n", info.ScoreBreakdown.CollateralAdjustment)
	fmt.Fprintf(w, "\t\tPrice:\t %.3f\n", info.ScoreBreakdown.PriceAdjustment*1e6)
	fmt.Fprintf(w, "\t\tStorage:\t %.3f\n", info.ScoreBreakdown.StorageRemainingAdjustment)
	fmt.Fprintf(w, "\t\tUptime:\t %.3f\n", info.ScoreBreakdown.UptimeAdjustment)
	fmt.Fprintf(w, "\t\tVersion:\t %.3f\n", info.ScoreBreakdown.VersionAdjustment)
	w.Flush()

	// Compute the total measured uptime and total measured downtime for this
	// host.
	uptimeRatio := float64(0)
	if len(info.Entry.ScanHistory) > 1 {
		downtime := info.Entry.HistoricDowntime
		uptime := info.Entry.HistoricUptime
		recentTime := info.Entry.ScanHistory[0].Timestamp
		recentSuccess := info.Entry.ScanHistory[0].Success
		for _, scan := range info.Entry.ScanHistory[1:] {
			if recentSuccess {
				uptime += scan.Timestamp.Sub(recentTime)
			} else {
				downtime += scan.Timestamp.Sub(recentTime)
			}
			recentTime = scan.Timestamp
			recentSuccess = scan.Success
		}
		uptimeRatio = float64(uptime) / float64(uptime+downtime)
	}

	// Compute the uptime ratio, but shift by 0.02 to acknowledge fully that
	// 98% uptime and 100% uptime is valued the same.
	fmt.Println("\n  Scan History Length:", len(info.Entry.ScanHistory))
	fmt.Printf("  Overall Uptime:      %.3f\n", uptimeRatio)

	fmt.Println()
}
<commit_msg>switch to displaying a 1/5-median normalized value<commit_after>package main

import (
	"fmt"
	"math/big"
	"os"
	"text/tabwriter"
	"time"

	"github.com/spf13/cobra"

	"github.com/NebulousLabs/Sia/api"
	"github.com/NebulousLabs/Sia/modules"
)

const scanHistoryLen = 30

var (
	hostdbNumHosts int
	hostdbVerbose  bool
)

var (
	hostdbCmd = &cobra.Command{
		Use:   "hostdb",
		Short: "Interact with the renter's host database.",
		Long:  "View the list of active hosts, the list of all hosts, or query specific hosts.\nIf the '-v' flag is set, a list of recent scans will be provided, with the most\nrecent scan on the right. a '0' indicates that the host was offline, and a '1'\nindicates that the host was online.",
		Run:   wrap(hostdbcmd),
	}

	hostdbViewCmd = &cobra.Command{
		Use:   "view [pubkey]",
		Short: "View the full information for a host.",
		Long:  "View detailed information about a host, including things like a score breakdown.",
		Run:   wrap(hostdbviewcmd),
	}
)

func hostdbcmd() {
	if !hostdbVerbose {
		info := new(api.HostdbActiveGET)
		err := getAPI("/hostdb/active", info)
		if err != nil {
			die("Could not fetch host list:", err)
		}
		if len(info.Hosts) == 0 {
			fmt.Println("No known active hosts")
			return
		}

		// Strip down to the number of requested hosts.
		if hostdbNumHosts != 0 && hostdbNumHosts < len(info.Hosts) {
			info.Hosts = info.Hosts[len(info.Hosts)-hostdbNumHosts:]
		}

		fmt.Println(len(info.Hosts), "Active Hosts:")
		w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
		fmt.Fprintln(w, "\t\tAddress\tPrice (per TB per Mo)")
		for i, host := range info.Hosts {
			price := host.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte)
			fmt.Fprintf(w, "\t%v:\t%v\t%v\n", len(info.Hosts)-i, host.NetAddress, currencyUnits(price))
		}
		w.Flush()
	} else {
		info := new(api.HostdbAllGET)
		err := getAPI("/hostdb/all", info)
		if err != nil {
			die("Could not fetch host list:", err)
		}
		if len(info.Hosts) == 0 {
			fmt.Println("No known hosts")
			return
		}

		// Iterate through the hosts and divide by category.
		var activeHosts, inactiveHosts, offlineHosts []api.ExtendedHostDBEntry
		for _, host := range info.Hosts {
			if host.AcceptingContracts && len(host.ScanHistory) > 0 && host.ScanHistory[len(host.ScanHistory)-1].Success {
				activeHosts = append(activeHosts, host)
				continue
			}
			if len(host.ScanHistory) > 0 && host.ScanHistory[len(host.ScanHistory)-1].Success {
				inactiveHosts = append(inactiveHosts, host)
				continue
			}
			offlineHosts = append(offlineHosts, host)
		}

		if hostdbNumHosts > 0 && len(offlineHosts) > hostdbNumHosts {
			offlineHosts = offlineHosts[len(offlineHosts)-hostdbNumHosts:]
		}
		if hostdbNumHosts > 0 && len(inactiveHosts) > hostdbNumHosts {
			inactiveHosts = inactiveHosts[len(inactiveHosts)-hostdbNumHosts:]
		}
		if hostdbNumHosts > 0 && len(activeHosts) > hostdbNumHosts {
			activeHosts = activeHosts[len(activeHosts)-hostdbNumHosts:]
		}

		fmt.Println()
		fmt.Println(len(offlineHosts), "Offline Hosts:")
		w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
		fmt.Fprintln(w, "\t\tPubkey\tAddress\tUptime\tPrice (/ TB / Month)\tDownload Price (/ TB)\tRecent Scans")
		for i, host := range offlineHosts {
			// Compute the total measured uptime and total measured downtime for this
			// host.
			uptimeRatio := float64(0)
			if len(host.ScanHistory) > 1 {
				var uptime time.Duration
				var downtime time.Duration
				recentTime := host.ScanHistory[0].Timestamp
				recentSuccess := host.ScanHistory[0].Success
				for _, scan := range host.ScanHistory[1:] {
					if recentSuccess {
						uptime += scan.Timestamp.Sub(recentTime)
					} else {
						downtime += scan.Timestamp.Sub(recentTime)
					}
					recentTime = scan.Timestamp
					recentSuccess = scan.Success
				}
				uptimeRatio = float64(uptime) / float64(uptime+downtime)
			}

			// Get the scan history string.
			scanHistStr := ""
			displayScans := host.ScanHistory
			if len(host.ScanHistory) > scanHistoryLen {
				displayScans = host.ScanHistory[len(host.ScanHistory)-scanHistoryLen:]
			}
			for _, scan := range displayScans {
				if scan.Success {
					scanHistStr += "1"
				} else {
					scanHistStr += "0"
				}
			}

			// Get a string representation of the historic outcomes of the most
			// recent scans.
			price := host.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte)
			downloadBWPrice := host.StoragePrice.Mul(modules.BytesPerTerabyte)
			fmt.Fprintf(w, "\t%v:\t%v\t%v\t%v\t%v\t%.3f\t%s\n", len(offlineHosts)-i, host.PublicKeyString, host.NetAddress, currencyUnits(price), currencyUnits(downloadBWPrice), uptimeRatio, scanHistStr)
		}
		w.Flush()

		fmt.Println()
		fmt.Println(len(inactiveHosts), "Inactive Hosts:")
		w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
		fmt.Fprintln(w, "\t\tPubkey\tAddress\tPrice (/ TB / Month)\tDownload Price (/ TB)\tUptime\tRecent Scans")
		for i, host := range inactiveHosts {
			// Compute the total measured uptime and total measured downtime for this
			// host.
			uptimeRatio := float64(0)
			if len(host.ScanHistory) > 1 {
				var uptime time.Duration
				var downtime time.Duration
				recentTime := host.ScanHistory[0].Timestamp
				recentSuccess := host.ScanHistory[0].Success
				for _, scan := range host.ScanHistory[1:] {
					if recentSuccess {
						uptime += scan.Timestamp.Sub(recentTime)
					} else {
						downtime += scan.Timestamp.Sub(recentTime)
					}
					recentTime = scan.Timestamp
					recentSuccess = scan.Success
				}
				uptimeRatio = float64(uptime) / float64(uptime+downtime)
			}

			// Get a string representation of the historic outcomes of the most
			// recent scans.
			scanHistStr := ""
			displayScans := host.ScanHistory
			if len(host.ScanHistory) > scanHistoryLen {
				displayScans = host.ScanHistory[len(host.ScanHistory)-scanHistoryLen:]
			}
			for _, scan := range displayScans {
				if scan.Success {
					scanHistStr += "1"
				} else {
					scanHistStr += "0"
				}
			}

			price := host.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte)
			downloadBWPrice := host.DownloadBandwidthPrice.Mul(modules.BytesPerTerabyte)
			fmt.Fprintf(w, "\t%v:\t%v\t%v\t%v\t%v\t%.3f\t%s\n", len(inactiveHosts)-i, host.PublicKeyString, host.NetAddress, currencyUnits(price), currencyUnits(downloadBWPrice), uptimeRatio, scanHistStr)
		}
		w.Flush()

		// Grab the host at the 1/5th point and use it as the reference. (it's
		// like using the median, except at the 1/5th point instead of the 1/2
		// point.)
		referenceScore := float64(1)
		if len(activeHosts) > 0 {
			referenceIndex := len(activeHosts)/5
			hostInfo := new(api.HostdbHostsGET)
			err := getAPI("/hostdb/hosts/"+activeHosts[referenceIndex].PublicKeyString, hostInfo)
			if err != nil {
				die("Could not fetch provided host:", err)
			}
			referenceScore, _ = big.NewFloat(1).SetInt(hostInfo.ScoreBreakdown.Score.Big()).Float64()
		}

		fmt.Println()
		fmt.Println(len(activeHosts), "Active Hosts:")
		w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
		fmt.Fprintln(w, "\t\tPubkey\tAddress\tScore\tPrice (/ TB / Month)\tDownload Price (/TB)\tUptime\tRecent Scans")
		for i, host := range activeHosts {
			// Compute the total measured uptime and total measured downtime for this
			// host.
			uptimeRatio := float64(0)
			if len(host.ScanHistory) > 1 {
				var uptime time.Duration
				var downtime time.Duration
				recentTime := host.ScanHistory[0].Timestamp
				recentSuccess := host.ScanHistory[0].Success
				for _, scan := range host.ScanHistory[1:] {
					if recentSuccess {
						uptime += scan.Timestamp.Sub(recentTime)
					} else {
						downtime += scan.Timestamp.Sub(recentTime)
					}
					recentTime = scan.Timestamp
					recentSuccess = scan.Success
				}
				uptimeRatio = float64(uptime) / float64(uptime+downtime)
			}

			// Get a string representation of the historic outcomes of the most
			// recent scans.
			scanHistStr := ""
			displayScans := host.ScanHistory
			if len(host.ScanHistory) > scanHistoryLen {
				displayScans = host.ScanHistory[len(host.ScanHistory)-scanHistoryLen:]
			}
			for _, scan := range displayScans {
				if scan.Success {
					scanHistStr += "1"
				} else {
					scanHistStr += "0"
				}
			}

			// Grab the score information for the active hosts.
			hostInfo := new(api.HostdbHostsGET)
			err := getAPI("/hostdb/hosts/"+host.PublicKeyString, hostInfo)
			if err != nil {
				die("Could not fetch provided host:", err)
			}
			score, _ := big.NewFloat(1).SetInt(hostInfo.ScoreBreakdown.Score.Big()).Float64()
			score /= referenceScore

			price := host.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte)
			downloadBWPrice := host.DownloadBandwidthPrice.Mul(modules.BytesPerTerabyte)
			fmt.Fprintf(w, "\t%v:\t%v\t%v\t%12.6g\t%v\t%v\t%.3f\t%s\n", len(activeHosts)-i, host.PublicKeyString, host.NetAddress, score, currencyUnits(price), currencyUnits(downloadBWPrice), uptimeRatio, scanHistStr)
		}
		w.Flush()
	}
}

func hostdbviewcmd(pubkey string) {
	info := new(api.HostdbHostsGET)
	err := getAPI("/hostdb/hosts/"+pubkey, info)
	if err != nil {
		die("Could not fetch provided host:", err)
	}

	fmt.Println("Host information:")

	fmt.Println("  Public Key:", info.Entry.PublicKeyString)
	fmt.Println("  Block First Seen:", info.Entry.FirstSeen)

	fmt.Println("\n  Host Settings:")
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	fmt.Fprintln(w, "\t\tAccepting Contracts:\t", info.Entry.AcceptingContracts)
	fmt.Fprintln(w, "\t\tTotal Storage:\t", info.Entry.TotalStorage/1e9, "GB")
	fmt.Fprintln(w, "\t\tRemaining Storage:\t", info.Entry.RemainingStorage/1e9, "GB")
	fmt.Fprintln(w, "\t\tOffered Collateral (TB / Mo):\t", currencyUnits(info.Entry.Collateral.Mul(modules.BlockBytesPerMonthTerabyte)))
	fmt.Fprintln(w, "\n\t\tContract Price:\t", currencyUnits(info.Entry.ContractPrice))
	fmt.Fprintln(w, "\t\tStorage Price (TB / Mo):\t", currencyUnits(info.Entry.StoragePrice.Mul(modules.BlockBytesPerMonthTerabyte)))
	fmt.Fprintln(w, "\t\tDownload Price (1 TB):\t", currencyUnits(info.Entry.DownloadBandwidthPrice.Mul(modules.BytesPerTerabyte)))
	fmt.Fprintln(w, "\t\tUpload Price (1 TB):\t", currencyUnits(info.Entry.UploadBandwidthPrice.Mul(modules.BytesPerTerabyte)))
	fmt.Fprintln(w, "\t\tVersion:\t", info.Entry.Version)
	w.Flush()

	fmt.Println("\n  Score Breakdown:")
	w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	total := info.ScoreBreakdown.Score
	fmt.Fprintf(w, "\t\tTotal Score:\t %v\n\n", total)
	fmt.Fprintf(w, "\t\tAge:\t %.3f\n", info.ScoreBreakdown.AgeAdjustment)
	fmt.Fprintf(w, "\t\tBurn:\t %.3f\n", info.ScoreBreakdown.BurnAdjustment)
	fmt.Fprintf(w, "\t\tCollateral:\t %.3f\n", info.ScoreBreakdown.CollateralAdjustment)
	fmt.Fprintf(w, "\t\tPrice:\t %.3f\n", info.ScoreBreakdown.PriceAdjustment*1e6)
	fmt.Fprintf(w, "\t\tStorage:\t %.3f\n", info.ScoreBreakdown.StorageRemainingAdjustment)
	fmt.Fprintf(w, "\t\tUptime:\t %.3f\n", info.ScoreBreakdown.UptimeAdjustment)
	fmt.Fprintf(w, "\t\tVersion:\t %.3f\n", info.ScoreBreakdown.VersionAdjustment)
	w.Flush()

	// Compute the total measured uptime and total measured downtime for this
	// host.
	uptimeRatio := float64(0)
	if len(info.Entry.ScanHistory) > 1 {
		downtime := info.Entry.HistoricDowntime
		uptime := info.Entry.HistoricUptime
		recentTime := info.Entry.ScanHistory[0].Timestamp
		recentSuccess := info.Entry.ScanHistory[0].Success
		for _, scan := range info.Entry.ScanHistory[1:] {
			if recentSuccess {
				uptime += scan.Timestamp.Sub(recentTime)
			} else {
				downtime += scan.Timestamp.Sub(recentTime)
			}
			recentTime = scan.Timestamp
			recentSuccess = scan.Success
		}
		uptimeRatio = float64(uptime) / float64(uptime+downtime)
	}

	// Compute the uptime ratio, but shift by 0.02 to acknowledge fully that
	// 98% uptime and 100% uptime is valued the same.
	fmt.Println("\n  Scan History Length:", len(info.Entry.ScanHistory))
	fmt.Printf("  Overall Uptime:      %.3f\n", uptimeRatio)

	fmt.Println()
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"math/big"
	"strings"

	"github.com/spf13/cobra"

	"github.com/NebulousLabs/Sia/api"
	"github.com/NebulousLabs/Sia/modules"
)

// coinUnits converts a siacoin amount to base units.
func coinUnits(amount string) (string, error) {
	units := []string{"pS", "nS", "uS", "mS", "SC", "KS", "MS", "GS", "TS"}
	for i, unit := range units {
		if strings.HasSuffix(amount, unit) {
			// scan into big.Rat
			r, ok := new(big.Rat).SetString(strings.TrimSuffix(amount, unit))
			if !ok {
				return "", errors.New("malformed amount")
			}
			// convert units
			exp := 24 + 3*(int64(i)-4)
			mag := new(big.Int).Exp(big.NewInt(10), big.NewInt(exp), nil)
			r.Mul(r, new(big.Rat).SetInt(mag))
			// r must be an integer at this point
			if !r.IsInt() {
				return "", errors.New("non-integer number of hastings")
			}
			return r.RatString(), nil
		}
	}
	return amount, nil // hastings
}

var (
	walletCmd = &cobra.Command{
		Use:   "wallet",
		Short: "Perform wallet actions",
		Long: `Generate a new address, send coins to another wallet, or view info about the wallet.

Units:
The smallest unit of siacoins is the hasting. One siacoin is 10^24 hastings. Other supported units are:
  pS (pico,  10^-12 SC)
  nS (nano,  10^-9 SC)
  uS (micro, 10^-6 SC)
  mS (milli, 10^-3 SC)
  SC
  KS (kilo, 10^3 SC)
  MS (mega, 10^6 SC)
  GS (giga, 10^9 SC)
  TS (tera, 10^12 SC)`,
		Run: wrap(walletstatuscmd),
	}

	walletAddressCmd = &cobra.Command{
		Use:   "address",
		Short: "Get a new wallet address",
		Long:  "Generate a new wallet address.",
		Run:   wrap(walletaddresscmd),
	}

	walletSendCmd = &cobra.Command{
		Use:   "send [amount] [dest]",
		Short: "Send coins to another wallet",
		Long: `Send coins to another wallet. 'dest' must be a 76-byte hexadecimal address.
'amount' can be specified in units, e.g. 1.23KS. Run 'wallet --help' for a list of units.
If no unit is supplied, hastings will be assumed.

A miner fee of 10 SC is levied on all transactions.`,
		Run: wrap(walletsendcmd),
	}

	walletSiafundsCmd = &cobra.Command{
		Use:   "siafunds",
		Short: "Display siafunds balance",
		Long:  "Display siafunds balance and siacoin claim balance.",
		Run:   wrap(walletsiafundscmd),
	}

	walletSiafundsSendCmd = &cobra.Command{
		Use:   "send [amount] [dest] [keyfiles]",
		Short: "Send siafunds",
		Long: `Send siafunds to an address, and transfer their siacoins to the wallet.
Run 'wallet send --help' to see a list of available units.`,
		Run: walletsiafundssendcmd, // see function docstring
	}

	walletSiafundsTrackCmd = &cobra.Command{
		Use:   "track [keyfile]",
		Short: "Track a siafund address generated by siag",
		Long:  "Track a siafund address generated by siag.",
		Run:   wrap(walletsiafundstrackcmd),
	}

	walletStatusCmd = &cobra.Command{
		Use:   "status",
		Short: "View wallet status",
		Long:  "View wallet status, including the current balance and number of addresses.",
		Run:   wrap(walletstatuscmd),
	}
)

// TODO: this should be defined outside of siac
type walletAddr struct {
	Address string
}

func walletaddresscmd() {
	addr := new(walletAddr)
	err := getAPI("/wallet/address", addr)
	if err != nil {
		fmt.Println("Could not generate new address:", err)
		return
	}
	fmt.Printf("Created new address: %s\n", addr.Address)
}

func walletsendcmd(amount, dest string) {
	adjAmount, err := coinUnits(amount)
	if err != nil {
		fmt.Println("Could not parse amount:", err)
		return
	}
	err = post("/wallet/send", fmt.Sprintf("amount=%s&destination=%s", adjAmount, dest))
	if err != nil {
		fmt.Println("Could not send:", err)
		return
	}
	fmt.Printf("Sent %s hastings to %s\n", adjAmount, dest)
}

func walletsiafundscmd() {
	bal := new(api.WalletSiafundsBalance)
	err := getAPI("/wallet/siafunds/balance", bal)
	if err != nil {
		fmt.Println("Could not get siafunds balance:", err)
		return
	}
	fmt.Printf("Siafunds Balance: %s\nClaim Balance: %s\n", bal.SiafundBalance, bal.SiacoinClaimBalance)
}

// special because list of keyfiles is variadic
func walletsiafundssendcmd(cmd *cobra.Command, args []string) {
	if len(args) < 3 {
		cmd.Usage()
		return
	}
	amount, dest, keyfiles := args[0], args[1], args[2:]
	for i := range keyfiles {
		keyfiles[i] = abs(keyfiles[i])
	}
	qs := fmt.Sprintf("amount=%s&destination=%s&keyfiles=%s", amount, dest, strings.Join(keyfiles, ","))

	err := post("/wallet/siafunds/send", qs)
	if err != nil {
		fmt.Println("Could not track siafunds:", err)
		return
	}
	fmt.Printf("Sent %s siafunds to %s\n", amount, dest)
}

func walletsiafundstrackcmd(keyfile string) {
	err := post("/wallet/siafunds/watchsiagaddress", "keyfile="+abs(keyfile))
	if err != nil {
		fmt.Println("Could not track siafunds:", err)
		return
	}
	fmt.Printf(`Added %s to tracked siafunds.

You must restart siad to update your siafund balance.
Do not delete the original keyfile.
`, keyfile)
}

func walletstatuscmd() {
	status := new(modules.WalletInfo)
	err := getAPI("/wallet/status", status)
	if err != nil {
		fmt.Println("Could not get wallet status:", err)
		return
	}
	// divide by 1e24 to get SC
	r := new(big.Rat).SetFrac(status.Balance.Big(), new(big.Int).Exp(big.NewInt(10), big.NewInt(24), nil))
	sc, _ := r.Float64()
	fmt.Printf(`Wallet status:
Balance:   %.2f SC
Exact:     %v
Addresses: %d
`, sc, status.Balance, status.NumAddresses)
}
<commit_msg>force user to specify unit<commit_after>package main

import (
	"errors"
	"fmt"
	"math/big"
	"strings"

	"github.com/spf13/cobra"

	"github.com/NebulousLabs/Sia/api"
	"github.com/NebulousLabs/Sia/modules"
)

// coinUnits converts a siacoin amount to base units.
func coinUnits(amount string) (string, error) {
	units := []string{"pS", "nS", "uS", "mS", "SC", "KS", "MS", "GS", "TS"}
	for i, unit := range units {
		if strings.HasSuffix(amount, unit) {
			// scan into big.Rat
			r, ok := new(big.Rat).SetString(strings.TrimSuffix(amount, unit))
			if !ok {
				return "", errors.New("malformed amount")
			}
			// convert units
			exp := 24 + 3*(int64(i)-4)
			mag := new(big.Int).Exp(big.NewInt(10), big.NewInt(exp), nil)
			r.Mul(r, new(big.Rat).SetInt(mag))
			// r must be an integer at this point
			if !r.IsInt() {
				return "", errors.New("non-integer number of hastings")
			}
			return r.RatString(), nil
		}
	}
	// check for hastings separately
	if strings.HasSuffix(amount, "H") {
		return strings.TrimSuffix(amount, "H"), nil
	}

	return "", errors.New("amount is missing units; run 'wallet --help' for a list of units")
}

var (
	walletCmd = &cobra.Command{
		Use:   "wallet",
		Short: "Perform wallet actions",
		Long: `Generate a new address, send coins to another wallet, or view info about the wallet.

Units:
The smallest unit of siacoins is the hasting. One siacoin is 10^24 hastings. Other supported units are:
  pS (pico,  10^-12 SC)
  nS (nano,  10^-9 SC)
  uS (micro, 10^-6 SC)
  mS (milli, 10^-3 SC)
  SC
  KS (kilo, 10^3 SC)
  MS (mega, 10^6 SC)
  GS (giga, 10^9 SC)
  TS (tera, 10^12 SC)`,
		Run: wrap(walletstatuscmd),
	}

	walletAddressCmd = &cobra.Command{
		Use:   "address",
		Short: "Get a new wallet address",
		Long:  "Generate a new wallet address.",
		Run:   wrap(walletaddresscmd),
	}

	walletSendCmd = &cobra.Command{
		Use:   "send [amount] [dest]",
		Short: "Send coins to another wallet",
		Long: `Send coins to another wallet. 'dest' must be a 76-byte hexadecimal address.
'amount' can be specified in units, e.g. 1.23KS. Run 'wallet --help' for a list of units.
If no unit is supplied, hastings will be assumed.

A miner fee of 10 SC is levied on all transactions.`,
		Run: wrap(walletsendcmd),
	}

	walletSiafundsCmd = &cobra.Command{
		Use:   "siafunds",
		Short: "Display siafunds balance",
		Long:  "Display siafunds balance and siacoin claim balance.",
		Run:   wrap(walletsiafundscmd),
	}

	walletSiafundsSendCmd = &cobra.Command{
		Use:   "send [amount] [dest] [keyfiles]",
		Short: "Send siafunds",
		Long: `Send siafunds to an address, and transfer their siacoins to the wallet.
Run 'wallet send --help' to see a list of available units.`,
		Run: walletsiafundssendcmd, // see function docstring
	}

	walletSiafundsTrackCmd = &cobra.Command{
		Use:   "track [keyfile]",
		Short: "Track a siafund address generated by siag",
		Long:  "Track a siafund address generated by siag.",
		Run:   wrap(walletsiafundstrackcmd),
	}

	walletStatusCmd = &cobra.Command{
		Use:   "status",
		Short: "View wallet status",
		Long:  "View wallet status, including the current balance and number of addresses.",
		Run:   wrap(walletstatuscmd),
	}
)

// TODO: this should be defined outside of siac
type walletAddr struct {
	Address string
}

func walletaddresscmd() {
	addr := new(walletAddr)
	err := getAPI("/wallet/address", addr)
	if err != nil {
		fmt.Println("Could not generate new address:", err)
		return
	}
	fmt.Printf("Created new address: %s\n", addr.Address)
}

func walletsendcmd(amount, dest string) {
	adjAmount, err := coinUnits(amount)
	if err != nil {
		fmt.Println("Could not parse amount:", err)
		return
	}
	err = post("/wallet/send", fmt.Sprintf("amount=%s&destination=%s", adjAmount, dest))
	if err != nil {
		fmt.Println("Could not send:", err)
		return
	}
	fmt.Printf("Sent %s hastings to %s\n", adjAmount, dest)
}

func walletsiafundscmd() {
	bal := new(api.WalletSiafundsBalance)
	err := getAPI("/wallet/siafunds/balance", bal)
	if err != nil {
		fmt.Println("Could not get siafunds balance:", err)
		return
	}
	fmt.Printf("Siafunds Balance: %s\nClaim Balance: %s\n", bal.SiafundBalance, bal.SiacoinClaimBalance)
}

// special because list of keyfiles is variadic
func walletsiafundssendcmd(cmd *cobra.Command, args []string) {
	if len(args) < 3 {
		cmd.Usage()
		return
	}
	amount, dest, keyfiles := args[0], args[1], args[2:]
	for i := range keyfiles {
		keyfiles[i] = abs(keyfiles[i])
	}
	qs := fmt.Sprintf("amount=%s&destination=%s&keyfiles=%s", amount, dest, strings.Join(keyfiles, ","))

	err := post("/wallet/siafunds/send", qs)
	if err != nil {
		fmt.Println("Could not track siafunds:", err)
		return
	}
	fmt.Printf("Sent %s siafunds to %s\n", amount, dest)
}

func walletsiafundstrackcmd(keyfile string) {
	err := post("/wallet/siafunds/watchsiagaddress", "keyfile="+abs(keyfile))
	if err != nil {
		fmt.Println("Could not track siafunds:", err)
		return
	}
	fmt.Printf(`Added %s to tracked siafunds.

You must restart siad to update your siafund balance.
Do not delete the original keyfile.
`, keyfile)
}

func walletstatuscmd() {
	status := new(modules.WalletInfo)
	err := getAPI("/wallet/status", status)
	if err != nil {
		fmt.Println("Could not get wallet status:", err)
		return
	}
	// divide by 1e24 to get SC
	r := new(big.Rat).SetFrac(status.Balance.Big(), new(big.Int).Exp(big.NewInt(10), big.NewInt(24), nil))
	sc, _ := r.Float64()
	fmt.Printf(`Wallet status:
Balance:   %.2f SC
Exact:     %v
Addresses: %d
`, sc, status.Balance, status.NumAddresses)
}
<|endoftext|>


<commit_before>package simplelog

import (
	"bytes"
	"io"
	"testing"
	"time"
)

func TestDefaultStdout(t *testing.T) {
	Trace("Some text printed by Trace()")
	Debug("Some text printed by Debug()")
	Info("Some text printed by Info()")
	err := Warning("Some text printed by Warning()")
	if err == nil || err.Error() != "[WARNING] Some text printed by Warning()" {
		t.Error("Warning() return wrong value.")
	}
	err = Error("Some text printed by Error()")
	if err == nil || err.Error() != "[ERROR] Some text printed by Error()" {
		t.Error("Error() return wrong value.")
	}

	root.restart()
}

func TestCustomLog(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_TRACE, W_BOTH)

	tests := map[string]func(string, ...interface{}){
		"TRACE": logger.Trace,
		"DEBUG": logger.Debug,
		"INFO":  logger.Info,
	}

	for n, f := range tests {
		f("Some %s passed to "+n+"()", "text")
		logger.restart()
		now := time.Now()
		expect := now.Format("2006/01/02 15:04:05") + " [" + n + "] Some text passed to " + n + "()\n"
		res, err := buf.ReadBytes('\n')
		if err != nil && err != io.EOF {
			t.Error(err)
		}
		if string(res) != expect {
			t.Errorf(n+"() printed wrong log expect '%s' got '%s'\n", expect, res)
		}
	}

	tests2 := map[string]func(string, ...interface{}) error{
		"WARNING": logger.Warning,
		"ERROR":   logger.Error,
	}

	for n, f := range tests2 {
		f("Some %s passed to "+n+"()", "text")
		logger.restart()
		now := time.Now()
		expect := now.Format("2006/01/02 15:04:05") + " [" + n + "] Some text passed to " + n + "()\n"
		res, err := buf.ReadBytes('\n')
		if err != nil && err != io.EOF {
			t.Error(err)
		}
		if string(res) != expect {
			t.Errorf(n+"() printed wrong log expect '%s' got '%s'\n", expect, res)
		}
	}

}

func TestCustomLogLevel(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_WARNING, W_BOTH)
	logger.Trace("Some text from Trace()")
	logger.Debug("Some text from DEBUG()")
	logger.Info("Some text from INFO()")
	logger.restart()

	res, err := buf.ReadBytes('\n')
	if err != io.EOF {
		t.Error("Log to wrong level")
	}

	logger.Warning("Some text from Warning()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if len(res) < 0 {
		t.Error("Log to wrong level")
	}

	logger.Error("Some text from Error()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if len(res) < 0 {
		t.Error("Log to wrong level")
	}
}

func TestCustomLogWayInfo(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_TRACE, W_INFO)

	logger.Warning("Some text from Warning()")
	logger.restart()
	res, err := buf.ReadBytes('\n')
	if err != io.EOF {
		t.Error("Log to wrong way")
	}

	logger.Error("Some text from Error()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != io.EOF {
		t.Error("Log to wrong way")
	}

	logger.Trace("Some text from Trace()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if len(res) < 0 {
		t.Error("Log to wrong way")
	}

	logger.Info("Some text from Info()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if len(res) < 0 {
		t.Error("Log to wrong way")
	}
}

func TestCustomLogWayError(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_WARNING, W_ERROR)

	logger.Trace("Some text from Trace()")
	logger.restart()
	res, err := buf.ReadBytes('\n')
	if err != io.EOF {
		t.Error("Log to wrong way")
	}

	logger.Debug("Some text from Debug()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != io.EOF {
		t.Error("Log to wrong way")
	}

	logger.Error("Some text from Error()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if len(res) < 0 {
		t.Error("Log to wrong way")
	}

	logger.Warning("Some text from Warning()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if len(res) < 0 {
		t.Error("Log to wrong way")
	}
}

func TestCustomLogPrefix(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_TRACE, W_BOTH)
	l := logger.Prefixed([]string{"HYPO", "CINDY"})
	l.Info("Some %s passed to Info()", "text")
	logger.restart()
	now := time.Now()
	expect := now.Format("2006/01/02 15:04:05") + " [INFO] [HYPO|CINDY] Some text passed to Info()\n"
	res, err := buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if string(res) != expect {
		t.Errorf("Logger wrote wrong prefix expect '%s' got '%s'", expect, res)
	}

}

func TestMuiltLogger(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_TRACE, W_BOTH)
	logger.AddWriter(buf, L_TRACE, W_BOTH)
	logger.Info("Some text passed to Info()")
	logger.restart()

	now := time.Now()
	expect := now.Format("2006/01/02 15:04:05") + " [INFO] Some text passed to Info()\n"
	res, err := buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if string(res) != expect {
		t.Errorf("Logger print wrong log expect '%s' got '%s'", expect, res)
	}

	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if string(res) != expect {
		t.Errorf("Logger print wrong log expect '%s' got '%s'", expect, res)
	}
}
<commit_msg>增加对Prefix的测试代码<commit_after>package simplelog

import (
	"bytes"
	"io"
	"strings"
	"testing"
	"time"
)

func TestDefaultStdout(t *testing.T) {
	Trace("Some text printed by Trace()")
	Debug("Some text printed by Debug()")
	Info("Some text printed by Info()")
	err := Warning("Some text printed by Warning()")
	if err == nil || err.Error() != "[WARNING] Some text printed by Warning()" {
		t.Error("Warning() return wrong value.")
	}
	err = Error("Some text printed by Error()")
	if err == nil || err.Error() != "[ERROR] Some text printed by Error()" {
		t.Error("Error() return wrong value.")
	}

	root.restart()
}

func TestCustomLog(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_TRACE, W_BOTH)

	tests := map[string]func(string, ...interface{}){
		"TRACE": logger.Trace,
		"DEBUG": logger.Debug,
		"INFO":  logger.Info,
	}

	for n, f := range tests {
		f("Some %s passed to "+n+"()", "text")
		logger.restart()
		now := time.Now()
		expect := now.Format("2006/01/02 15:04:05") + " [" + n + "] Some text passed to " + n + "()\n"
		res, err := buf.ReadBytes('\n')
		if err != nil && err != io.EOF {
			t.Error(err)
		}
		if string(res) != expect {
			t.Errorf(n+"() printed wrong log expect '%s' got '%s'\n", expect, res)
		}
	}

	tests2 := map[string]func(string, ...interface{}) error{
		"WARNING": logger.Warning,
		"ERROR":   logger.Error,
	}

	for n, f := range tests2 {
		f("Some %s passed to "+n+"()", "text")
		logger.restart()
		now := time.Now()
		expect := now.Format("2006/01/02 15:04:05") + " [" + n + "] Some text passed to " + n + "()\n"
		res, err := buf.ReadBytes('\n')
		if err != nil && err != io.EOF {
			t.Error(err)
		}
		if string(res) != expect {
			t.Errorf(n+"() printed wrong log expect '%s' got '%s'\n", expect, res)
		}
	}

}

func TestCustomLogLevel(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_WARNING, W_BOTH)
	logger.Trace("Some text from Trace()")
	logger.Debug("Some text from DEBUG()")
	logger.Info("Some text from INFO()")
	logger.restart()

	res, err := buf.ReadBytes('\n')
	if err != io.EOF {
		t.Error("Log to wrong level")
	}

	logger.Warning("Some text from Warning()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if len(res) < 0 {
		t.Error("Log to wrong level")
	}

	logger.Error("Some text from Error()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if len(res) < 0 {
		t.Error("Log to wrong level")
	}
}

func TestCustomLogWayInfo(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_TRACE, W_INFO)

	logger.Warning("Some text from Warning()")
	logger.restart()
	res, err := buf.ReadBytes('\n')
	if err != io.EOF {
		t.Error("Log to wrong way")
	}

	logger.Error("Some text from Error()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != io.EOF {
		t.Error("Log to wrong way")
	}

	logger.Trace("Some text from Trace()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if len(res) < 0 {
		t.Error("Log to wrong way")
	}

	logger.Info("Some text from Info()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if len(res) < 0 {
		t.Error("Log to wrong way")
	}
}

func TestCustomLogWayError(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_WARNING, W_ERROR)

	logger.Trace("Some text from Trace()")
	logger.restart()
	res, err := buf.ReadBytes('\n')
	if err != io.EOF {
		t.Error("Log to wrong way")
	}

	logger.Debug("Some text from Debug()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != io.EOF {
		t.Error("Log to wrong way")
	}

	logger.Error("Some text from Error()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if len(res) < 0 {
		t.Error("Log to wrong way")
	}

	logger.Warning("Some text from Warning()")
	logger.restart()
	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if len(res) < 0 {
		t.Error("Log to wrong way")
	}
}

func TestCustomLogPrefix(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_TRACE, W_BOTH)
	l := logger.Prefixed([]string{"HYPO", "CINDY"})
	l.Info("Some %s passed to Info()", "text")
	logger.restart()
	now := time.Now()
	expect := now.Format("2006/01/02 15:04:05") + " [INFO] [HYPO|CINDY] Some text passed to Info()\n"
	res, err := buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if string(res) != expect {
		t.Errorf("Logger wrote wrong prefix expect '%s' got '%s'", expect, res)
	}
}

func TestCustomLogPrefixOpreation(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_TRACE, W_BOTH)
	l := logger.Prefixed([]string{"HYPO"})
	jp := func(s []string) string {
		return P_STAR + strings.Join(s, P_SEPE) + P_END
	}
	if l.prefix != jp([]string{"HYPO"}) {
		t.Errorf("Prefix generated wrong prefix expect '%s' got '%s'", jp([]string{"HYPO"}), l.prefix)
	}

	l.AppendPrefix("CINDY")
	if l.prefix != jp([]string{"HYPO", "CINDY"}) {
		t.Errorf("Prefix generated wrong prefix got '%s'", l.prefix)
	}

	l.PrependPrefix("HU")
	if l.prefix != jp([]string{"HU", "HYPO", "CINDY"}) {
		t.Errorf("Prefix generated wrong prefix got '%s'", l.prefix)
	}

	l.CleanPrefix()
	if l.prefix != jp([]string{""}) {
		t.Errorf("Prefix generated wrong prefix got '%s'", l.prefix)
	}

	l.PrependPrefix("HYPO")
	if l.prefix != jp([]string{"HYPO"}) {
		t.Errorf("Prefix generated wrong prefix got '%s'", l.prefix)
	}

	l.CleanPrefix()
	l.AppendPrefix("HYPO")
	if l.prefix != jp([]string{"HYPO"}) {
		t.Errorf("Prefix generated wrong prefix got '%s'", l.prefix)
	}
}

func TestMuiltLogger(t *testing.T) {
	buf := &bytes.Buffer{}
	logger := New()
	logger.AddWriter(buf, L_TRACE, W_BOTH)
	logger.AddWriter(buf, L_TRACE, W_BOTH)
	logger.Info("Some text passed to Info()")
	logger.restart()

	now := time.Now()
	expect := now.Format("2006/01/02 15:04:05") + " [INFO] Some text passed to Info()\n"
	res, err := buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if string(res) != expect {
		t.Errorf("Logger print wrong log expect '%s' got '%s'", expect, res)
	}

	res, err = buf.ReadBytes('\n')
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if string(res) != expect {
		t.Errorf("Logger print wrong log expect '%s' got '%s'", expect, res)
	}
}
<|endoftext|>