<commit_before>package statsd

// In package metrics so we can stub tick.

import (
	"bytes"
	"fmt"
	"runtime"
	"testing"
	"time"
)

func TestCounter(t *testing.T) {
	ch := make(chan time.Time)
	tick = func(time.Duration) <-chan time.Time { return ch }
	defer func() { tick = time.Tick }()

	buf := &bytes.Buffer{}
	c := NewCounter(buf, "test_statsd_counter", time.Second)

	c.Add(1)
	c.Add(2)
	ch <- time.Now()

	for i := 0; i < 10 && buf.Len() == 0; i++ {
		time.Sleep(time.Millisecond)
	}

	if want, have := "test_statsd_counter:1|c\ntest_statsd_counter:2|c\n", buf.String(); want != have {
		t.Errorf("want %q, have %q", want, have)
	}
}

func TestGauge(t *testing.T) {
	ch := make(chan time.Time)
	tick = func(time.Duration) <-chan time.Time { return ch }
	defer func() { tick = time.Tick }()

	buf := &bytes.Buffer{}
	g := NewGauge(buf, "test_statsd_gauge", time.Second)

	delta := 1.0
	g.Add(delta)      // send command
	runtime.Gosched() // yield to buffer write
	ch <- time.Now()  // signal flush
	runtime.Gosched() // yield to flush
	if want, have := fmt.Sprintf("test_statsd_gauge:+%f|g\n", delta), buf.String(); want != have {
		t.Errorf("want %q, have %q", want, have)
	}

	buf.Reset()

	delta = -2.0
	g.Add(delta)
	runtime.Gosched()
	ch <- time.Now()
	runtime.Gosched()
	if want, have := fmt.Sprintf("test_statsd_gauge:%f|g\n", delta), buf.String(); want != have {
		t.Errorf("want %q, have %q", want, have)
	}

	buf.Reset()

	value := 3.0
	g.Set(value)
	runtime.Gosched()
	ch <- time.Now()
	runtime.Gosched()
	if want, have := fmt.Sprintf("test_statsd_gauge:%f|g\n", value), buf.String(); want != have {
		t.Errorf("want %q, have %q", want, have)
	}
}

func TestCallbackGauge(t *testing.T) {
	ch := make(chan time.Time)
	tick = func(time.Duration) <-chan time.Time { return ch }
	defer func() { tick = time.Tick }()

	buf := &bytes.Buffer{}
	value := 55.55
	cb := func() float64 { return value }
	NewCallbackGauge(buf, "test_statsd_callback_gauge", time.Second, time.Nanosecond, cb)

	ch <- time.Now()  // signal emitter
	runtime.Gosched() // yield to emitter
	ch <- time.Now()  // signal flush
	runtime.Gosched() // yield to flush

	// Travis is annoying
	by(t, time.Second,
		func() bool { return buf.String() != "" },
		func() { ch <- time.Now(); runtime.Gosched(); time.Sleep(5 * time.Millisecond) },
		"buffer never got write+flush",
	)

	if want, have := fmt.Sprintf("test_statsd_callback_gauge:%f|g\n", value), buf.String(); want != have {
		t.Errorf("want %q, have %q", want, have)
	}
}

func TestHistogram(t *testing.T) {
	ch := make(chan time.Time)
	tick = func(time.Duration) <-chan time.Time { return ch }
	defer func() { tick = time.Tick }()

	buf := &bytes.Buffer{}
	h := NewHistogram(buf, "test_statsd_histogram", time.Second)

	h.Observe(123)

	runtime.Gosched()
	ch <- time.Now()
	runtime.Gosched()
	if want, have := "test_statsd_histogram:123|ms\n", buf.String(); want != have {
		t.Errorf("want %q, have %q", want, have)
	}
}

func by(t *testing.T, d time.Duration, b func() bool, c func(), msg string) {
	deadline := time.Now().Add(d)
	for !b() {
		if time.Now().After(deadline) {
			t.Fatal(msg)
		}
		c()
	}
}
<commit_msg>Nicer fix for flaky Travis<commit_after>package statsd

// In package metrics so we can stub tick.

import (
	"bytes"
	"fmt"
	"runtime"
	"testing"
	"time"
)

func TestCounter(t *testing.T) {
	ch := make(chan time.Time)
	tick = func(time.Duration) <-chan time.Time { return ch }
	defer func() { tick = time.Tick }()

	buf := &bytes.Buffer{}
	c := NewCounter(buf, "test_statsd_counter", time.Second)

	c.Add(1)
	c.Add(2)
	ch <- time.Now()

	for i := 0; i < 10 && buf.Len() == 0; i++ {
		time.Sleep(time.Millisecond)
	}

	if want, have := "test_statsd_counter:1|c\ntest_statsd_counter:2|c\n", buf.String(); want != have {
		t.Errorf("want %q, have %q", want, have)
	}
}

func TestGauge(t *testing.T) {
	ch := make(chan time.Time)
	tick = func(time.Duration) <-chan time.Time { return ch }
	defer func() { tick = time.Tick }()

	buf := &bytes.Buffer{}
	g := NewGauge(buf, "test_statsd_gauge", time.Second)

	delta := 1.0
	g.Add(delta)      // send command
	runtime.Gosched() // yield to buffer write
	ch <- time.Now()  // signal flush
	runtime.Gosched() // yield to flush
	if want, have := fmt.Sprintf("test_statsd_gauge:+%f|g\n", delta), buf.String(); want != have {
		t.Errorf("want %q, have %q", want, have)
	}

	buf.Reset()

	delta = -2.0
	g.Add(delta)
	runtime.Gosched()
	ch <- time.Now()
	runtime.Gosched()
	if want, have := fmt.Sprintf("test_statsd_gauge:%f|g\n", delta), buf.String(); want != have {
		t.Errorf("want %q, have %q", want, have)
	}

	buf.Reset()

	value := 3.0
	g.Set(value)
	runtime.Gosched()
	ch <- time.Now()
	runtime.Gosched()
	if want, have := fmt.Sprintf("test_statsd_gauge:%f|g\n", value), buf.String(); want != have {
		t.Errorf("want %q, have %q", want, have)
	}
}

func TestCallbackGauge(t *testing.T) {
	ch := make(chan time.Time)
	tick = func(time.Duration) <-chan time.Time { return ch }
	defer func() { tick = time.Tick }()

	buf := &bytes.Buffer{}
	value := 55.55
	cb := func() float64 { return value }
	NewCallbackGauge(buf, "test_statsd_callback_gauge", time.Second, time.Nanosecond, cb)

	ch <- time.Now()  // signal emitter
	runtime.Gosched() // yield to emitter
	ch <- time.Now()  // signal flush
	runtime.Gosched() // yield to flush

	// Travis is annoying
	check := func() bool { return buf.String() != "" }
	execute := func() { ch <- time.Now(); runtime.Gosched(); time.Sleep(5 * time.Millisecond) }
	by(t, time.Second, check, execute, "buffer never got write+flush")

	if want, have := fmt.Sprintf("test_statsd_callback_gauge:%f|g\n", value), buf.String(); want != have {
		t.Errorf("want %q, have %q", want, have)
	}
}

func TestHistogram(t *testing.T) {
	ch := make(chan time.Time)
	tick = func(time.Duration) <-chan time.Time { return ch }
	defer func() { tick = time.Tick }()

	buf := &bytes.Buffer{}
	h := NewHistogram(buf, "test_statsd_histogram", time.Second)

	h.Observe(123)

	runtime.Gosched()
	ch <- time.Now()
	runtime.Gosched()
	if want, have := "test_statsd_histogram:123|ms\n", buf.String(); want != have {
		t.Errorf("want %q, have %q", want, have)
	}
}

func by(t *testing.T, d time.Duration, check func() bool, execute func(), msg string) {
	deadline := time.Now().Add(d)
	for !check() {
		if time.Now().After(deadline) {
			t.Fatal(msg)
		}
		execute()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

func visit(path string, f os.FileInfo, err error) error {
	//fmt.Printf("Visited: %s\n", path)
	//tmp := strings.Split(path, ".")
	re := regexp.MustCompile(`^ex-\S*[^_][^t][^e][^s][^t].go$`)
	match := re.MatchString(path)
	if match {
		tmp := strings.Split(path, ".")
		fmt.Printf("1. %s\n", path)
		cmd := exec.Command("go", "build", "-o", "./"+tmp[len(tmp)-2], "./"+path)
		fmt.Println(cmd)
		out, err := cmd.Output()

		if err != nil {
			fmt.Println(err.Error())
			panic(err)
		}

		fmt.Print(string(out))

	}

	return nil
}

func main() {
	flag.Parse()
	root := flag.Arg(0)
	err := filepath.Walk(root, visit)
	fmt.Printf("filepath.Walk() returned %v\n", err)
}
<commit_msg>modify build.go<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

func visit(path string, f os.FileInfo, err error) error {
	//fmt.Printf("Visited: %s\n", path)
	//tmp := strings.Split(path, ".")
	re := regexp.MustCompile(`^ex-\S*[^_][^t][^e][^s][^t].go$`)
	match := re.MatchString(path)
	if match {
		tmp := strings.Split(path, ".")
		fmt.Printf("1. %s\n", path)
		cmd := exec.Command("go", "build", "-tags", "local", "-o", "./"+tmp[len(tmp)-2], "./"+path)
		fmt.Println(cmd)
		out, err := cmd.Output()

		if err != nil {
			fmt.Println(err.Error())
			panic(err)
		}

		fmt.Print(string(out))

	}

	return nil
}

func main() {
	flag.Parse()
	root := flag.Arg(0)
	err := filepath.Walk(root, visit)
	fmt.Printf("filepath.Walk() returned %v\n", err)
}
<|endoftext|>


<commit_before>//
// SpotClient
//
// @author darryl.west <darryl.west@raincitysoftware.com>
// @created 2017-04-15 08:40:52
//

package spotclient

import (
	"fmt"
	"net"
	"time"

	"github.com/darrylwest/spot-cache/spotcache"
)

// SpotClient - client struct
type SpotClient struct {
	CreateTime time.Time
	cfg        *Config
	Session    spotcache.SessionType
    builder    *spotcache.RequestBuilder
}

// NewSpotClient - create the client
func NewSpotClient(cfg *Config) *SpotClient {
	client := &SpotClient{}

	client.cfg = cfg
	client.CreateTime = time.Now()

	return client
}

func (client *SpotClient) getSession(conn net.Conn) spotcache.SessionType {
    buf := make([]byte, 32)
    n, err := conn.Read(buf)
    if err != nil {
        panic(err)
    }

    copy(client.Session[:], buf[:n])

    return client.Session
}

// SendPing - sends a basic ping to the server
func (client *SpotClient) SendPing(conn net.Conn, count int, interval time.Duration) error {
    buf := make([]byte, 128)
    fmt.Println("send a ping request")
    request := client.builder.CreatePingRequest()
    bytes, _ := request.ToBytes()

    if _, err := conn.Write(bytes); err != nil {
        fmt.Println("lost connection...");
        return err
    }

    fmt.Printf("request: %v\n", request)

    n, err := conn.Read(buf)
    if err != nil {
        fmt.Println("lost connection..");
        return err
    }

    for i := 0; i < count; i++ {
        if i > 0 {
            time.Sleep(interval);
        }
        resp, _ := spotcache.ResponseFromBytes(buf[:n])
        fmt.Printf("ping response #%d: ID: %s SS: %s data:%s\n", (i + 1), resp.ID, resp.Session, string(resp.Data))
    }

    return nil
}

// Exec - run the command
func (client SpotClient) Exec() error {
    var err error

    fmt.Printf("exec %v\n", client.cfg.Args)

    // implement ping first...
    conn, _ := client.Connect()
    defer conn.Close();

    sess := client.getSession(conn)
    fmt.Printf("session: %s\n", sess);

    client.builder = spotcache.NewRequestBuilder(sess)

    // now send a ping
    client.SendPing(conn, 1e6, time.Second * 10)

    return err
}

// Connect - return the connection
func (client *SpotClient) Connect() (net.Conn, error) {
	host := fmt.Sprintf("%s:%d", client.cfg.Host, client.cfg.Port)

	fmt.Printf("dialing %s\n", host);
	conn, err := net.Dial("tcp", host)
    if err != nil {
        panic(err)
    }

	return conn, err
}

// Send a command request
func (client *SpotClient) Send(request *spotcache.Request) error {
	return nil
}
<commit_msg>cleaned up ping output<commit_after>//
// SpotClient
//
// @author darryl.west <darryl.west@raincitysoftware.com>
// @created 2017-04-15 08:40:52
//

package spotclient

import (
	"fmt"
	"net"
	"time"

	"github.com/darrylwest/spot-cache/spotcache"
)

// SpotClient - client struct
type SpotClient struct {
	CreateTime time.Time
	cfg        *Config
	Session    spotcache.SessionType
    builder    *spotcache.RequestBuilder
}

// NewSpotClient - create the client
func NewSpotClient(cfg *Config) *SpotClient {
	client := &SpotClient{}

	client.cfg = cfg
	client.CreateTime = time.Now()

	return client
}

func (client *SpotClient) getSession(conn net.Conn) spotcache.SessionType {
    buf := make([]byte, 32)
    n, err := conn.Read(buf)
    if err != nil {
        panic(err)
    }

    copy(client.Session[:], buf[:n])

    return client.Session
}

// SendPing - sends a basic ping to the server
func (client *SpotClient) SendPing(conn net.Conn, count int, interval time.Duration) error {
    fmt.Printf("send a ping %d requests...\n", count)

    buf := make([]byte, 128)
    for i := 0; i < count; i++ {
        if i > 0 {
            time.Sleep(interval);
        }

        request := client.builder.CreatePingRequest()
        bytes, _ := request.ToBytes()

        if _, err := conn.Write(bytes); err != nil {
            fmt.Println("lost connection...");
            return err
        }

        fmt.Printf("ping request  #%d: %v\n", (i+1), request)

        n, err := conn.Read(buf)
        if err != nil {
            fmt.Println("lost connection..");
            return err
        }

        resp, _ := spotcache.ResponseFromBytes(buf[:n])
        fmt.Printf("ping response #%d: ID:%s SS: %s data:%s\n", (i + 1), resp.ID, resp.Session, string(resp.Data))
    }

    return nil
}

// Exec - run the command
func (client SpotClient) Exec() error {
    var err error

    fmt.Printf("exec %v\n", client.cfg.Args)

    // implement ping first...
    conn, _ := client.Connect()
    defer conn.Close();

    sess := client.getSession(conn)
    fmt.Printf("session: %s\n", sess);

    client.builder = spotcache.NewRequestBuilder(sess)

    // now send a ping
    client.SendPing(conn, 1e6, time.Second * 10)

    return err
}

// Connect - return the connection
func (client *SpotClient) Connect() (net.Conn, error) {
	host := fmt.Sprintf("%s:%d", client.cfg.Host, client.cfg.Port)

	fmt.Printf("dialing %s\n", host);
	conn, err := net.Dial("tcp", host)
    if err != nil {
        panic(err)
    }

	return conn, err
}

// Send a command request
func (client *SpotClient) Send(request *spotcache.Request) error {
	return nil
}
<|endoftext|>


<commit_before>package slack

import (
	"context"
	"fmt"
	"io"
	"net/url"
	"strconv"
	"strings"
)

const (
	// Add here the defaults in the siten
	DEFAULT_FILES_USER    = ""
	DEFAULT_FILES_CHANNEL = ""
	DEFAULT_FILES_TS_FROM = 0
	DEFAULT_FILES_TS_TO   = -1
	DEFAULT_FILES_TYPES   = "all"
	DEFAULT_FILES_COUNT   = 100
	DEFAULT_FILES_PAGE    = 1
)

// File contains all the information for a file
type File struct {
	ID        string   `json:"id"`
	Created   JSONTime `json:"created"`
	Timestamp JSONTime `json:"timestamp"`

	Name              string `json:"name"`
	Title             string `json:"title"`
	Mimetype          string `json:"mimetype"`
	ImageExifRotation int    `json:"image_exif_rotation"`
	Filetype          string `json:"filetype"`
	PrettyType        string `json:"pretty_type"`
	User              string `json:"user"`

	Mode         string `json:"mode"`
	Editable     bool   `json:"editable"`
	IsExternal   bool   `json:"is_external"`
	ExternalType string `json:"external_type"`

	Size int `json:"size"`

	URL                string `json:"url"`          // Deprecated - never set
	URLDownload        string `json:"url_download"` // Deprecated - never set
	URLPrivate         string `json:"url_private"`
	URLPrivateDownload string `json:"url_private_download"`

	OriginalH   int    `json:"original_h"`
	OriginalW   int    `json:"original_w"`
	Thumb64     string `json:"thumb_64"`
	Thumb80     string `json:"thumb_80"`
	Thumb160    string `json:"thumb_160"`
	Thumb360    string `json:"thumb_360"`
	Thumb360Gif string `json:"thumb_360_gif"`
	Thumb360W   int    `json:"thumb_360_w"`
	Thumb360H   int    `json:"thumb_360_h"`
	Thumb480    string `json:"thumb_480"`
	Thumb480W   int    `json:"thumb_480_w"`
	Thumb480H   int    `json:"thumb_480_h"`
	Thumb720    string `json:"thumb_720"`
	Thumb720W   int    `json:"thumb_720_w"`
	Thumb720H   int    `json:"thumb_720_h"`
	Thumb960    string `json:"thumb_960"`
	Thumb960W   int    `json:"thumb_960_w"`
	Thumb960H   int    `json:"thumb_960_h"`
	Thumb1024   string `json:"thumb_1024"`
	Thumb1024W  int    `json:"thumb_1024_w"`
	Thumb1024H  int    `json:"thumb_1024_h"`

	Permalink       string `json:"permalink"`
	PermalinkPublic string `json:"permalink_public"`

	EditLink         string `json:"edit_link"`
	Preview          string `json:"preview"`
	PreviewHighlight string `json:"preview_highlight"`
	Lines            int    `json:"lines"`
	LinesMore        int    `json:"lines_more"`

	IsPublic        bool     `json:"is_public"`
	PublicURLShared bool     `json:"public_url_shared"`
	Channels        []string `json:"channels"`
	Groups          []string `json:"groups"`
	IMs             []string `json:"ims"`
	InitialComment  Comment  `json:"initial_comment"`
	CommentsCount   int      `json:"comments_count"`
	NumStars        int      `json:"num_stars"`
	IsStarred       bool     `json:"is_starred"`
	Shares          Share    `json:"shares"`
}

type Share struct {
	Public  map[string][]ShareFileInfo `json:"public"`
	Private map[string][]ShareFileInfo `json:"private"`
}

type ShareFileInfo struct {
	ReplyUsers      []string `json:"reply_users"`
	ReplyUsersCount int      `json:"reply_users_count"`
	ReplyCount      int      `json:"reply_count"`
	Ts              string   `json:"ts"`
	ThreadTs        string   `json:"thread_ts"`
	LatestReply     string   `json:"latest_reply"`
	ChannelName     string   `json:"channel_name"`
	TeamID          string   `json:"team_id"`
}

// FileUploadParameters contains all the parameters necessary (including the optional ones) for an UploadFile() request.
//
// There are three ways to upload a file. You can either set Content if file is small, set Reader if file is large,
// or provide a local file path in File to upload it from your filesystem.
//
// Note that when using the Reader option, you *must* specify the Filename, otherwise the Slack API isn't happy.
type FileUploadParameters struct {
	File            string
	Content         string
	Reader          io.Reader
	Filetype        string
	Filename        string
	Title           string
	InitialComment  string
	Channels        []string
	ThreadTimestamp string
}

// GetFilesParameters contains all the parameters necessary (including the optional ones) for a GetFiles() request
type GetFilesParameters struct {
	User          string
	Channel       string
	TimestampFrom JSONTime
	TimestampTo   JSONTime
	Types         string
	Count         int
	Page          int
}

// ListFilesParameters contains all the parameters necessary (including the optional ones) for a ListFiles() request
type ListFilesParameters struct {
	Limit   int
	User    string
	Channel string
	Types   string
	Cursor  string
}

type fileResponseFull struct {
	File     `json:"file"`
	Paging   `json:"paging"`
	Comments []Comment        `json:"comments"`
	Files    []File           `json:"files"`
	Metadata ResponseMetadata `json:"response_metadata"`

	SlackResponse
}

// NewGetFilesParameters provides an instance of GetFilesParameters with all the sane default values set
func NewGetFilesParameters() GetFilesParameters {
	return GetFilesParameters{
		User:          DEFAULT_FILES_USER,
		Channel:       DEFAULT_FILES_CHANNEL,
		TimestampFrom: DEFAULT_FILES_TS_FROM,
		TimestampTo:   DEFAULT_FILES_TS_TO,
		Types:         DEFAULT_FILES_TYPES,
		Count:         DEFAULT_FILES_COUNT,
		Page:          DEFAULT_FILES_PAGE,
	}
}

func (api *Client) fileRequest(ctx context.Context, path string, values url.Values) (*fileResponseFull, error) {
	response := &fileResponseFull{}
	err := api.postMethod(ctx, path, values, response)
	if err != nil {
		return nil, err
	}

	return response, response.Err()
}

// GetFileInfo retrieves a file and related comments
func (api *Client) GetFileInfo(fileID string, count, page int) (*File, []Comment, *Paging, error) {
	return api.GetFileInfoContext(context.Background(), fileID, count, page)
}

// GetFileInfoContext retrieves a file and related comments with a custom context
func (api *Client) GetFileInfoContext(ctx context.Context, fileID string, count, page int) (*File, []Comment, *Paging, error) {
	values := url.Values{
		"token": {api.token},
		"file":  {fileID},
		"count": {strconv.Itoa(count)},
		"page":  {strconv.Itoa(page)},
	}

	response, err := api.fileRequest(ctx, "files.info", values)
	if err != nil {
		return nil, nil, nil, err
	}
	return &response.File, response.Comments, &response.Paging, nil
}

// GetFile retreives a given file from its private download URL
func (api *Client) GetFile(downloadURL string, writer io.Writer) error {
	return downloadFile(api.httpclient, api.token, downloadURL, writer, api)
}

// GetFiles retrieves all files according to the parameters given
func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error) {
	return api.GetFilesContext(context.Background(), params)
}

// ListFiles retrieves all files according to the parameters given. Uses cursor based pagination.
func (api *Client) ListFiles(params ListFilesParameters) ([]File, *ListFilesParameters, error) {
	return api.ListFilesContext(context.Background(), params)
}

// ListFilesContext retrieves all files according to the parameters given with a custom context. Uses cursor based pagination.
func (api *Client) ListFilesContext(ctx context.Context, params ListFilesParameters) ([]File, *ListFilesParameters, error) {
	values := url.Values{
		"token": {api.token},
	}

	if params.User != DEFAULT_FILES_USER {
		values.Add("user", params.User)
	}
	if params.Channel != DEFAULT_FILES_CHANNEL {
		values.Add("channel", params.Channel)
	}
	if params.Limit != DEFAULT_FILES_COUNT {
		values.Add("limit", strconv.Itoa(params.Limit))
	}
	if params.Cursor != "" {
		values.Add("cursor", params.Cursor)
	}

	response, err := api.fileRequest(ctx, "files.list", values)
	if err != nil {
		return nil, nil, err
	}

	params.Cursor = response.Metadata.Cursor

	return response.Files, &params, nil
}

// GetFilesContext retrieves all files according to the parameters given with a custom context
func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameters) ([]File, *Paging, error) {
	values := url.Values{
		"token": {api.token},
	}
	if params.User != DEFAULT_FILES_USER {
		values.Add("user", params.User)
	}
	if params.Channel != DEFAULT_FILES_CHANNEL {
		values.Add("channel", params.Channel)
	}
	if params.TimestampFrom != DEFAULT_FILES_TS_FROM {
		values.Add("ts_from", strconv.FormatInt(int64(params.TimestampFrom), 10))
	}
	if params.TimestampTo != DEFAULT_FILES_TS_TO {
		values.Add("ts_to", strconv.FormatInt(int64(params.TimestampTo), 10))
	}
	if params.Types != DEFAULT_FILES_TYPES {
		values.Add("types", params.Types)
	}
	if params.Count != DEFAULT_FILES_COUNT {
		values.Add("count", strconv.Itoa(params.Count))
	}
	if params.Page != DEFAULT_FILES_PAGE {
		values.Add("page", strconv.Itoa(params.Page))
	}

	response, err := api.fileRequest(ctx, "files.list", values)
	if err != nil {
		return nil, nil, err
	}
	return response.Files, &response.Paging, nil
}

// UploadFile uploads a file
func (api *Client) UploadFile(params FileUploadParameters) (file *File, err error) {
	return api.UploadFileContext(context.Background(), params)
}

// UploadFileContext uploads a file and setting a custom context
func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParameters) (file *File, err error) {
	// Test if user token is valid. This helps because client.Do doesn't like this for some reason. XXX: More
	// investigation needed, but for now this will do.
	_, err = api.AuthTest()
	if err != nil {
		return nil, err
	}
	if params.Filename == "" {
		return nil, fmt.Errorf("files.upload: FileUploadParameters.Filename is mandatory")
	}
	response := &fileResponseFull{}
	values := url.Values{
		"token": {api.token},
	}
	if params.Filetype != "" {
		values.Add("filetype", params.Filetype)
	}
	if params.Filename != "" {
		values.Add("filename", params.Filename)
	}
	if params.Title != "" {
		values.Add("title", params.Title)
	}
	if params.InitialComment != "" {
		values.Add("initial_comment", params.InitialComment)
	}
	if params.ThreadTimestamp != "" {
		values.Add("thread_ts", params.ThreadTimestamp)
	}
	if len(params.Channels) != 0 {
		values.Add("channels", strings.Join(params.Channels, ","))
	}
	if params.Content != "" {
		values.Add("content", params.Content)
		err = api.postMethod(ctx, "files.upload", values, response)
	} else if params.File != "" {
		err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.File, "file", values, response, api)
	} else if params.Reader != nil {
		err = postWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.Filename, "file", values, params.Reader, response, api)
	}
	if err != nil {
		return nil, err
	}

	return &response.File, response.Err()
}

// DeleteFileComment deletes a file's comment
func (api *Client) DeleteFileComment(commentID, fileID string) error {
	return api.DeleteFileCommentContext(context.Background(), fileID, commentID)
}

// DeleteFileCommentContext deletes a file's comment with a custom context
func (api *Client) DeleteFileCommentContext(ctx context.Context, fileID, commentID string) (err error) {
	if fileID == "" || commentID == "" {
		return ErrParametersMissing
	}

	values := url.Values{
		"token": {api.token},
		"file":  {fileID},
		"id":    {commentID},
	}
	_, err = api.fileRequest(ctx, "files.comments.delete", values)
	return err
}

// DeleteFile deletes a file
func (api *Client) DeleteFile(fileID string) error {
	return api.DeleteFileContext(context.Background(), fileID)
}

// DeleteFileContext deletes a file with a custom context
func (api *Client) DeleteFileContext(ctx context.Context, fileID string) (err error) {
	values := url.Values{
		"token": {api.token},
		"file":  {fileID},
	}

	_, err = api.fileRequest(ctx, "files.delete", values)
	return err
}

// RevokeFilePublicURL disables public/external sharing for a file
func (api *Client) RevokeFilePublicURL(fileID string) (*File, error) {
	return api.RevokeFilePublicURLContext(context.Background(), fileID)
}

// RevokeFilePublicURLContext disables public/external sharing for a file with a custom context
func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string) (*File, error) {
	values := url.Values{
		"token": {api.token},
		"file":  {fileID},
	}

	response, err := api.fileRequest(ctx, "files.revokePublicURL", values)
	if err != nil {
		return nil, err
	}
	return &response.File, nil
}

// ShareFilePublicURL enabled public/external sharing for a file
func (api *Client) ShareFilePublicURL(fileID string) (*File, []Comment, *Paging, error) {
	return api.ShareFilePublicURLContext(context.Background(), fileID)
}

// ShareFilePublicURLContext enabled public/external sharing for a file with a custom context
func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) (*File, []Comment, *Paging, error) {
	values := url.Values{
		"token": {api.token},
		"file":  {fileID},
	}

	response, err := api.fileRequest(ctx, "files.sharedPublicURL", values)
	if err != nil {
		return nil, nil, nil, err
	}
	return &response.File, response.Comments, &response.Paging, nil
}
<commit_msg>Only make filename mandatory if Reader is primary method of specifying files<commit_after>package slack

import (
	"context"
	"fmt"
	"io"
	"net/url"
	"strconv"
	"strings"
)

const (
	// Add here the defaults in the siten
	DEFAULT_FILES_USER    = ""
	DEFAULT_FILES_CHANNEL = ""
	DEFAULT_FILES_TS_FROM = 0
	DEFAULT_FILES_TS_TO   = -1
	DEFAULT_FILES_TYPES   = "all"
	DEFAULT_FILES_COUNT   = 100
	DEFAULT_FILES_PAGE    = 1
)

// File contains all the information for a file
type File struct {
	ID        string   `json:"id"`
	Created   JSONTime `json:"created"`
	Timestamp JSONTime `json:"timestamp"`

	Name              string `json:"name"`
	Title             string `json:"title"`
	Mimetype          string `json:"mimetype"`
	ImageExifRotation int    `json:"image_exif_rotation"`
	Filetype          string `json:"filetype"`
	PrettyType        string `json:"pretty_type"`
	User              string `json:"user"`

	Mode         string `json:"mode"`
	Editable     bool   `json:"editable"`
	IsExternal   bool   `json:"is_external"`
	ExternalType string `json:"external_type"`

	Size int `json:"size"`

	URL                string `json:"url"`          // Deprecated - never set
	URLDownload        string `json:"url_download"` // Deprecated - never set
	URLPrivate         string `json:"url_private"`
	URLPrivateDownload string `json:"url_private_download"`

	OriginalH   int    `json:"original_h"`
	OriginalW   int    `json:"original_w"`
	Thumb64     string `json:"thumb_64"`
	Thumb80     string `json:"thumb_80"`
	Thumb160    string `json:"thumb_160"`
	Thumb360    string `json:"thumb_360"`
	Thumb360Gif string `json:"thumb_360_gif"`
	Thumb360W   int    `json:"thumb_360_w"`
	Thumb360H   int    `json:"thumb_360_h"`
	Thumb480    string `json:"thumb_480"`
	Thumb480W   int    `json:"thumb_480_w"`
	Thumb480H   int    `json:"thumb_480_h"`
	Thumb720    string `json:"thumb_720"`
	Thumb720W   int    `json:"thumb_720_w"`
	Thumb720H   int    `json:"thumb_720_h"`
	Thumb960    string `json:"thumb_960"`
	Thumb960W   int    `json:"thumb_960_w"`
	Thumb960H   int    `json:"thumb_960_h"`
	Thumb1024   string `json:"thumb_1024"`
	Thumb1024W  int    `json:"thumb_1024_w"`
	Thumb1024H  int    `json:"thumb_1024_h"`

	Permalink       string `json:"permalink"`
	PermalinkPublic string `json:"permalink_public"`

	EditLink         string `json:"edit_link"`
	Preview          string `json:"preview"`
	PreviewHighlight string `json:"preview_highlight"`
	Lines            int    `json:"lines"`
	LinesMore        int    `json:"lines_more"`

	IsPublic        bool     `json:"is_public"`
	PublicURLShared bool     `json:"public_url_shared"`
	Channels        []string `json:"channels"`
	Groups          []string `json:"groups"`
	IMs             []string `json:"ims"`
	InitialComment  Comment  `json:"initial_comment"`
	CommentsCount   int      `json:"comments_count"`
	NumStars        int      `json:"num_stars"`
	IsStarred       bool     `json:"is_starred"`
	Shares          Share    `json:"shares"`
}

type Share struct {
	Public  map[string][]ShareFileInfo `json:"public"`
	Private map[string][]ShareFileInfo `json:"private"`
}

type ShareFileInfo struct {
	ReplyUsers      []string `json:"reply_users"`
	ReplyUsersCount int      `json:"reply_users_count"`
	ReplyCount      int      `json:"reply_count"`
	Ts              string   `json:"ts"`
	ThreadTs        string   `json:"thread_ts"`
	LatestReply     string   `json:"latest_reply"`
	ChannelName     string   `json:"channel_name"`
	TeamID          string   `json:"team_id"`
}

// FileUploadParameters contains all the parameters necessary (including the optional ones) for an UploadFile() request.
//
// There are three ways to upload a file. You can either set Content if file is small, set Reader if file is large,
// or provide a local file path in File to upload it from your filesystem.
//
// Note that when using the Reader option, you *must* specify the Filename, otherwise the Slack API isn't happy.
type FileUploadParameters struct {
	File            string
	Content         string
	Reader          io.Reader
	Filetype        string
	Filename        string
	Title           string
	InitialComment  string
	Channels        []string
	ThreadTimestamp string
}

// GetFilesParameters contains all the parameters necessary (including the optional ones) for a GetFiles() request
type GetFilesParameters struct {
	User          string
	Channel       string
	TimestampFrom JSONTime
	TimestampTo   JSONTime
	Types         string
	Count         int
	Page          int
}

// ListFilesParameters contains all the parameters necessary (including the optional ones) for a ListFiles() request
type ListFilesParameters struct {
	Limit   int
	User    string
	Channel string
	Types   string
	Cursor  string
}

type fileResponseFull struct {
	File     `json:"file"`
	Paging   `json:"paging"`
	Comments []Comment        `json:"comments"`
	Files    []File           `json:"files"`
	Metadata ResponseMetadata `json:"response_metadata"`

	SlackResponse
}

// NewGetFilesParameters provides an instance of GetFilesParameters with all the sane default values set
func NewGetFilesParameters() GetFilesParameters {
	return GetFilesParameters{
		User:          DEFAULT_FILES_USER,
		Channel:       DEFAULT_FILES_CHANNEL,
		TimestampFrom: DEFAULT_FILES_TS_FROM,
		TimestampTo:   DEFAULT_FILES_TS_TO,
		Types:         DEFAULT_FILES_TYPES,
		Count:         DEFAULT_FILES_COUNT,
		Page:          DEFAULT_FILES_PAGE,
	}
}

func (api *Client) fileRequest(ctx context.Context, path string, values url.Values) (*fileResponseFull, error) {
	response := &fileResponseFull{}
	err := api.postMethod(ctx, path, values, response)
	if err != nil {
		return nil, err
	}

	return response, response.Err()
}

// GetFileInfo retrieves a file and related comments
func (api *Client) GetFileInfo(fileID string, count, page int) (*File, []Comment, *Paging, error) {
	return api.GetFileInfoContext(context.Background(), fileID, count, page)
}

// GetFileInfoContext retrieves a file and related comments with a custom context
func (api *Client) GetFileInfoContext(ctx context.Context, fileID string, count, page int) (*File, []Comment, *Paging, error) {
	values := url.Values{
		"token": {api.token},
		"file":  {fileID},
		"count": {strconv.Itoa(count)},
		"page":  {strconv.Itoa(page)},
	}

	response, err := api.fileRequest(ctx, "files.info", values)
	if err != nil {
		return nil, nil, nil, err
	}
	return &response.File, response.Comments, &response.Paging, nil
}

// GetFile retreives a given file from its private download URL
func (api *Client) GetFile(downloadURL string, writer io.Writer) error {
	return downloadFile(api.httpclient, api.token, downloadURL, writer, api)
}

// GetFiles retrieves all files according to the parameters given
func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error) {
	return api.GetFilesContext(context.Background(), params)
}

// ListFiles retrieves all files according to the parameters given. Uses cursor based pagination.
func (api *Client) ListFiles(params ListFilesParameters) ([]File, *ListFilesParameters, error) {
	return api.ListFilesContext(context.Background(), params)
}

// ListFilesContext retrieves all files according to the parameters given with a custom context. Uses cursor based pagination.
func (api *Client) ListFilesContext(ctx context.Context, params ListFilesParameters) ([]File, *ListFilesParameters, error) {
	values := url.Values{
		"token": {api.token},
	}

	if params.User != DEFAULT_FILES_USER {
		values.Add("user", params.User)
	}
	if params.Channel != DEFAULT_FILES_CHANNEL {
		values.Add("channel", params.Channel)
	}
	if params.Limit != DEFAULT_FILES_COUNT {
		values.Add("limit", strconv.Itoa(params.Limit))
	}
	if params.Cursor != "" {
		values.Add("cursor", params.Cursor)
	}

	response, err := api.fileRequest(ctx, "files.list", values)
	if err != nil {
		return nil, nil, err
	}

	params.Cursor = response.Metadata.Cursor

	return response.Files, &params, nil
}

// GetFilesContext retrieves all files according to the parameters given with a custom context
func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameters) ([]File, *Paging, error) {
	values := url.Values{
		"token": {api.token},
	}
	if params.User != DEFAULT_FILES_USER {
		values.Add("user", params.User)
	}
	if params.Channel != DEFAULT_FILES_CHANNEL {
		values.Add("channel", params.Channel)
	}
	if params.TimestampFrom != DEFAULT_FILES_TS_FROM {
		values.Add("ts_from", strconv.FormatInt(int64(params.TimestampFrom), 10))
	}
	if params.TimestampTo != DEFAULT_FILES_TS_TO {
		values.Add("ts_to", strconv.FormatInt(int64(params.TimestampTo), 10))
	}
	if params.Types != DEFAULT_FILES_TYPES {
		values.Add("types", params.Types)
	}
	if params.Count != DEFAULT_FILES_COUNT {
		values.Add("count", strconv.Itoa(params.Count))
	}
	if params.Page != DEFAULT_FILES_PAGE {
		values.Add("page", strconv.Itoa(params.Page))
	}

	response, err := api.fileRequest(ctx, "files.list", values)
	if err != nil {
		return nil, nil, err
	}
	return response.Files, &response.Paging, nil
}

// UploadFile uploads a file
func (api *Client) UploadFile(params FileUploadParameters) (file *File, err error) {
	return api.UploadFileContext(context.Background(), params)
}

// UploadFileContext uploads a file and setting a custom context
func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParameters) (file *File, err error) {
	// Test if user token is valid. This helps because client.Do doesn't like this for some reason. XXX: More
	// investigation needed, but for now this will do.
	_, err = api.AuthTest()
	if err != nil {
		return nil, err
	}
	response := &fileResponseFull{}
	values := url.Values{
		"token": {api.token},
	}
	if params.Filetype != "" {
		values.Add("filetype", params.Filetype)
	}
	if params.Filename != "" {
		values.Add("filename", params.Filename)
	}
	if params.Title != "" {
		values.Add("title", params.Title)
	}
	if params.InitialComment != "" {
		values.Add("initial_comment", params.InitialComment)
	}
	if params.ThreadTimestamp != "" {
		values.Add("thread_ts", params.ThreadTimestamp)
	}
	if len(params.Channels) != 0 {
		values.Add("channels", strings.Join(params.Channels, ","))
	}
	if params.Content != "" {
		values.Add("content", params.Content)
		err = api.postMethod(ctx, "files.upload", values, response)
	} else if params.File != "" {
		err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.File, "file", values, response, api)
	} else if params.Reader != nil {
		if params.Filename == "" {
			return nil, fmt.Errorf("files.upload: FileUploadParameters.Filename is mandatory when using FileUploadParameters.Reader")
		}
		err = postWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.Filename, "file", values, params.Reader, response, api)
	}
	if err != nil {
		return nil, err
	}

	return &response.File, response.Err()
}

// DeleteFileComment deletes a file's comment
func (api *Client) DeleteFileComment(commentID, fileID string) error {
	return api.DeleteFileCommentContext(context.Background(), fileID, commentID)
}

// DeleteFileCommentContext deletes a file's comment with a custom context
func (api *Client) DeleteFileCommentContext(ctx context.Context, fileID, commentID string) (err error) {
	if fileID == "" || commentID == "" {
		return ErrParametersMissing
	}

	values := url.Values{
		"token": {api.token},
		"file":  {fileID},
		"id":    {commentID},
	}
	_, err = api.fileRequest(ctx, "files.comments.delete", values)
	return err
}

// DeleteFile deletes a file
func (api *Client) DeleteFile(fileID string) error {
	return api.DeleteFileContext(context.Background(), fileID)
}

// DeleteFileContext deletes a file with a custom context
func (api *Client) DeleteFileContext(ctx context.Context, fileID string) (err error) {
	values := url.Values{
		"token": {api.token},
		"file":  {fileID},
	}

	_, err = api.fileRequest(ctx, "files.delete", values)
	return err
}

// RevokeFilePublicURL disables public/external sharing for a file
func (api *Client) RevokeFilePublicURL(fileID string) (*File, error) {
	return api.RevokeFilePublicURLContext(context.Background(), fileID)
}

// RevokeFilePublicURLContext disables public/external sharing for a file with a custom context
func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string) (*File, error) {
	values := url.Values{
		"token": {api.token},
		"file":  {fileID},
	}

	response, err := api.fileRequest(ctx, "files.revokePublicURL", values)
	if err != nil {
		return nil, err
	}
	return &response.File, nil
}

// ShareFilePublicURL enabled public/external sharing for a file
func (api *Client) ShareFilePublicURL(fileID string) (*File, []Comment, *Paging, error) {
	return api.ShareFilePublicURLContext(context.Background(), fileID)
}

// ShareFilePublicURLContext enabled public/external sharing for a file with a custom context
func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) (*File, []Comment, *Paging, error) {
	values := url.Values{
		"token": {api.token},
		"file":  {fileID},
	}

	response, err := api.fileRequest(ctx, "files.sharedPublicURL", values)
	if err != nil {
		return nil, nil, nil, err
	}
	return &response.File, response.Comments, &response.Paging, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2017-2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
	"html/template"
	"log"
	"regexp"

	"github.com/decred/dcrd/dcrjson"
	"github.com/decred/dcrd/rpcclient"
)

// Agenda contains all of the data representing an agenda for the html
// template programming.
type Agenda struct {
	ID              string
	Status          string
	Description     string
	Mask            uint16
	VoteVersion     uint32
	QuorumThreshold uint32
	StartHeight     int64
	EndHeight       int64
	VoteChoices     map[string]VoteChoice
	VoteCounts      map[string]uint32
}

// VoteChoice contains the details of a vote choice from an agenda,
// Each agenda will have 3 choices - yes/no/maybe
type VoteChoice struct {
	ID          string
	Description string
	Bits        uint16
}

var dcpRE = regexp.MustCompile(`(?i)DCP\-?(\d{4})`)

// Agenda status may be: started, defined, lockedin, failed, active

// IsActive indicates if the agenda is active
func (a *Agenda) IsActive() bool {
	return a.Status == "active"
}

// IsStarted indicates if the agenda is started
func (a *Agenda) IsStarted() bool {
	return a.Status == "started"
}

// IsDefined indicates if the agenda is defined
func (a *Agenda) IsDefined() bool {
	return a.Status == "defined"
}

// IsLockedIn indicates if the agenda is lockedin
func (a *Agenda) IsLockedIn() bool {
	return a.Status == "lockedin"
}

// IsFailed indicates if the agenda is failed
func (a *Agenda) IsFailed() bool {
	return a.Status == "failed"
}

// BlockLockedIn returns the height of the first block of this agenda's lock-in period. -1 if this agenda has not been locked-in.
func (a *Agenda) BlockLockedIn() int64 {
	if a.IsLockedIn() || a.IsActive() {
		return a.EndHeight + 1
	}
	return -1
}

// BlockActivated returns the height of the first block with this agenda active. -1 if this agenda has not been activated.
func (a *Agenda) BlockActivated() int64 {
	if a.IsActive() {
		return a.BlockLockedIn() + int64(activeNetParams.RuleChangeActivationInterval)
	}
	return -1
}

// TotalVotes returns the total number of No, Yes and Abstain votes cast against this agenda
func (a *Agenda) TotalVotes() uint32 {
	return a.VoteCounts["yes"] + a.VoteCounts["no"] + a.VoteCounts["abstain"]
}

// VotePercent returns the the number of yes/no/abstains votes, as a percentage of
// all votes cast against this agenda
func (a *Agenda) VotePercent(voteID string) float64 {
	percent := float64(a.VoteCounts[voteID]) / float64(a.TotalVotes())
	return toFixed(percent*100, 2)
}

// VoteCountPercentage returns the number of votes cast against this agenda as
// a percentage of the theoretical maximum number of possible votes
func (a *Agenda) VoteCountPercentage() float64 {
	maxPossibleVotes := float64(activeNetParams.RuleChangeActivationInterval) * float64(activeNetParams.TicketsPerBlock)
	voteCountPercentage := float64(a.TotalVotes()) / maxPossibleVotes
	return toFixed(voteCountPercentage*100, 1)
}

// QuorumVotePercentage returns the total number of Yes and No votes as a
// percentage of the quorum threshold.
func (a *Agenda) QuorumVotePercentage() float64 {
	quorumVotePercentage := float64(a.VoteCounts["yes"]+a.VoteCounts["no"]) / float64(a.QuorumThreshold)
	return toFixed(quorumVotePercentage*100, 1)
}

// DescriptionWithDCPURL writes a new description with an link to any DCP that
// is detected in the text.  It is written to a template.HTML type so the link
// is not escaped when the template is executed.
func (a *Agenda) DescriptionWithDCPURL() template.HTML {
	subst := `<a href="https://github.com/decred/dcps/blob/master/dcp-${1}/dcp-${1}.mediawiki" target="_blank" rel="noopener noreferrer">${0}</a>`
	return template.HTML(dcpRE.ReplaceAllString(a.Description, subst))
}

// CountVotes uses the dcrd client to find all yes/no/abstain votes
// cast against this agenda. It will count the votes and store the
// totals inside the Agenda
func (a *Agenda) countVotes(dcrdClient *rpcclient.Client, votingStartHeight int64, votingEndHeight int64) error {
	// Find the last block hash of this voting period
	// Required to call GetStakeVersions
	lastBlockHash, err := dcrdClient.GetBlockHash(votingEndHeight)
	if err != nil {
		return err
	}

	// Retrieve all votes for this voting period
	stakeVersions, err := dcrdClient.GetStakeVersions(lastBlockHash.String(), int32(votingEndHeight-votingStartHeight))
	if err != nil {
		return err
	}

	// Collect all votes of the correct version
	var votes []dcrjson.VersionBits
	for _, sVer := range stakeVersions.StakeVersions {
		for _, vote := range sVer.Votes {
			if vote.Version == a.VoteVersion {
				votes = append(votes, vote)
			}
		}
	}

	// Count the votes and store the total
	for vID := range a.VoteChoices {
		var matchingVotes uint32
		for _, vote := range votes {
			if vote.Bits&a.Mask == a.VoteChoices[vID].Bits {
				matchingVotes++
			}
		}
		a.VoteCounts[vID] = matchingVotes
		log.Printf("\t%s: %d", vID, matchingVotes)
	}

	return nil
}

// agendasFromJSON parses the response from GetVoteInfo, and
// uses the data to create a set of Agenda objects
func agendasFromJSON(getVoteInfo dcrjson.GetVoteInfoResult) []Agenda {
	var parsedAgendas []Agenda
	for _, a := range getVoteInfo.Agendas {
		voteChoices := make(map[string]VoteChoice)
		for _, choice := range a.Choices {
			vote := VoteChoice{
				ID:          choice.ID,
				Description: choice.Description,
				Bits:        choice.Bits,
			}
			voteChoices[vote.ID] = vote
		}
		parsedAgendas = append(parsedAgendas, Agenda{
			ID:              a.ID,
			Status:          a.Status,
			Description:     a.Description,
			Mask:            a.Mask,
			VoteVersion:     getVoteInfo.VoteVersion,
			QuorumThreshold: getVoteInfo.Quorum,
			VoteChoices:     voteChoices,
			VoteCounts:      make(map[string]uint32),
		})
	}
	return parsedAgendas
}

func agendasForVersions(dcrdClient *rpcclient.Client, versions []uint32, currentHeight int64, svis StakeVersionIntervals) ([]Agenda, error) {
	var allAgendas []Agenda
	for _, version := range versions {
		// Retrieve Agendas for this voting period
		getVoteInfo, err := dcrdClient.GetVoteInfo(version)
		if err != nil {
			return nil, err
		}
		agendas := agendasFromJSON(*getVoteInfo)

		// Check if upgrade to this version has occurred yet
		upgradeOccurred, upgradeHeight := svis.GetStakeVersionUpgradeHeight(version)

		if !upgradeOccurred {
			// Haven't upgraded to this stake version yet. Therefore
			// we dont know when the voting start/end heights will be.
			// Nothing more to do with these agendas
			log.Printf("Upgrade to stake version %d has not happened", version)
			allAgendas = append(allAgendas, agendas...)
			break
		}

		log.Printf("Upgrade to version %d happened at height %d", version, upgradeHeight)

		// Find the start of the next RCI after the threshold was met
		nextRCIStartHeight := activeNetParams.StakeValidationHeight
		for nextRCIStartHeight < upgradeHeight {
			nextRCIStartHeight += int64(activeNetParams.RuleChangeActivationInterval)
		}

		// Next RCI height tells us the voting start/end heights, and we can add these to the agendas
		votingStartHeight := nextRCIStartHeight
		votingEndHeight := nextRCIStartHeight + int64(activeNetParams.RuleChangeActivationInterval) - 1
		for _, agenda := range agendas {
			agenda.StartHeight = votingStartHeight
			agenda.EndHeight = votingEndHeight
			log.Printf("Voting on %s will occur between %d-%d", agenda.ID, votingStartHeight, votingEndHeight)
		}

		if votingStartHeight > currentHeight {
			// Voting hasnt started yet. So we cannot count the votes.
			// Nothing more to do with these agendas
			log.Printf("Voting is in the future so not counting votes yet")
			allAgendas = append(allAgendas, agendas...)
			break
		}

		// If agenda voting is currently in progress, only check votes up to the latest block
		if votingEndHeight > currentHeight {
			log.Printf("Voting is currently on-going")
			votingEndHeight = currentHeight
		}

		// Count votes and store totals within Agenda struct
		for _, agenda := range agendas {
			log.Printf("Counting votes for %s between blocks %d-%d",
				agenda.ID, votingStartHeight, votingEndHeight)
			err = agenda.countVotes(dcrdClient, votingStartHeight, votingEndHeight)
			if err != nil {
				return nil, err
			}
		}

		allAgendas = append(allAgendas, agendas...)
	}
	return allAgendas, nil
}
<commit_msg>Fix incorrect locked-in and activated heights. (#214)<commit_after>// Copyright (c) 2017-2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
	"html/template"
	"log"
	"regexp"

	"github.com/decred/dcrd/dcrjson"
	"github.com/decred/dcrd/rpcclient"
)

// Agenda contains all of the data representing an agenda for the html
// template programming.
type Agenda struct {
	ID              string
	Status          string
	Description     string
	Mask            uint16
	VoteVersion     uint32
	QuorumThreshold uint32
	StartHeight     int64
	EndHeight       int64
	VoteChoices     map[string]VoteChoice
	VoteCounts      map[string]uint32
}

// VoteChoice contains the details of a vote choice from an agenda,
// Each agenda will have 3 choices - yes/no/maybe
type VoteChoice struct {
	ID          string
	Description string
	Bits        uint16
}

var dcpRE = regexp.MustCompile(`(?i)DCP\-?(\d{4})`)

// Agenda status may be: started, defined, lockedin, failed, active

// IsActive indicates if the agenda is active
func (a *Agenda) IsActive() bool {
	return a.Status == "active"
}

// IsStarted indicates if the agenda is started
func (a *Agenda) IsStarted() bool {
	return a.Status == "started"
}

// IsDefined indicates if the agenda is defined
func (a *Agenda) IsDefined() bool {
	return a.Status == "defined"
}

// IsLockedIn indicates if the agenda is lockedin
func (a *Agenda) IsLockedIn() bool {
	return a.Status == "lockedin"
}

// IsFailed indicates if the agenda is failed
func (a *Agenda) IsFailed() bool {
	return a.Status == "failed"
}

// BlockLockedIn returns the height of the first block of this agenda's lock-in period. -1 if this agenda has not been locked-in.
func (a *Agenda) BlockLockedIn() int64 {
	if a.IsLockedIn() || a.IsActive() {
		return a.EndHeight + 1
	}
	return -1
}

// BlockActivated returns the height of the first block with this agenda active. -1 if this agenda has not been activated.
func (a *Agenda) BlockActivated() int64 {
	if a.IsActive() {
		return a.BlockLockedIn() + int64(activeNetParams.RuleChangeActivationInterval)
	}
	return -1
}

// TotalVotes returns the total number of No, Yes and Abstain votes cast against this agenda
func (a *Agenda) TotalVotes() uint32 {
	return a.VoteCounts["yes"] + a.VoteCounts["no"] + a.VoteCounts["abstain"]
}

// VotePercent returns the the number of yes/no/abstains votes, as a percentage of
// all votes cast against this agenda
func (a *Agenda) VotePercent(voteID string) float64 {
	percent := float64(a.VoteCounts[voteID]) / float64(a.TotalVotes())
	return toFixed(percent*100, 2)
}

// VoteCountPercentage returns the number of votes cast against this agenda as
// a percentage of the theoretical maximum number of possible votes
func (a *Agenda) VoteCountPercentage() float64 {
	maxPossibleVotes := float64(activeNetParams.RuleChangeActivationInterval) * float64(activeNetParams.TicketsPerBlock)
	voteCountPercentage := float64(a.TotalVotes()) / maxPossibleVotes
	return toFixed(voteCountPercentage*100, 1)
}

// QuorumVotePercentage returns the total number of Yes and No votes as a
// percentage of the quorum threshold.
func (a *Agenda) QuorumVotePercentage() float64 {
	quorumVotePercentage := float64(a.VoteCounts["yes"]+a.VoteCounts["no"]) / float64(a.QuorumThreshold)
	return toFixed(quorumVotePercentage*100, 1)
}

// DescriptionWithDCPURL writes a new description with an link to any DCP that
// is detected in the text.  It is written to a template.HTML type so the link
// is not escaped when the template is executed.
func (a *Agenda) DescriptionWithDCPURL() template.HTML {
	subst := `<a href="https://github.com/decred/dcps/blob/master/dcp-${1}/dcp-${1}.mediawiki" target="_blank" rel="noopener noreferrer">${0}</a>`
	return template.HTML(dcpRE.ReplaceAllString(a.Description, subst))
}

// CountVotes uses the dcrd client to find all yes/no/abstain votes
// cast against this agenda. It will count the votes and store the
// totals inside the Agenda
func (a *Agenda) countVotes(dcrdClient *rpcclient.Client, votingStartHeight int64, votingEndHeight int64) error {
	// Find the last block hash of this voting period
	// Required to call GetStakeVersions
	lastBlockHash, err := dcrdClient.GetBlockHash(votingEndHeight)
	if err != nil {
		return err
	}

	// Retrieve all votes for this voting period
	stakeVersions, err := dcrdClient.GetStakeVersions(lastBlockHash.String(), int32(votingEndHeight-votingStartHeight))
	if err != nil {
		return err
	}

	// Collect all votes of the correct version
	var votes []dcrjson.VersionBits
	for _, sVer := range stakeVersions.StakeVersions {
		for _, vote := range sVer.Votes {
			if vote.Version == a.VoteVersion {
				votes = append(votes, vote)
			}
		}
	}

	// Count the votes and store the total
	for vID := range a.VoteChoices {
		var matchingVotes uint32
		for _, vote := range votes {
			if vote.Bits&a.Mask == a.VoteChoices[vID].Bits {
				matchingVotes++
			}
		}
		a.VoteCounts[vID] = matchingVotes
		log.Printf("\t%s: %d", vID, matchingVotes)
	}

	return nil
}

// agendasFromJSON parses the response from GetVoteInfo, and
// uses the data to create a set of Agenda objects
func agendasFromJSON(getVoteInfo dcrjson.GetVoteInfoResult) []Agenda {
	var parsedAgendas []Agenda
	for _, a := range getVoteInfo.Agendas {
		voteChoices := make(map[string]VoteChoice)
		for _, choice := range a.Choices {
			vote := VoteChoice{
				ID:          choice.ID,
				Description: choice.Description,
				Bits:        choice.Bits,
			}
			voteChoices[vote.ID] = vote
		}
		parsedAgendas = append(parsedAgendas, Agenda{
			ID:              a.ID,
			Status:          a.Status,
			Description:     a.Description,
			Mask:            a.Mask,
			VoteVersion:     getVoteInfo.VoteVersion,
			QuorumThreshold: getVoteInfo.Quorum,
			VoteChoices:     voteChoices,
			VoteCounts:      make(map[string]uint32),
		})
	}
	return parsedAgendas
}

func agendasForVersions(dcrdClient *rpcclient.Client, versions []uint32, currentHeight int64, svis StakeVersionIntervals) ([]Agenda, error) {
	var allAgendas []Agenda
	for _, version := range versions {
		// Retrieve Agendas for this voting period
		getVoteInfo, err := dcrdClient.GetVoteInfo(version)
		if err != nil {
			return nil, err
		}
		agendas := agendasFromJSON(*getVoteInfo)

		// Check if upgrade to this version has occurred yet
		upgradeOccurred, upgradeHeight := svis.GetStakeVersionUpgradeHeight(version)

		if !upgradeOccurred {
			// Haven't upgraded to this stake version yet. Therefore
			// we dont know when the voting start/end heights will be.
			// Nothing more to do with these agendas
			log.Printf("Upgrade to stake version %d has not happened", version)
			allAgendas = append(allAgendas, agendas...)
			break
		}

		log.Printf("Upgrade to version %d happened at height %d", version, upgradeHeight)

		// Find the start of the next RCI after the threshold was met
		nextRCIStartHeight := activeNetParams.StakeValidationHeight
		for nextRCIStartHeight < upgradeHeight {
			nextRCIStartHeight += int64(activeNetParams.RuleChangeActivationInterval)
		}

		// Next RCI height tells us the voting start/end heights, and we can add these to the agendas
		votingStartHeight := nextRCIStartHeight
		votingEndHeight := nextRCIStartHeight + int64(activeNetParams.RuleChangeActivationInterval) - 1
		for i := range agendas {
			agendas[i].StartHeight = votingStartHeight
			agendas[i].EndHeight = votingEndHeight
			log.Printf("Voting on %s will occur between %d-%d", agendas[i].ID, votingStartHeight, votingEndHeight)
		}

		if votingStartHeight > currentHeight {
			// Voting hasnt started yet. So we cannot count the votes.
			// Nothing more to do with these agendas
			log.Printf("Voting is in the future so not counting votes yet")
			allAgendas = append(allAgendas, agendas...)
			break
		}

		// If agenda voting is currently in progress, only check votes up to the latest block
		if votingEndHeight > currentHeight {
			log.Printf("Voting is currently on-going")
			votingEndHeight = currentHeight
		}

		// Count votes and store totals within Agenda struct
		for _, agenda := range agendas {
			log.Printf("Counting votes for %s between blocks %d-%d",
				agenda.ID, votingStartHeight, votingEndHeight)
			err = agenda.countVotes(dcrdClient, votingStartHeight, votingEndHeight)
			if err != nil {
				return nil, err
			}
		}

		allAgendas = append(allAgendas, agendas...)
	}
	return allAgendas, nil
}
<|endoftext|>


<commit_before>// in memory of http://memegenerator.net/instance/37313316 and https://xkcd.com/927/

package unifi

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"reflect"
)

type Unifi struct {
	client *http.Client
	host   string
}

type meta struct {
	Rc string
}

// Initializes a session
func Login(user, pass, host string) *Unifi {
	u := new(Unifi)
	u.login(user, pass, host)
	return u
}

func (u *Unifi) login(user, pass, host string) {
	val := url.Values{
		"login":    {"login"},
		"username": {user},
		"password": {pass},
	}
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	cj, _ := cookiejar.New(nil)
	u.client = &http.Client{
		Transport: tr,
		Jar:       cj,
	}
	u.host = "https://" + host + ":8443/"
	if _, err := u.client.PostForm(u.host+"login", val); err != nil {
		log.Fatal(err)
	}
}

// Terminates a session
func (u *Unifi) Logout() {
	u.client.Get(u.host + "logout")
}

func (u *Unifi) apicmd(cmd string) []byte {
	resp, err := u.client.Get(u.host + "api/" + cmd)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	return body
}

func (u *Unifi) devcmd(mac, cmd string) {
	u.maccmd(mac, cmd, "devmgr")
}

func (u *Unifi) stacmd(mac, cmd string) {
	u.maccmd(mac, cmd, "stamgr")
}

func (u *Unifi) maccmd(mac, cmd, mgr string) {
	type command struct {
		Mac string `json:"mac"`
		Cmd string `json:"cmd"`
	}
	param, err := json.Marshal(command{Mac: mac, Cmd: cmd})
	if err != nil {
		log.Fatal(err)
	}
	val := url.Values{"json": {string(param)}}
	if _, err := u.client.PostForm(u.host+"api/cmd/"+mgr, val); err != nil {
		log.Fatal(err)
	}
}

func (u *Unifi) parse(cmd string, v interface{}) {
	body := u.apicmd(cmd)
	if err := json.Unmarshal(body, &v); err != nil {
		log.Fatal(err)
	}
	m := reflect.ValueOf(v).Elem().FieldByName("Meta").Interface().(meta)
	if m.Rc != "ok" {
		log.Fatal(m.Rc)
	}
}

// Returns a slice of access points
func (u *Unifi) Aps() []Aps {
	var response struct {
		Data []Aps
		Meta meta
	}
	u.parse("stat/device", &response)
	for i, _ := range response.Data {
		response.Data[i].u = u
	}
	return response.Data
}

// Returns a map of access points with mac as a key
func (u *Unifi) ApsMap() ApsMap {
	m := make(ApsMap)
	for _, a := range u.Aps() {
		m[a.Mac] = a
	}
	return m
}

// Returns a slice of stations
func (u *Unifi) Sta() []Sta {
	var response struct {
		Data []Sta
		Meta meta
	}
	u.parse("stat/sta", &response)
	for i, _ := range response.Data {
		response.Data[i].u = u
	}
	return response.Data
}

// Returns a map of stations with MAC as a key
func (u *Unifi) StaMap() StaMap {
	m := make(StaMap)
	for _, s := range u.Sta() {
		m[s.Mac] = s
	}
	return m
}

// Returns a slice of known users
func (u *Unifi) Users() []User {
	var response struct {
		Data []User
		Meta meta
	}
	u.parse("list/user", &response)
	return response.Data
}

func (u *Unifi) UserGroups() {
	body := u.apicmd("list/usergroup")
	fmt.Printf("%s\n", body)
}

// Returns a Wlan config
func (u *Unifi) WlanConf() []WlanConf {
	var response struct {
		Data []WlanConf
		Meta meta
	}
	u.parse("list/wlanconf", &response)
	return response.Data
}

func (u *Unifi) CreateBackup() {
}

func (u *Unifi) GetBackup() {
}
<commit_msg>add usergroup<commit_after>// in memory of http://memegenerator.net/instance/37313316 and https://xkcd.com/927/

package unifi

import (
	"crypto/tls"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"reflect"
)

type Unifi struct {
	client *http.Client
	host   string
}

type meta struct {
	Rc string
}

// Initializes a session
func Login(user, pass, host string) *Unifi {
	u := new(Unifi)
	u.login(user, pass, host)
	return u
}

func (u *Unifi) login(user, pass, host string) {
	val := url.Values{
		"login":    {"login"},
		"username": {user},
		"password": {pass},
	}
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	cj, _ := cookiejar.New(nil)
	u.client = &http.Client{
		Transport: tr,
		Jar:       cj,
	}
	u.host = "https://" + host + ":8443/"
	if _, err := u.client.PostForm(u.host+"login", val); err != nil {
		log.Fatal(err)
	}
}

// Terminates a session
func (u *Unifi) Logout() {
	u.client.Get(u.host + "logout")
}

func (u *Unifi) apicmd(cmd string) []byte {
	resp, err := u.client.Get(u.host + "api/" + cmd)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	return body
}

func (u *Unifi) devcmd(mac, cmd string) {
	u.maccmd(mac, cmd, "devmgr")
}

func (u *Unifi) stacmd(mac, cmd string) {
	u.maccmd(mac, cmd, "stamgr")
}

func (u *Unifi) maccmd(mac, cmd, mgr string) {
	type command struct {
		Mac string `json:"mac"`
		Cmd string `json:"cmd"`
	}
	param, err := json.Marshal(command{Mac: mac, Cmd: cmd})
	if err != nil {
		log.Fatal(err)
	}
	val := url.Values{"json": {string(param)}}
	if _, err := u.client.PostForm(u.host+"api/cmd/"+mgr, val); err != nil {
		log.Fatal(err)
	}
}

func (u *Unifi) parse(cmd string, v interface{}) {
	body := u.apicmd(cmd)
	if err := json.Unmarshal(body, &v); err != nil {
		log.Fatal(err)
	}
	m := reflect.ValueOf(v).Elem().FieldByName("Meta").Interface().(meta)
	if m.Rc != "ok" {
		log.Fatal(m.Rc)
	}
}

// Returns a slice of access points
func (u *Unifi) Aps() []Aps {
	var response struct {
		Data []Aps
		Meta meta
	}
	u.parse("stat/device", &response)
	for i, _ := range response.Data {
		response.Data[i].u = u
	}
	return response.Data
}

// Returns a map of access points with mac as a key
func (u *Unifi) ApsMap() ApsMap {
	m := make(ApsMap)
	for _, a := range u.Aps() {
		m[a.Mac] = a
	}
	return m
}

// Returns a slice of stations
func (u *Unifi) Sta() []Sta {
	var response struct {
		Data []Sta
		Meta meta
	}
	u.parse("stat/sta", &response)
	for i, _ := range response.Data {
		response.Data[i].u = u
	}
	return response.Data
}

// Returns a map of stations with MAC as a key
func (u *Unifi) StaMap() StaMap {
	m := make(StaMap)
	for _, s := range u.Sta() {
		m[s.Mac] = s
	}
	return m
}

// Returns a slice of known users
func (u *Unifi) Users() []User {
	var response struct {
		Data []User
		Meta meta
	}
	u.parse("list/user", &response)
	return response.Data
}

// Returns user groups
func (u *Unifi) UserGroups() []UserGroup {
	var response struct {
		Data []UserGroup
		Meta meta
	}
	u.parse("list/usergroup", &response)
	return response.Data
}

// Returns a Wlan config
func (u *Unifi) WlanConf() []WlanConf {
	var response struct {
		Data []WlanConf
		Meta meta
	}
	u.parse("list/wlanconf", &response)
	return response.Data
}

func (u *Unifi) CreateBackup() {
}

func (u *Unifi) GetBackup() {
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/fatih/color"
	mkr "github.com/mackerelio/mackerel-client-go"
	"github.com/mackerelio/mkr/logger"
	"gopkg.in/urfave/cli.v1"
)

var commandAlerts = cli.Command{
	Name:  "alerts",
	Usage: "Retrieve/Close alerts",
	Description: `
    Retrieve/Close alerts. With no subcommand specified, this will show all alerts.
    Requests APIs under "/api/v0/alerts". See https://mackerel.io/api-docs/entry/alerts .
`,
	Action: doAlertsRetrieve,
	Subcommands: []cli.Command{
		{
			Name:      "list",
			Usage:     "list alerts",
			ArgsUsage: "[--service | -s <service>] [--host-status | -S <file>] [--color | -c]",
			Description: `
    Shows alerts in human-readable format.
`,
			Action: doAlertsList,
			Flags: []cli.Flag{
				cli.StringSliceFlag{
					Name:  "service, s",
					Value: &cli.StringSlice{},
					Usage: "Filters alerts by service. Multiple choices are allowed.",
				},
				cli.StringSliceFlag{
					Name:  "host-status, S",
					Value: &cli.StringSlice{},
					Usage: "Filters alerts by status of each host. Multiple choices are allowed.",
				},
				cli.BoolTFlag{Name: "color, c", Usage: "Colorize output. default: true"},
			},
		},
		{
			Name:      "close",
			Usage:     "close alerts",
			ArgsUsage: "<alertIds....>",
			Description: `
    Closes alerts. Multiple alert IDs can be specified.
`,
			Action: doAlertsClose,
			Flags: []cli.Flag{
				cli.StringFlag{Name: "reason, r", Value: "", Usage: "Reason of closing alert."},
				cli.BoolFlag{Name: "verbose, v", Usage: "Verbose output mode"},
			},
		},
	},
}

type alertSet struct {
	Alert   *mkr.Alert
	Host    *mkr.Host
	Monitor mkr.Monitor
}

func joinMonitorsAndHosts(client *mkr.Client, alerts []*mkr.Alert) []*alertSet {
	hostsJSON, err := client.FindHosts(&mkr.FindHostsParam{
		Statuses: []string{"working", "standby", "poweroff", "maintenance"},
	})
	logger.DieIf(err)

	hosts := map[string]*mkr.Host{}
	for _, host := range hostsJSON {
		hosts[host.ID] = host
	}

	monitorsJSON, err := client.FindMonitors()
	logger.DieIf(err)

	monitors := map[string]mkr.Monitor{}
	for _, monitor := range monitorsJSON {
		monitors[monitor.MonitorID()] = monitor
	}

	alertSets := []*alertSet{}
	for _, alert := range alerts {
		alertSets = append(
			alertSets,
			&alertSet{Alert: alert, Host: hosts[alert.HostID], Monitor: monitors[alert.MonitorID]},
		)
	}
	return alertSets
}

func formatJoinedAlert(alertSet *alertSet, colorize bool) string {
	const layout = "2006-01-02 15:04:05"

	host := alertSet.Host
	monitor := alertSet.Monitor
	alert := alertSet.Alert

	hostMsg := ""
	if host != nil {
		statusMsg := host.Status
		if host.IsRetired == true {
			statusMsg = "retired"
		}
		if colorize {
			switch statusMsg {
			case "working":
				statusMsg = color.BlueString("working")
			case "standby":
				statusMsg = color.GreenString("standby")
			case "poweroff":
				statusMsg = "poweroff"
			case "maintenance":
				statusMsg = color.YellowString("maintenance")
			}
		}
		hostMsg = fmt.Sprintf(" %s %s", host.Name, statusMsg)
		roleMsgs := []string{}
		for service, roles := range host.Roles {
			roleMsgs = append(roleMsgs, fmt.Sprintf("%s:%s", service, strings.Join(roles, ",")))
		}
		hostMsg += " [" + strings.Join(roleMsgs, ", ") + "]"
	}

	monitorMsg := ""
	if monitor != nil {
		switch m := monitor.(type) {
		case *mkr.MonitorConnectivity:
			monitorMsg = ""
		case *mkr.MonitorHostMetric:
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", m.Metric, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", m.Metric, alert.Value, m.Operator, *m.Warning)
			} else {
				monitorMsg = fmt.Sprintf("%s %.2f", m.Metric, alert.Value)
			}
		case *mkr.MonitorServiceMetric:
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %s %.2f %s %.2f", m.Service, m.Metric, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %s %.2f %s %.2f", m.Service, m.Metric, alert.Value, m.Operator, *m.Warning)
			} else {
				monitorMsg = fmt.Sprintf("%s %s %.2f", m.Service, m.Metric, alert.Value)
			}
		case *mkr.MonitorExternalHTTP:
			statusRegexp, _ := regexp.Compile("^[2345][0-9][0-9]$")
			switch alert.Status {
			case "CRITICAL":
				if statusRegexp.MatchString(alert.Message) && m.ResponseTimeCritical != nil {
					monitorMsg = fmt.Sprintf("%s %.2f > %.2f msec, status:%s", m.URL, alert.Value, *m.ResponseTimeCritical, alert.Message)
				} else {
					monitorMsg = fmt.Sprintf("%s %.2f msec, %s", m.URL, alert.Value, alert.Message)
				}
			case "WARNING":
				if statusRegexp.MatchString(alert.Message) && m.ResponseTimeWarning != nil {
					monitorMsg = fmt.Sprintf("%.2f > %.2f msec, status:%s", alert.Value, *m.ResponseTimeWarning, alert.Message)
				} else {
					monitorMsg = fmt.Sprintf("%.2f msec, %s", alert.Value, alert.Message)
				}
			default:
				monitorMsg = fmt.Sprintf("%.2f msec, status:%s", alert.Value, alert.Message)
			}
		case *mkr.MonitorExpression:
			expression := formatExpressionOneline(m.Expression)
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", expression, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", expression, alert.Value, m.Operator, *m.Warning)
			} else if alert.Status == "UNKNOWN" {
				monitorMsg = fmt.Sprintf("%s", expression)
			} else {
				monitorMsg = fmt.Sprintf("%s %.2f", expression, alert.Value)
			}
		default:
			monitorMsg = fmt.Sprintf("%s", monitor.MonitorType())
		}
		if monitorMsg == "" {
			monitorMsg = monitor.MonitorName()
		} else {
			monitorMsg = monitor.MonitorName() + " " + monitorMsg
		}
	}
	statusMsg := alert.Status
	if colorize {
		switch alert.Status {
		case "CRITICAL":
			statusMsg = color.RedString("CRITICAL")
		case "WARNING":
			statusMsg = color.YellowString("WARNING ")
		case "UNKNOWN":
			statusMsg = "UNKNOWN "
		}
	}
	return fmt.Sprintf("%s %s %s %s%s", alert.ID, time.Unix(alert.OpenedAt, 0).Format(layout), statusMsg, monitorMsg, hostMsg)
}

var expressionNewlinePattern = regexp.MustCompile(`\s*[\r\n]+\s*`)

func formatExpressionOneline(expr string) string {
	expr = strings.Trim(expressionNewlinePattern.ReplaceAllString(expr, " "), " ")
	return strings.Replace(strings.Replace(expr, "( ", "(", -1), " )", ")", -1)
}

func doAlertsRetrieve(c *cli.Context) error {
	client := newMackerelFromContext(c)

	alerts, err := client.FindAlerts()
	logger.DieIf(err)
	PrettyPrintJSON(alerts)
	return nil
}

func doAlertsList(c *cli.Context) error {
	filterServices := c.StringSlice("service")
	filterStatuses := c.StringSlice("host-status")
	client := newMackerelFromContext(c)

	alerts, err := client.FindAlerts()
	logger.DieIf(err)
	joinedAlerts := joinMonitorsAndHosts(client, alerts)

	for _, joinAlert := range joinedAlerts {
		if len(filterServices) > 0 {
			found := false
			for _, filterService := range filterServices {
				if joinAlert.Host != nil {
					if _, ok := joinAlert.Host.Roles[filterService]; ok {
						found = true
					}
				} else {
					var service string
					if m, ok := joinAlert.Monitor.(*mkr.MonitorServiceMetric); ok {
						service = m.Service
					} else if m, ok := joinAlert.Monitor.(*mkr.MonitorExternalHTTP); ok {
						service = m.Service
					}
					found = service == filterService
				}
			}
			if !found {
				continue
			}
		}
		if len(filterStatuses) > 0 {
			found := false
			for _, filterStatus := range filterStatuses {
				if joinAlert.Host != nil && joinAlert.Host.Status == filterStatus {
					found = true
				}
			}
			if !found {
				continue
			}
		}
		fmt.Fprintln(color.Output, formatJoinedAlert(joinAlert, c.BoolT("color")))
	}
	return nil
}

func doAlertsClose(c *cli.Context) error {
	isVerbose := c.Bool("verbose")
	argAlertIDs := c.Args()
	reason := c.String("reason")

	if len(argAlertIDs) < 1 {
		cli.ShowCommandHelp(c, "alerts")
		os.Exit(1)
	}

	client := newMackerelFromContext(c)
	for _, alertID := range argAlertIDs {
		alert, err := client.CloseAlert(alertID, reason)
		logger.DieIf(err)

		logger.Log("Alert closed", alertID)
		if isVerbose == true {
			PrettyPrintJSON(alert)
		}
	}
	return nil
}
<commit_msg>show alert.Message as monitorMsg in mkr alerts list<commit_after>package main

import (
	"fmt"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/fatih/color"
	mkr "github.com/mackerelio/mackerel-client-go"
	"github.com/mackerelio/mkr/logger"
	"gopkg.in/urfave/cli.v1"
)

var commandAlerts = cli.Command{
	Name:  "alerts",
	Usage: "Retrieve/Close alerts",
	Description: `
    Retrieve/Close alerts. With no subcommand specified, this will show all alerts.
    Requests APIs under "/api/v0/alerts". See https://mackerel.io/api-docs/entry/alerts .
`,
	Action: doAlertsRetrieve,
	Subcommands: []cli.Command{
		{
			Name:      "list",
			Usage:     "list alerts",
			ArgsUsage: "[--service | -s <service>] [--host-status | -S <file>] [--color | -c]",
			Description: `
    Shows alerts in human-readable format.
`,
			Action: doAlertsList,
			Flags: []cli.Flag{
				cli.StringSliceFlag{
					Name:  "service, s",
					Value: &cli.StringSlice{},
					Usage: "Filters alerts by service. Multiple choices are allowed.",
				},
				cli.StringSliceFlag{
					Name:  "host-status, S",
					Value: &cli.StringSlice{},
					Usage: "Filters alerts by status of each host. Multiple choices are allowed.",
				},
				cli.BoolTFlag{Name: "color, c", Usage: "Colorize output. default: true"},
			},
		},
		{
			Name:      "close",
			Usage:     "close alerts",
			ArgsUsage: "<alertIds....>",
			Description: `
    Closes alerts. Multiple alert IDs can be specified.
`,
			Action: doAlertsClose,
			Flags: []cli.Flag{
				cli.StringFlag{Name: "reason, r", Value: "", Usage: "Reason of closing alert."},
				cli.BoolFlag{Name: "verbose, v", Usage: "Verbose output mode"},
			},
		},
	},
}

type alertSet struct {
	Alert   *mkr.Alert
	Host    *mkr.Host
	Monitor mkr.Monitor
}

func joinMonitorsAndHosts(client *mkr.Client, alerts []*mkr.Alert) []*alertSet {
	hostsJSON, err := client.FindHosts(&mkr.FindHostsParam{
		Statuses: []string{"working", "standby", "poweroff", "maintenance"},
	})
	logger.DieIf(err)

	hosts := map[string]*mkr.Host{}
	for _, host := range hostsJSON {
		hosts[host.ID] = host
	}

	monitorsJSON, err := client.FindMonitors()
	logger.DieIf(err)

	monitors := map[string]mkr.Monitor{}
	for _, monitor := range monitorsJSON {
		monitors[monitor.MonitorID()] = monitor
	}

	alertSets := []*alertSet{}
	for _, alert := range alerts {
		alertSets = append(
			alertSets,
			&alertSet{Alert: alert, Host: hosts[alert.HostID], Monitor: monitors[alert.MonitorID]},
		)
	}
	return alertSets
}

func formatJoinedAlert(alertSet *alertSet, colorize bool) string {
	const layout = "2006-01-02 15:04:05"

	host := alertSet.Host
	monitor := alertSet.Monitor
	alert := alertSet.Alert

	hostMsg := ""
	if host != nil {
		statusMsg := host.Status
		if host.IsRetired == true {
			statusMsg = "retired"
		}
		if colorize {
			switch statusMsg {
			case "working":
				statusMsg = color.BlueString("working")
			case "standby":
				statusMsg = color.GreenString("standby")
			case "poweroff":
				statusMsg = "poweroff"
			case "maintenance":
				statusMsg = color.YellowString("maintenance")
			}
		}
		hostMsg = fmt.Sprintf(" %s %s", host.Name, statusMsg)
		roleMsgs := []string{}
		for service, roles := range host.Roles {
			roleMsgs = append(roleMsgs, fmt.Sprintf("%s:%s", service, strings.Join(roles, ",")))
		}
		hostMsg += " [" + strings.Join(roleMsgs, ", ") + "]"
	}

	monitorMsg := ""
	if monitor != nil {
		switch m := monitor.(type) {
		case *mkr.MonitorConnectivity:
			monitorMsg = ""
		case *mkr.MonitorHostMetric:
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", m.Metric, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", m.Metric, alert.Value, m.Operator, *m.Warning)
			} else {
				monitorMsg = fmt.Sprintf("%s %.2f", m.Metric, alert.Value)
			}
		case *mkr.MonitorServiceMetric:
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %s %.2f %s %.2f", m.Service, m.Metric, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %s %.2f %s %.2f", m.Service, m.Metric, alert.Value, m.Operator, *m.Warning)
			} else {
				monitorMsg = fmt.Sprintf("%s %s %.2f", m.Service, m.Metric, alert.Value)
			}
		case *mkr.MonitorExternalHTTP:
			statusRegexp, _ := regexp.Compile("^[2345][0-9][0-9]$")
			switch alert.Status {
			case "CRITICAL":
				if statusRegexp.MatchString(alert.Message) && m.ResponseTimeCritical != nil {
					monitorMsg = fmt.Sprintf("%s %.2f > %.2f msec, status:%s", m.URL, alert.Value, *m.ResponseTimeCritical, alert.Message)
				} else {
					monitorMsg = fmt.Sprintf("%s %.2f msec, %s", m.URL, alert.Value, alert.Message)
				}
			case "WARNING":
				if statusRegexp.MatchString(alert.Message) && m.ResponseTimeWarning != nil {
					monitorMsg = fmt.Sprintf("%.2f > %.2f msec, status:%s", alert.Value, *m.ResponseTimeWarning, alert.Message)
				} else {
					monitorMsg = fmt.Sprintf("%.2f msec, %s", alert.Value, alert.Message)
				}
			default:
				monitorMsg = fmt.Sprintf("%.2f msec, status:%s", alert.Value, alert.Message)
			}
		case *mkr.MonitorExpression:
			expression := formatExpressionOneline(m.Expression)
			if alert.Status == "CRITICAL" && m.Critical != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", expression, alert.Value, m.Operator, *m.Critical)
			} else if alert.Status == "WARNING" && m.Warning != nil {
				monitorMsg = fmt.Sprintf("%s %.2f %s %.2f", expression, alert.Value, m.Operator, *m.Warning)
			} else if alert.Status == "UNKNOWN" {
				monitorMsg = fmt.Sprintf("%s", expression)
			} else {
				monitorMsg = fmt.Sprintf("%s %.2f", expression, alert.Value)
			}
		default:
			monitorMsg = fmt.Sprintf("%s", monitor.MonitorType())
		}
		if monitorMsg == "" {
			monitorMsg = monitor.MonitorName()
		} else {
			monitorMsg = monitor.MonitorName() + " " + monitorMsg
		}
	}
	// If alert is caused by check monitoring, take monitorMsg from alert.message
	if alert.Type == "check" {
		monitorMsg = alert.Message
	}

	statusMsg := alert.Status
	if colorize {
		switch alert.Status {
		case "CRITICAL":
			statusMsg = color.RedString("CRITICAL")
		case "WARNING":
			statusMsg = color.YellowString("WARNING ")
		case "UNKNOWN":
			statusMsg = "UNKNOWN "
		}
	}
	return fmt.Sprintf("%s %s %s %s%s", alert.ID, time.Unix(alert.OpenedAt, 0).Format(layout), statusMsg, monitorMsg, hostMsg)
}

var expressionNewlinePattern = regexp.MustCompile(`\s*[\r\n]+\s*`)

func formatExpressionOneline(expr string) string {
	expr = strings.Trim(expressionNewlinePattern.ReplaceAllString(expr, " "), " ")
	return strings.Replace(strings.Replace(expr, "( ", "(", -1), " )", ")", -1)
}

func doAlertsRetrieve(c *cli.Context) error {
	client := newMackerelFromContext(c)

	alerts, err := client.FindAlerts()
	logger.DieIf(err)
	PrettyPrintJSON(alerts)
	return nil
}

func doAlertsList(c *cli.Context) error {
	filterServices := c.StringSlice("service")
	filterStatuses := c.StringSlice("host-status")
	client := newMackerelFromContext(c)

	alerts, err := client.FindAlerts()
	logger.DieIf(err)
	joinedAlerts := joinMonitorsAndHosts(client, alerts)

	for _, joinAlert := range joinedAlerts {
		if len(filterServices) > 0 {
			found := false
			for _, filterService := range filterServices {
				if joinAlert.Host != nil {
					if _, ok := joinAlert.Host.Roles[filterService]; ok {
						found = true
					}
				} else {
					var service string
					if m, ok := joinAlert.Monitor.(*mkr.MonitorServiceMetric); ok {
						service = m.Service
					} else if m, ok := joinAlert.Monitor.(*mkr.MonitorExternalHTTP); ok {
						service = m.Service
					}
					found = service == filterService
				}
			}
			if !found {
				continue
			}
		}
		if len(filterStatuses) > 0 {
			found := false
			for _, filterStatus := range filterStatuses {
				if joinAlert.Host != nil && joinAlert.Host.Status == filterStatus {
					found = true
				}
			}
			if !found {
				continue
			}
		}
		fmt.Fprintln(color.Output, formatJoinedAlert(joinAlert, c.BoolT("color")))
	}
	return nil
}

func doAlertsClose(c *cli.Context) error {
	isVerbose := c.Bool("verbose")
	argAlertIDs := c.Args()
	reason := c.String("reason")

	if len(argAlertIDs) < 1 {
		cli.ShowCommandHelp(c, "alerts")
		os.Exit(1)
	}

	client := newMackerelFromContext(c)
	for _, alertID := range argAlertIDs {
		alert, err := client.CloseAlert(alertID, reason)
		logger.DieIf(err)

		logger.Log("Alert closed", alertID)
		if isVerbose == true {
			PrettyPrintJSON(alert)
		}
	}
	return nil
}
<|endoftext|>