<commit_before>package queue_test

import(
	"go-queues/queue"
	"log"
	"sync"
)

// In this example, we build two integer queues
// and play around with them a little.

// IntQueue is our example structure to hold queues for integers.
type IntQueue struct {
	// Enqueue is identified as the enqueueing method by the
	// `queue:"enqueue"` structure tag.
	// This tag is sufficient to identify the enqueuing method.
	// We could have given the Enqueue method a different name,
	// but for clarity it makes sense to stick with Enqueue.
	// Enqueue takes exactly one argument,
	// the element x to be enqueued, and returns nothing.
	// Since this is supposed to be an integer queue,
	// we declare x to be of type int.
	Enqueue func( x int ) `queue:"enqueue"`

	// Analogous to Enqueue, Dequeue is identified as the dequeueing
	// method by the structure tag `queue:"dequeue"`.
	// Dequeue does not take any argument,
	// and returns two values: the dequeued element x and ok,
	// a boolean indicator whether the dequeue operation was successful.
	// A value of true for ok means success,
	// a value of false means that the queue was empty just as Dequeue
	// attempted to dequeue an element. In this case, x is the zero value,
	// which in the case of int is 0.
	// It would be an error to declare the return value x
	// to be of different type
	// than the argument x of the enqueueing method.
	Dequeue func()( x int, ok bool ) `queue:"dequeue"`
}

func Example() {
	// First, we make a queue for non-concurrent use.
	var ncq IntQueue
	if err := queue.Make( &ncq, queue.DefaultConfig().NonConcurrent() ); err != nil {
		log.Fatal( err )
	}
	// Let's enqueue a bunch of integers...
	for i := 1; i <= 10; i++ {
		ncq.Enqueue( i )
		log.Printf( "Enqueued %d\n", i )
	}
	// ...and dequeue them.
	for i := 1; i <= 10; i++ {
		x, _ := ncq.Dequeue()
		log.Printf( "Dequeued %d\n", x )
	}
	// The queue is now empty. Trying to dequeue again fails:
	_, ok := ncq.Dequeue()
	log.Printf( "Success dequeueing from empty queue: %v\n", ok )

	// Let's try concurrent queue access now.
	var ccq IntQueue
	if err := queue.Make( &ccq, nil /* passing nil selects a default implementation suitable for concurrent use */ ); err != nil {
		log.Fatal( err )
	}
	// We create two writing (enqueueing) and two reading goroutines.
	var wg sync.WaitGroup
	writer := func( id int ) {
		defer wg.Done()
		for i := 1; i <= 10; i++ {
			ccq.Enqueue( i )
			log.Printf( "Goroutine %d enqueued %d\n", id, i )
		}
	}
	reader := func( id int ) {
		defer wg.Done()
		for i := 1; i <= 10; i++ {
			x, ok := ccq.Dequeue()
			if ok {
				log.Printf( "Goroutine %d dequeued %d\n", id, x )
			} else {
				log.Printf( "Goroutine %d: queue was empty\n", id )
			}
		}
	}
	wg.Add( 4 )
	go writer( 1 )
	go writer( 2 )
	go reader( 3 )
	go reader( 4 )
	wg.Wait()
}
<commit_msg>Example: change import path to github repo path<commit_after>package queue_test

import(
	"github.com/TheCount/go-queues/queue"
	"log"
	"sync"
)

// In this example, we build two integer queues
// and play around with them a little.

// IntQueue is our example structure to hold queues for integers.
type IntQueue struct {
	// Enqueue is identified as the enqueueing method by the
	// `queue:"enqueue"` structure tag.
	// This tag is sufficient to identify the enqueuing method.
	// We could have given the Enqueue method a different name,
	// but for clarity it makes sense to stick with Enqueue.
	// Enqueue takes exactly one argument,
	// the element x to be enqueued, and returns nothing.
	// Since this is supposed to be an integer queue,
	// we declare x to be of type int.
	Enqueue func( x int ) `queue:"enqueue"`

	// Analogous to Enqueue, Dequeue is identified as the dequeueing
	// method by the structure tag `queue:"dequeue"`.
	// Dequeue does not take any argument,
	// and returns two values: the dequeued element x and ok,
	// a boolean indicator whether the dequeue operation was successful.
	// A value of true for ok means success,
	// a value of false means that the queue was empty just as Dequeue
	// attempted to dequeue an element. In this case, x is the zero value,
	// which in the case of int is 0.
	// It would be an error to declare the return value x
	// to be of different type
	// than the argument x of the enqueueing method.
	Dequeue func()( x int, ok bool ) `queue:"dequeue"`
}

func Example() {
	// First, we make a queue for non-concurrent use.
	var ncq IntQueue
	if err := queue.Make( &ncq, queue.DefaultConfig().NonConcurrent() ); err != nil {
		log.Fatal( err )
	}
	// Let's enqueue a bunch of integers...
	for i := 1; i <= 10; i++ {
		ncq.Enqueue( i )
		log.Printf( "Enqueued %d\n", i )
	}
	// ...and dequeue them.
	for i := 1; i <= 10; i++ {
		x, _ := ncq.Dequeue()
		log.Printf( "Dequeued %d\n", x )
	}
	// The queue is now empty. Trying to dequeue again fails:
	_, ok := ncq.Dequeue()
	log.Printf( "Success dequeueing from empty queue: %v\n", ok )

	// Let's try concurrent queue access now.
	var ccq IntQueue
	if err := queue.Make( &ccq, nil /* passing nil selects a default implementation suitable for concurrent use */ ); err != nil {
		log.Fatal( err )
	}
	// We create two writing (enqueueing) and two reading goroutines.
	var wg sync.WaitGroup
	writer := func( id int ) {
		defer wg.Done()
		for i := 1; i <= 10; i++ {
			ccq.Enqueue( i )
			log.Printf( "Goroutine %d enqueued %d\n", id, i )
		}
	}
	reader := func( id int ) {
		defer wg.Done()
		for i := 1; i <= 10; i++ {
			x, ok := ccq.Dequeue()
			if ok {
				log.Printf( "Goroutine %d dequeued %d\n", id, x )
			} else {
				log.Printf( "Goroutine %d: queue was empty\n", id )
			}
		}
	}
	wg.Add( 4 )
	go writer( 1 )
	go writer( 2 )
	go reader( 3 )
	go reader( 4 )
	wg.Wait()
}
<|endoftext|>


<commit_before>package ledger

import (
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
)

type Entry struct {
	Date        string // "Y-m-d"
	Description string
	Change      int // in cents
}

func FormatLedger(currency string, locale string, entries []Entry) (output string, e error) {
	if !isValidCurrency(currency) {
		return "", errors.New("invalid currency")
	}
	if !isValidLocale(locale) {
		return "", errors.New("invalid locale")
	}
	if !isValidDate(entries) {
		return "", errors.New("invalid date")
	}

	entriesCopy := append([]Entry{}, entries...)

	m1 := map[bool]int{true: 0, false: 1}
	m2 := map[bool]int{true: -1, false: 1}
	es := entriesCopy
	for len(es) > 1 {
		first, rest := es[0], es[1:]
		success := false
		for !success {
			success = true
			for i, e := range rest {
				if (m1[e.Date == first.Date]*m2[e.Date < first.Date]*4 +
					m1[e.Description == first.Description]*m2[e.Description < first.Description]*2 +
					m1[e.Change == first.Change]*m2[e.Change < first.Change]*1) < 0 {
					es[0], es[i+1] = es[i+1], es[0]
					success = false
				}
			}
		}
		es = es[1:]
	}

	output += header(locale)
	for _, entry := range entriesCopy {
		output += formatEntry(entry, locale, currency)
	}
	return output, nil
}

func formatEntry(entry Entry, locale string, currency string) (formatted string) {
	description := formatDescription(entry.Description)
	date := formatDate(locale, entry.Date)
	change := formatChange(locale, currency, entry.Change)
	return date + strings.Repeat(" ", 10-len(date)) + " | " + description + " | " +
		strings.Repeat(" ", 13-len(change)) + change + "\n"
}

func getCurrencySymbol(currency string) (symbol string) {
	if currency == "EUR" {
		return "€"
	} else if currency == "USD" {
		return "$"
	}
	panic("invalid currency")
}

func formatChange(locale string, currency string, cents int) (change string) {
	cents = int(math.Abs(float64(cents)))
	if locale == "nl-NL" {
		change += getCurrencySymbol(currency)
		change += " "
		centsStr := strconv.Itoa(cents)
		switch len(centsStr) {
		case 1:
			centsStr = "00" + centsStr
		case 2:
			centsStr = "0" + centsStr
		}
		rest := centsStr[:len(centsStr)-2]
		var parts []string
		for len(rest) > 3 {
			parts = append(parts, rest[len(rest)-3:])
			rest = rest[:len(rest)-3]
		}
		if len(rest) > 0 {
			parts = append(parts, rest)
		}
		for i := len(parts) - 1; i >= 0; i-- {
			change += parts[i] + "."
		}
		change = change[:len(change)-1]
		change += ","
		change += centsStr[len(centsStr)-2:]
		if cents < 0 {
			change += "-"
		} else {
			change += " "
		}
	} else if locale == "en-US" {
		if cents < 0 {
			change += "("
		}
		change += getCurrencySymbol(currency)
		centsStr := strconv.Itoa(cents)
		switch len(centsStr) {
		case 1:
			centsStr = "00" + centsStr
		case 2:
			centsStr = "0" + centsStr
		}
		rest := centsStr[:len(centsStr)-2]
		var parts []string
		for len(rest) > 3 {
			parts = append(parts, rest[len(rest)-3:])
			rest = rest[:len(rest)-3]
		}
		if len(rest) > 0 {
			parts = append(parts, rest)
		}
		for i := len(parts) - 1; i >= 0; i-- {
			change += parts[i] + ","
		}
		change = change[:len(change)-1]
		change += "."
		change += centsStr[len(centsStr)-2:]
		if cents < 0 {
			change += ")"
		} else {
			change += " "
		}
	}
	return change
}

func isValidCurrency(currency string) bool {
	return currency == "USD" || currency == "EUR"
}

func isValidLocale(locale string) bool {
	return locale == "en-US" || locale == "nl-NL"
}

func isValidDate(entries []Entry) bool {
	for _, entry := range entries {
		if len(entry.Date) != 10 {
			return false
		}
		_, d2, _, d4, _ := entry.Date[0:4], entry.Date[4], entry.Date[5:7], entry.Date[7], entry.Date[8:10]
		if d2 != '-' || d4 != '-' {
			return false
		}
	}
	return true
}

func formatDescription(description string) string {
	if len(description) > 25 {
		return description[:22] + "..."
	}
	return description + strings.Repeat(" ", 25-len(description))
}
func formatDate(locale string, date string) string {
	year, month, day := date[0:4], date[5:7], date[8:10]

	if locale == "nl-NL" {
		return fmt.Sprintf("%v-%v-%v", day, month, year)
	} else if locale == "en-US" {
		return fmt.Sprintf("%v/%v/%v", month, day, year)
	}
	panic("invalid locale")
}

func header(locale string) (output string) {
	if locale == "nl-NL" {
		return "Datum" +
			strings.Repeat(" ", 10-len("Datum")) +
			" | " +
			"Omschrijving" +
			strings.Repeat(" ", 25-len("Omschrijving")) +
			" | " + "Verandering" + "\n"
	} else if locale == "en-US" {
		return "Date" +
			strings.Repeat(" ", 10-len("Date")) +
			" | " +
			"Description" +
			strings.Repeat(" ", 25-len("Description")) +
			" | " + "Change" + "\n"
	}
	panic("invalid locale")
}
<commit_msg>Prefer fmt verbs<commit_after>package ledger

import (
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
)

type Entry struct {
	Date        string // "Y-m-d"
	Description string
	Change      int // in cents
}

func FormatLedger(currency string, locale string, entries []Entry) (output string, e error) {
	if !isValidCurrency(currency) {
		return "", errors.New("invalid currency")
	}
	if !isValidLocale(locale) {
		return "", errors.New("invalid locale")
	}
	if !isValidDate(entries) {
		return "", errors.New("invalid date")
	}

	entriesCopy := append([]Entry{}, entries...)

	m1 := map[bool]int{true: 0, false: 1}
	m2 := map[bool]int{true: -1, false: 1}
	es := entriesCopy
	for len(es) > 1 {
		first, rest := es[0], es[1:]
		success := false
		for !success {
			success = true
			for i, e := range rest {
				if (m1[e.Date == first.Date]*m2[e.Date < first.Date]*4 +
					m1[e.Description == first.Description]*m2[e.Description < first.Description]*2 +
					m1[e.Change == first.Change]*m2[e.Change < first.Change]*1) < 0 {
					es[0], es[i+1] = es[i+1], es[0]
					success = false
				}
			}
		}
		es = es[1:]
	}

	output += header(locale)
	for _, entry := range entriesCopy {
		output += formatEntry(entry, locale, currency)
	}
	return output, nil
}

func formatEntry(entry Entry, locale string, currency string) (formatted string) {
	description := formatDescription(entry.Description)
	date := formatDate(locale, entry.Date)
	change := formatChange(locale, currency, entry.Change)
	return fmt.Sprintf("%s | %s | %13s\n", date, description, change)
}

func getCurrencySymbol(currency string) (symbol string) {
	if currency == "EUR" {
		return "€"
	} else if currency == "USD" {
		return "$"
	}
	panic("invalid currency")
}

func formatChange(locale string, currency string, cents int) (change string) {
	cents = int(math.Abs(float64(cents)))
	if locale == "nl-NL" {
		change += getCurrencySymbol(currency)
		change += " "
		centsStr := strconv.Itoa(cents)
		switch len(centsStr) {
		case 1:
			centsStr = "00" + centsStr
		case 2:
			centsStr = "0" + centsStr
		}
		rest := centsStr[:len(centsStr)-2]
		var parts []string
		for len(rest) > 3 {
			parts = append(parts, rest[len(rest)-3:])
			rest = rest[:len(rest)-3]
		}
		if len(rest) > 0 {
			parts = append(parts, rest)
		}
		for i := len(parts) - 1; i >= 0; i-- {
			change += parts[i] + "."
		}
		change = change[:len(change)-1]
		change += ","
		change += centsStr[len(centsStr)-2:]
		if cents < 0 {
			change += "-"
		} else {
			change += " "
		}
	} else if locale == "en-US" {
		if cents < 0 {
			change += "("
		}
		change += getCurrencySymbol(currency)
		centsStr := strconv.Itoa(cents)
		switch len(centsStr) {
		case 1:
			centsStr = "00" + centsStr
		case 2:
			centsStr = "0" + centsStr
		}
		rest := centsStr[:len(centsStr)-2]
		var parts []string
		for len(rest) > 3 {
			parts = append(parts, rest[len(rest)-3:])
			rest = rest[:len(rest)-3]
		}
		if len(rest) > 0 {
			parts = append(parts, rest)
		}
		for i := len(parts) - 1; i >= 0; i-- {
			change += parts[i] + ","
		}
		change = change[:len(change)-1]
		change += "."
		change += centsStr[len(centsStr)-2:]
		if cents < 0 {
			change += ")"
		} else {
			change += " "
		}
	}
	return change
}

func isValidCurrency(currency string) bool {
	return currency == "USD" || currency == "EUR"
}

func isValidLocale(locale string) bool {
	return locale == "en-US" || locale == "nl-NL"
}

func isValidDate(entries []Entry) bool {
	for _, entry := range entries {
		if len(entry.Date) != 10 {
			return false
		}
		_, d2, _, d4, _ := entry.Date[0:4], entry.Date[4], entry.Date[5:7], entry.Date[7], entry.Date[8:10]
		if d2 != '-' || d4 != '-' {
			return false
		}
	}
	return true
}

func formatDescription(description string) string {
	if len(description) > 25 {
		return description[:22] + "..."
	}
	return description + strings.Repeat(" ", 25-len(description))
}
func formatDate(locale string, date string) string {
	year, month, day := date[0:4], date[5:7], date[8:10]

	if locale == "nl-NL" {
		return fmt.Sprintf("%v-%v-%v", day, month, year)
	} else if locale == "en-US" {
		return fmt.Sprintf("%v/%v/%v", month, day, year)
	}
	panic("invalid locale")
}

func header(locale string) (output string) {
	if locale == "nl-NL" {
		return "Datum" +
			strings.Repeat(" ", 10-len("Datum")) +
			" | " +
			"Omschrijving" +
			strings.Repeat(" ", 25-len("Omschrijving")) +
			" | " + "Verandering" + "\n"
	} else if locale == "en-US" {
		return "Date" +
			strings.Repeat(" ", 10-len("Date")) +
			" | " +
			"Description" +
			strings.Repeat(" ", 25-len("Description")) +
			" | " + "Change" + "\n"
	}
	panic("invalid locale")
}
<|endoftext|>


<commit_before>package ledger

import (
	"errors"
	"fmt"
	"math"
	"sort"
	"strconv"
	"strings"
)

type Entry struct {
	Date        string // "Y-m-d"
	Description string
	Change      int // in cents
}

type Locale struct {
	Date              string
	Description       string
	Change            string
	DateSeperator     string
	DecimalPoint      string
	IntegralSeperator string
}

var locales = map[string]Locale{
	"en-US": {
		Date:              "Date",
		Description:       "Description",
		Change:            "Change",
		DateSeperator:     "/",
		DecimalPoint:      ".",
		IntegralSeperator: ",",
	},
	"nl-NL": {
		Date:              "Datum",
		Description:       "Omschrijving",
		Change:            "Verandering",
		DateSeperator:     "-",
		DecimalPoint:      ",",
		IntegralSeperator: ".",
	},
}

var currencyToSymbol = map[string]string{
	"USD": "$",
	"EUR": "€",
}

func FormatLedger(currency string, locale string, entries []Entry) (output string, e error) {
	if !isValidCurrency(currency) {
		return "", errors.New("invalid currency")
	}
	if !isValidLocale(locale) {
		return "", errors.New("invalid locale")
	}
	if !isValidDate(entries) {
		return "", errors.New("invalid date")
	}

	entriesCopy := make([]Entry, len(entries))
	copy(entriesCopy, entries)
	sort.Slice(entriesCopy, func(i int, j int) bool {
		a := entries[i]
		b := entries[j]
		if a.Date != b.Date {
			return a.Date < b.Date
		} else if a.Description != b.Description {
			return a.Description < b.Description
		} else {
			return a.Change < b.Change
		}
	})

	output += header(locale)
	for _, entry := range entriesCopy {
		output += formatEntry(locale, currency, entry)
	}
	return output, nil
}

func formatEntry(locale string, currency string, entry Entry) (formatted string) {
	date := formatDate(locale, entry.Date)
	description := formatDescription(entry.Description)
	change := formatChange(locale, currency, entry.Change)

	// This conditional is necessary because expected output is aligned
	// differently for negative vs. non-negative values
	if isNegative(entry.Change) {
		return fmt.Sprintf("%-10s | %-25s | %13s\n", date, description, change)
	} else {
		return fmt.Sprintf("%-10s | %-25s | %12s\n", date, description, change)
	}
}

func header(locale string) (output string) {
	return fmt.Sprintf("%-10s | %-25s | %s\n", locales[locale].Date, locales[locale].Description, locales[locale].Change)
}

func formatDate(locale string, date string) string {
	year, month, day := date[0:4], date[5:7], date[8:10]

	if locale == "nl-NL" {
		return strings.Join([]string{day, month, year}, locales[locale].DateSeperator)
	} else if locale == "en-US" {
		return strings.Join([]string{month, day, year}, locales[locale].DateSeperator)
	}
	panic("invalid locale")
}

// formatDescription will ellipsize the description if it is longer than 25
// characters
func formatDescription(description string) string {
	if len(description) > 25 {
		return description[:22] + "..."
	}
	return description
}

func formatChange(locale string, currency string, cents int) (change string) {
	isNegative := isNegative(cents)
	paddedChange := fmt.Sprintf("%03s", strconv.Itoa(absoluteValue(cents)))
	change += formatCurrencySymbol(locale, currency)
	change += formatNumber(locale, paddedChange)
	if isNegative {
		change = formatNegative(locale, change)
	}
	return change
}

func formatNumber(locale string, paddedChange string) (formatted string) {
	integralPart := formatIntegralPart(paddedChange, locale)
	decimalPart := paddedChange[len(paddedChange)-2:]
	return strings.Join([]string{integralPart, decimalPart}, locales[locale].DecimalPoint)
}

func formatCurrencySymbol(locale string, currency string) (formatted string) {
	if locale == "en-US" {
		return currencyToSymbol[currency]
	} else if locale == "nl-NL" {
		return currencyToSymbol[currency] + " "
	}
	panic("invalid locale")
}

func formatNegative(locale string, change string) (formatted string) {
	if locale == "en-US" {
		return fmt.Sprintf("(%s)", change)
	} else if locale == "nl-NL" {
		return fmt.Sprintf("%s-", change)
	}
	panic("invalid locale")
}

func formatIntegralPart(centsStr string, locale string) (formatted string) {
	var parts []string
	remainder := centsStr[:len(centsStr)-2]
	for len(remainder) > 3 {
		parts = append([]string{remainder[len(remainder)-3:]}, parts...)
		remainder = remainder[:len(remainder)-3]
	}
	if len(remainder) > 0 {
		parts = append([]string{remainder}, parts...)
	}
	return strings.Join(parts, locales[locale].IntegralSeperator)
}

func isValidCurrency(currency string) bool {
	_, ok := currencyToSymbol[currency]
	return ok
}

func isValidLocale(locale string) bool {
	_, ok := locales[locale]
	return ok
}

func isValidDate(entries []Entry) bool {
	for _, entry := range entries {
		if len(entry.Date) != 10 {
			return false
		}
		_, d2, _, d4, _ := entry.Date[0:4], entry.Date[4], entry.Date[5:7], entry.Date[7], entry.Date[8:10]
		if d2 != '-' || d4 != '-' {
			return false
		}
	}
	return true
}

func isNegative(cents int) bool {
	return cents < 0
}

func absoluteValue(n int) int {
	return int(math.Abs(float64(n)))
}
<commit_msg>Extract sortEntries<commit_after>package ledger

import (
	"errors"
	"fmt"
	"math"
	"sort"
	"strconv"
	"strings"
)

type Entry struct {
	Date        string // "Y-m-d"
	Description string
	Change      int // in cents
}

type Locale struct {
	Date              string
	Description       string
	Change            string
	DateSeperator     string
	DecimalPoint      string
	IntegralSeperator string
}

var locales = map[string]Locale{
	"en-US": {
		Date:              "Date",
		Description:       "Description",
		Change:            "Change",
		DateSeperator:     "/",
		DecimalPoint:      ".",
		IntegralSeperator: ",",
	},
	"nl-NL": {
		Date:              "Datum",
		Description:       "Omschrijving",
		Change:            "Verandering",
		DateSeperator:     "-",
		DecimalPoint:      ",",
		IntegralSeperator: ".",
	},
}

var currencyToSymbol = map[string]string{
	"USD": "$",
	"EUR": "€",
}

func FormatLedger(currency string, locale string, entries []Entry) (output string, e error) {
	if !isValidCurrency(currency) {
		return "", errors.New("invalid currency")
	}
	if !isValidLocale(locale) {
		return "", errors.New("invalid locale")
	}
	if !isValidDate(entries) {
		return "", errors.New("invalid date")
	}

	output += header(locale)
	for _, entry := range sortEntries(entries) {
		output += formatEntry(locale, currency, entry)
	}
	return output, nil
}

func sortEntries(entries []Entry) (sorted []Entry) {
	sorted = make([]Entry, len(entries))
	copy(sorted, entries)
	sort.Slice(sorted, func(i int, j int) bool {
		a := entries[i]
		b := entries[j]
		if a.Date != b.Date {
			return a.Date < b.Date
		} else if a.Description != b.Description {
			return a.Description < b.Description
		} else {
			return a.Change < b.Change
		}
	})
	return sorted
}

func formatEntry(locale string, currency string, entry Entry) (formatted string) {
	date := formatDate(locale, entry.Date)
	description := formatDescription(entry.Description)
	change := formatChange(locale, currency, entry.Change)

	// This conditional is necessary because expected output is aligned
	// differently for negative vs. non-negative values
	if isNegative(entry.Change) {
		return fmt.Sprintf("%-10s | %-25s | %13s\n", date, description, change)
	} else {
		return fmt.Sprintf("%-10s | %-25s | %12s\n", date, description, change)
	}
}

func header(locale string) (output string) {
	return fmt.Sprintf("%-10s | %-25s | %s\n", locales[locale].Date, locales[locale].Description, locales[locale].Change)
}

func formatDate(locale string, date string) string {
	year, month, day := date[0:4], date[5:7], date[8:10]

	if locale == "nl-NL" {
		return strings.Join([]string{day, month, year}, locales[locale].DateSeperator)
	} else if locale == "en-US" {
		return strings.Join([]string{month, day, year}, locales[locale].DateSeperator)
	}
	panic("invalid locale")
}

// formatDescription will ellipsize the description if it is longer than 25
// characters
func formatDescription(description string) string {
	if len(description) > 25 {
		return description[:22] + "..."
	}
	return description
}

func formatChange(locale string, currency string, cents int) (change string) {
	isNegative := isNegative(cents)
	paddedChange := fmt.Sprintf("%03s", strconv.Itoa(absoluteValue(cents)))
	change += formatCurrencySymbol(locale, currency)
	change += formatNumber(locale, paddedChange)
	if isNegative {
		change = formatNegative(locale, change)
	}
	return change
}

func formatNumber(locale string, paddedChange string) (formatted string) {
	integralPart := formatIntegralPart(paddedChange, locale)
	decimalPart := paddedChange[len(paddedChange)-2:]
	return strings.Join([]string{integralPart, decimalPart}, locales[locale].DecimalPoint)
}

func formatCurrencySymbol(locale string, currency string) (formatted string) {
	if locale == "en-US" {
		return currencyToSymbol[currency]
	} else if locale == "nl-NL" {
		return currencyToSymbol[currency] + " "
	}
	panic("invalid locale")
}

func formatNegative(locale string, change string) (formatted string) {
	if locale == "en-US" {
		return fmt.Sprintf("(%s)", change)
	} else if locale == "nl-NL" {
		return fmt.Sprintf("%s-", change)
	}
	panic("invalid locale")
}

func formatIntegralPart(centsStr string, locale string) (formatted string) {
	var parts []string
	remainder := centsStr[:len(centsStr)-2]
	for len(remainder) > 3 {
		parts = append([]string{remainder[len(remainder)-3:]}, parts...)
		remainder = remainder[:len(remainder)-3]
	}
	if len(remainder) > 0 {
		parts = append([]string{remainder}, parts...)
	}
	return strings.Join(parts, locales[locale].IntegralSeperator)
}

func isValidCurrency(currency string) bool {
	_, ok := currencyToSymbol[currency]
	return ok
}

func isValidLocale(locale string) bool {
	_, ok := locales[locale]
	return ok
}

func isValidDate(entries []Entry) bool {
	for _, entry := range entries {
		if len(entry.Date) != 10 {
			return false
		}
		_, d2, _, d4, _ := entry.Date[0:4], entry.Date[4], entry.Date[5:7], entry.Date[7], entry.Date[8:10]
		if d2 != '-' || d4 != '-' {
			return false
		}
	}
	return true
}

func isNegative(cents int) bool {
	return cents < 0
}

func absoluteValue(n int) int {
	return int(math.Abs(float64(n)))
}
<|endoftext|>


<commit_before>package libkb

import (
	"fmt"
)

// NOTE: This file is the source of truth for our version number, but we have a
//       script that reads it at packaging/version.sh. If you refactor this
//       file, update that script.

// Version as MAJOR.MINOR.PATCH
const Version = "1.0.0"

// Build number
const Build = "23"

// VersionString returns semantic version string.
func VersionString() string {
	return fmt.Sprintf("%s-%s", Version, Build)
}
<commit_msg>New version v1.0.0-24<commit_after>package libkb

import (
	"fmt"
)

// NOTE: This file is the source of truth for our version number, but we have a
//       script that reads it at packaging/version.sh. If you refactor this
//       file, update that script.

// Version as MAJOR.MINOR.PATCH
const Version = "1.0.0"

// Build number
const Build = "24"

// VersionString returns semantic version string.
func VersionString() string {
	return fmt.Sprintf("%s-%s", Version, Build)
}
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package timers stores points in time to be used for metrics.
package timers

import (
	"fmt"
	"time"

	"golang.org/x/sys/windows/registry"
)

const (
	timerFmt = "2006-01-02 15:04:05.000000+00:00"
)

var (
	// TimersRoot indicates the root Timers registry key.
	TimersRoot = `SOFTWARE\Glazier\Timers`
)

// A Timer stores named time elements.
type Timer struct {
	Name string
	Time time.Time
}

// NewTimer creates a new Timer.
func NewTimer(name string, at *time.Time) *Timer {
	if at == nil {
		t := time.Now()
		at = &t
	}
	return &Timer{Name: name, Time: *at}
}

// Load loads a timer object into the registry.
func (t *Timer) Load() error {
	k, err := registry.OpenKey(registry.LOCAL_MACHINE, TimersRoot, registry.QUERY_VALUE)
	if err != nil {
		return fmt.Errorf("reg.OpenKey: %w", err)
	}
	defer k.Close()
	v, _, err := k.GetStringValue("TIMER_" + t.Name)
	if err != nil {
		return fmt.Errorf("GetStringValue: %w", err)
	}
	p, err := time.Parse(timerFmt, v)
	if err != nil {
		return fmt.Errorf("time.Parse: %w", err)
	}
	t.Time = p
	return nil
}

// Record records a timer object into the registry.
func (t *Timer) Record() error {
	k, err := registry.OpenKey(registry.LOCAL_MACHINE, TimersRoot, registry.WRITE)
	if err != nil {
		return err
	}
	defer k.Close()
	return k.SetStringValue("TIMER_"+t.Name, t.TimeString())
}

// TimeString renders a timer's time value with the default formatting.
func (t *Timer) TimeString() string {
	return t.Time.Format(timerFmt)
}
<commit_msg>Use CreateKey for Record(), to create timer keys that don't already exist.<commit_after>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package timers stores points in time to be used for metrics.
package timers

import (
	"fmt"
	"time"

	"golang.org/x/sys/windows/registry"
)

const (
	timerFmt = "2006-01-02 15:04:05.000000+00:00"
)

var (
	// TimersRoot indicates the root Timers registry key.
	TimersRoot = `SOFTWARE\Glazier\Timers`
)

// A Timer stores named time elements.
type Timer struct {
	Name string
	Time time.Time
}

// NewTimer creates a new Timer.
func NewTimer(name string, at *time.Time) *Timer {
	if at == nil {
		t := time.Now()
		at = &t
	}
	return &Timer{Name: name, Time: *at}
}

// Load loads a timer object into the registry.
func (t *Timer) Load() error {
	k, err := registry.OpenKey(registry.LOCAL_MACHINE, TimersRoot, registry.QUERY_VALUE)
	if err != nil {
		return fmt.Errorf("reg.OpenKey: %w", err)
	}
	defer k.Close()
	v, _, err := k.GetStringValue("TIMER_" + t.Name)
	if err != nil {
		return fmt.Errorf("GetStringValue: %w", err)
	}
	p, err := time.Parse(timerFmt, v)
	if err != nil {
		return fmt.Errorf("time.Parse: %w", err)
	}
	t.Time = p
	return nil
}

// Record records a timer object into the registry.
func (t *Timer) Record() error {
	k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, TimersRoot, registry.WRITE)
	if err != nil {
		return err
	}
	defer k.Close()
	return k.SetStringValue("TIMER_"+t.Name, t.TimeString())
}

// TimeString renders a timer's time value with the default formatting.
func (t *Timer) TimeString() string {
	return t.Time.Format(timerFmt)
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"

	"github.com/Sirupsen/logrus"
	"golang.org/x/net/context"
	pb "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
)

// ImageStatus returns the status of the image.
func (s *Server) ImageStatus(ctx context.Context, req *pb.ImageStatusRequest) (*pb.ImageStatusResponse, error) {
	logrus.Debugf("ImageStatusRequest: %+v", req)
	image := ""
	img := req.GetImage()
	if img != nil {
		image = img.GetImage()
	}
	if image == "" {
		return nil, fmt.Errorf("no image specified")
	}
	status, err := s.images.ImageStatus(s.imageContext, image)
	if err != nil {
		return nil, err
	}
	resp := &pb.ImageStatusResponse{
		Image: &pb.Image{
			Id:       &status.ID,
			RepoTags: status.Names,
			Size_:    status.Size,
		},
	}
	logrus.Debugf("ImageStatusResponse: %+v", resp)
	return resp, nil
}
<commit_msg>server: image_status: ignore storage.ErrImageUnknown<commit_after>package server

import (
	"fmt"

	"github.com/Sirupsen/logrus"
	"github.com/containers/storage/storage"
	"golang.org/x/net/context"
	pb "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
)

// ImageStatus returns the status of the image.
func (s *Server) ImageStatus(ctx context.Context, req *pb.ImageStatusRequest) (*pb.ImageStatusResponse, error) {
	logrus.Debugf("ImageStatusRequest: %+v", req)
	image := ""
	img := req.GetImage()
	if img != nil {
		image = img.GetImage()
	}
	if image == "" {
		return nil, fmt.Errorf("no image specified")
	}
	status, err := s.images.ImageStatus(s.imageContext, image)
	if err != nil {
		if err == storage.ErrImageUnknown {
			return &pb.ImageStatusResponse{}, nil
		}
		return nil, err
	}
	resp := &pb.ImageStatusResponse{
		Image: &pb.Image{
			Id:       &status.ID,
			RepoTags: status.Names,
			Size_:    status.Size,
		},
	}
	logrus.Debugf("ImageStatusResponse: %+v", resp)
	return resp, nil
}
<|endoftext|>


<commit_before>package core

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"regexp"
	"strings"
)

// https://en.wikipedia.org/wiki/Chord_(music)
type Chord struct {
	start     Note
	inversion int // Ground,Inversion1,Inversion2,Inversion3
	interval  int // Triad,Seventh,Sixth
	quality   int // Major,Minor,Dominant,Augmented,Diminished,Suspended2,Suspended4
}

func zeroChord() Chord {
	return Chord{start: N("C"), inversion: Ground, quality: Major, interval: Triad}
}

func (c Chord) Inspect(i Inspection) {
	i.Properties[""] = c.S().String()
}

func (c Chord) WithInterval(i int) Chord {
	c.interval = i
	return c
}

func (c Chord) WithInversion(i int) Chord {
	c.inversion = i
	return c
}

func (c Chord) WithQuality(q int) Chord {
	c.quality = q
	return c
}

func (c Chord) String() string {
	if c.start.IsRest() {
		return c.start.String()
	}
	var b bytes.Buffer
	fmt.Fprint(&b, c.start.String())
	endsWithSlash := false
	emitSeparator := func() {
		if !endsWithSlash {
			io.WriteString(&b, "/")
		}
		endsWithSlash = false
	}

	if c.quality != Major {
		switch c.quality {
		case Minor:
			emitSeparator()
			io.WriteString(&b, "m")
		case Major:
			emitSeparator()
			io.WriteString(&b, "M")
		case Diminished:
			emitSeparator()
			io.WriteString(&b, "dim")
		case Septiem:
			emitSeparator()
		case Augmented:
			emitSeparator()
			io.WriteString(&b, "aug") // OR +  TODO
		case Suspended2:
			emitSeparator()
			io.WriteString(&b, "sus2")
		case Suspended4:
			emitSeparator()
			io.WriteString(&b, "sus4")
		}

	}
	if c.interval != Triad {
		switch c.interval {
		case Sixth:
			emitSeparator()
			io.WriteString(&b, "6")
		case Seventh:
			emitSeparator()
			io.WriteString(&b, "7")
		}
	}
	if c.inversion != Ground {
		switch c.inversion {
		case Inversion1:
			emitSeparator()
			io.WriteString(&b, "1")
		case Inversion2:
			emitSeparator()
			io.WriteString(&b, "2")
		case Inversion3:
			emitSeparator()
			io.WriteString(&b, "3")
		}
	}
	return b.String()
}

// Storex implements Storable
func (c Chord) Storex() string {
	return fmt.Sprintf("chord('%s')", c.String())
}

// S converts a Chord into a Sequence
func (c Chord) S() Sequence {
	return Sequence{[][]Note{c.Notes()}}
}

// Replaced is part of Replaceable
func (c Chord) Replaced(from, to Sequenceable) Sequenceable {
	if IsIdenticalTo(from, c) {
		return to
	}
	return c
}

// Notes returns the Note values for this chord.
func (c Chord) Notes() []Note {
	notes := []Note{c.start}
	if c.start.IsRest() ||
		c.start.IsPedalDown() ||
		c.start.IsPedalUp() ||
		c.start.IsPedalUpDown() {
		return notes
	}
	var semitones []int
	if c.interval == Triad {
		if c.quality == Augmented {
			semitones = []int{4, 8}
		} else if c.quality == Diminished {
			semitones = []int{3, 6}
		} else if c.quality == Major {
			semitones = []int{4, 7}
		} else if c.quality == Minor {
			semitones = []int{3, 7}
		} else if c.quality == Suspended2 {
			semitones = []int{2, 7}
		} else if c.quality == Suspended4 {
			semitones = []int{5, 7}
		}
	}
	if c.interval == Seventh {
		if c.quality == Augmented {
			semitones = []int{4, 8, 10}
		} else if c.quality == Diminished {
			semitones = []int{3, 6, 9}
		} else if c.quality == Minor {
			semitones = []int{3, 7, 10}
		} else if c.quality == Major {
			semitones = []int{4, 7, 11}
		} else if c.quality == Septiem {
			semitones = []int{4, 7, 10}
		}
	}
	for _, each := range semitones {
		next := c.start.Pitched(each)
		notes = append(notes, next)
	}
	// apply inversion
	if c.interval == Triad {
		if c.inversion == Inversion1 {
			notes = append(notes, notes[0].Octaved(1))[1:]
		}
		if c.inversion == Inversion2 {
			notes = append(notes, notes[0].Octaved(1))[1:]
			notes = append(notes, notes[0].Octaved(1))[1:]
		}
		// TODO handle inversion 3
	}
	return notes
}

var chordRegexp = regexp.MustCompile("([Mmdijaugo+su]*)([2467]?)")

//  C/D7/2 = C dominant 7, 2nd inversion
func ParseChord(s string) (Chord, error) {
	if len(s) == 0 {
		return Chord{}, errors.New("illegal chord: missing note")
	}
	parts := strings.Split(s, "/")
	start, err := ParseNote(parts[0])
	if err != nil {
		return Chord{}, err
	}
	if len(parts) == 1 {
		z := zeroChord()
		z.start = start
		return z, nil
	}
	// parts > 1
	chord := Chord{start: start}
	chord.inversion = readInversion(parts[1])

	matches := chordRegexp.FindStringSubmatch(parts[1])
	if matches == nil {
		return Chord{}, fmt.Errorf("illegal chord: [%s]", s)
	}
	switch matches[1] {
	case "maj":
		chord.quality = Major
	case "m":
		chord.quality = Minor
	case "dim":
		chord.quality = Diminished
	case "o":
		chord.quality = Diminished
	case "aug":
		chord.quality = Augmented
	case "+":
		chord.quality = Augmented
	case "sus":
		chord.quality = Suspended2
	case "":
		chord.quality = Major
	default:
		return Chord{}, fmt.Errorf("illegal chord quality: [%s]", matches[1])
	}
	chord.interval = Triad
	switch matches[2] {
	case "2":
		chord.quality = Suspended2
	case "4":
		chord.quality = Suspended4
	case "6":
		chord.interval = Sixth
	case "7":
		if len(matches[1]) == 0 {
			chord.quality = Septiem
		}
		chord.interval = Seventh
	}

	// parts > 2
	if len(parts) > 2 {
		chord.inversion = readInversion(parts[2])
	}
	return chord, nil
}

func readInversion(s string) int {
	switch s {
	case "1":
		return Inversion1
	case "2":
		return Inversion2
	case "3":
		return Inversion3
	default:
		return Ground
	}
}

func MustParseChord(s string) Chord {
	c, err := ParseChord(s)
	if err != nil {
		log.Fatal("ParseChord failed:", err)
	}
	return c
}
<commit_msg>fix chord string print<commit_after>package core

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"regexp"
	"strings"
)

// https://en.wikipedia.org/wiki/Chord_(music)
type Chord struct {
	start     Note
	inversion int // Ground,Inversion1,Inversion2,Inversion3
	interval  int // Triad,Seventh,Sixth
	quality   int // Major,Minor,Dominant,Augmented,Diminished,Suspended2,Suspended4
}

func zeroChord() Chord {
	return Chord{start: N("C"), inversion: Ground, quality: Major, interval: Triad}
}

func (c Chord) Inspect(i Inspection) {
	i.Properties[""] = c.S().String()
}

func (c Chord) WithInterval(i int) Chord {
	c.interval = i
	return c
}

func (c Chord) WithInversion(i int) Chord {
	c.inversion = i
	return c
}

func (c Chord) WithQuality(q int) Chord {
	c.quality = q
	return c
}

func (c Chord) String() string {
	if c.start.IsRest() {
		return c.start.String()
	}
	var b bytes.Buffer
	fmt.Fprint(&b, c.start.String())
	endsWithSlash := false
	emitSeparator := func() {
		if !endsWithSlash {
			io.WriteString(&b, "/")
		}
		endsWithSlash = false
	}

	if c.quality != Major {
		switch c.quality {
		case Minor:
			emitSeparator()
			io.WriteString(&b, "m")
		case Major:
			emitSeparator()
			io.WriteString(&b, "M")
		case Diminished:
			emitSeparator()
			io.WriteString(&b, "dim")
		case Augmented:
			emitSeparator()
			io.WriteString(&b, "aug") // OR +  TODO
		case Suspended2:
			emitSeparator()
			io.WriteString(&b, "sus2")
		case Suspended4:
			emitSeparator()
			io.WriteString(&b, "sus4")
		}

	}
	if c.interval != Triad {
		switch c.interval {
		case Sixth:
			emitSeparator()
			io.WriteString(&b, "6")
		case Seventh:
			emitSeparator()
			io.WriteString(&b, "7")
		}
	}
	if c.inversion != Ground {
		switch c.inversion {
		case Inversion1:
			emitSeparator()
			io.WriteString(&b, "1")
		case Inversion2:
			emitSeparator()
			io.WriteString(&b, "2")
		case Inversion3:
			emitSeparator()
			io.WriteString(&b, "3")
		}
	}
	return b.String()
}

// Storex implements Storable
func (c Chord) Storex() string {
	return fmt.Sprintf("chord('%s')", c.String())
}

// S converts a Chord into a Sequence
func (c Chord) S() Sequence {
	return Sequence{[][]Note{c.Notes()}}
}

// Replaced is part of Replaceable
func (c Chord) Replaced(from, to Sequenceable) Sequenceable {
	if IsIdenticalTo(from, c) {
		return to
	}
	return c
}

// Notes returns the Note values for this chord.
func (c Chord) Notes() []Note {
	notes := []Note{c.start}
	if c.start.IsRest() ||
		c.start.IsPedalDown() ||
		c.start.IsPedalUp() ||
		c.start.IsPedalUpDown() {
		return notes
	}
	var semitones []int
	if c.interval == Triad {
		if c.quality == Augmented {
			semitones = []int{4, 8}
		} else if c.quality == Diminished {
			semitones = []int{3, 6}
		} else if c.quality == Major {
			semitones = []int{4, 7}
		} else if c.quality == Minor {
			semitones = []int{3, 7}
		} else if c.quality == Suspended2 {
			semitones = []int{2, 7}
		} else if c.quality == Suspended4 {
			semitones = []int{5, 7}
		}
	}
	if c.interval == Seventh {
		if c.quality == Augmented {
			semitones = []int{4, 8, 10}
		} else if c.quality == Diminished {
			semitones = []int{3, 6, 9}
		} else if c.quality == Minor {
			semitones = []int{3, 7, 10}
		} else if c.quality == Major {
			semitones = []int{4, 7, 11}
		} else if c.quality == Septiem {
			semitones = []int{4, 7, 10}
		}
	}
	for _, each := range semitones {
		next := c.start.Pitched(each)
		notes = append(notes, next)
	}
	// apply inversion
	if c.interval == Triad {
		if c.inversion == Inversion1 {
			notes = append(notes, notes[0].Octaved(1))[1:]
		}
		if c.inversion == Inversion2 {
			notes = append(notes, notes[0].Octaved(1))[1:]
			notes = append(notes, notes[0].Octaved(1))[1:]
		}
		// TODO handle inversion 3
	}
	return notes
}

var chordRegexp = regexp.MustCompile("([Mmdijaugo+su]*)([2467]?)")

//  C/D7/2 = C dominant 7, 2nd inversion
func ParseChord(s string) (Chord, error) {
	if len(s) == 0 {
		return Chord{}, errors.New("illegal chord: missing note")
	}
	parts := strings.Split(s, "/")
	start, err := ParseNote(parts[0])
	if err != nil {
		return Chord{}, err
	}
	if len(parts) == 1 {
		z := zeroChord()
		z.start = start
		return z, nil
	}
	// parts > 1
	chord := Chord{start: start}
	chord.inversion = readInversion(parts[1])

	matches := chordRegexp.FindStringSubmatch(parts[1])
	if matches == nil {
		return Chord{}, fmt.Errorf("illegal chord: [%s]", s)
	}
	switch matches[1] {
	case "maj":
		chord.quality = Major
	case "m":
		chord.quality = Minor
	case "dim":
		chord.quality = Diminished
	case "o":
		chord.quality = Diminished
	case "aug":
		chord.quality = Augmented
	case "+":
		chord.quality = Augmented
	case "sus":
		chord.quality = Suspended2
	case "":
		chord.quality = Major
	default:
		return Chord{}, fmt.Errorf("illegal chord quality: [%s]", matches[1])
	}
	chord.interval = Triad
	switch matches[2] {
	case "2":
		chord.quality = Suspended2
	case "4":
		chord.quality = Suspended4
	case "6":
		chord.interval = Sixth
	case "7":
		if len(matches[1]) == 0 {
			chord.quality = Septiem
		}
		chord.interval = Seventh
	}

	// parts > 2
	if len(parts) > 2 {
		chord.inversion = readInversion(parts[2])
	}
	return chord, nil
}

func readInversion(s string) int {
	switch s {
	case "1":
		return Inversion1
	case "2":
		return Inversion2
	case "3":
		return Inversion3
	default:
		return Ground
	}
}

func MustParseChord(s string) Chord {
	c, err := ParseChord(s)
	if err != nil {
		log.Fatal("ParseChord failed:", err)
	}
	return c
}
<|endoftext|>


<commit_before>// Copyright 2014 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License. See the AUTHORS file
// for names of contributors.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

package gossip

import (
	"bytes"
	"fmt"
	"math"
	"reflect"
	"regexp"
	"sync"
	"time"

	"github.com/cockroachdb/cockroach/proto"
	"github.com/cockroachdb/cockroach/util"
	"github.com/cockroachdb/cockroach/util/log"
)

// callback holds regexp pattern match and GossipCallback method.
type callback struct {
	pattern *regexp.Regexp
	method  Callback
}

// infoStore objects manage maps of Info objects. They maintain a
// sequence number generator which they use to allocate new info
// objects.
//
// infoStores can be queried for incremental updates occurring since a
// specified sequence number.
//
// infoStores can be combined using deltas from peer nodes.
//
// infoStores are not thread safe.
type infoStore struct {
	Infos     infoMap             `json:"infos,omitempty"` // Map from key to info
	NodeID    proto.NodeID        `json:"-"`               // Owning node's ID
	NodeAddr  util.UnresolvedAddr `json:"-"`               // Address of node owning this info store: "host:port"
	MaxSeq    int64               `json:"-"`               // Maximum sequence number inserted
	seqGen    int64               // Sequence generator incremented each time info is added
	callbacks []callback
}

// monotonicUnixNano returns a monotonically increasing value for
// nanoseconds in Unix time. Since equal times are ignored with
// updates to infos, we're careful to avoid incorrectly ignoring a
// newly created value in the event one is created within the same
// nanosecond. Really unlikely except for the case of unittests, but
// better safe than sorry.
func monotonicUnixNano() int64 {
	monoTimeMu.Lock()
	defer monoTimeMu.Unlock()

	now := time.Now().UnixNano()
	if now <= lastTime {
		now = lastTime + 1
	}
	lastTime = now
	return now
}

// String returns a string representation of an infostore.
func (is *infoStore) String() string {
	buf := bytes.Buffer{}
	if infoCount := len(is.Infos); infoCount > 0 {
		buf.WriteString(fmt.Sprintf("infostore with %d info(s): ", infoCount))
	} else {
		return "infostore (empty)"
	}

	prepend := ""

	if err := is.visitInfos(func(i *info) error {
		str := fmt.Sprintf("%sinfo %q: %+v", prepend, i.Key, i.Val)
		prepend = ", "
		_, err := buf.WriteString(str)
		return err
	}); err != nil {
		log.Errorf("failed to properly construct string representation of infoStore: %s", err)
	}
	return buf.String()
}

var (
	monoTimeMu sync.Mutex
	lastTime   int64
)

// newInfoStore allocates and returns a new infoStore.
// "NodeAddr" is the address of the node owning the infostore
// in "host:port" format.
func newInfoStore(nodeID proto.NodeID, nodeAddr util.UnresolvedAddr) *infoStore {
	return &infoStore{
		Infos:    infoMap{},
		NodeID:   nodeID,
		NodeAddr: nodeAddr,
	}
}

// newInfo allocates and returns a new info object using specified key,
// value, and time-to-live.
func (is *infoStore) newInfo(key string, val interface{}, ttl time.Duration) *info {
	is.seqGen++
	now := monotonicUnixNano()
	ttlStamp := now + int64(ttl)
	if ttl == 0 {
		ttlStamp = math.MaxInt64
	}
	return &info{
		Key:       key,
		Val:       val,
		Timestamp: now,
		TTLStamp:  ttlStamp,
		NodeID:    is.NodeID,
		peerID:    is.NodeID,
		seq:       is.seqGen,
	}
}

// getInfo returns an info object by key or nil if it doesn't exist.
func (is *infoStore) getInfo(key string) *info {
	if info, ok := is.Infos[key]; ok {
		// Check TTL and discard if too old.
		if info.expired(time.Now().UnixNano()) {
			delete(is.Infos, key)
			return nil
		}
		return info
	}
	return nil
}

// addInfo adds or updates an info in the infos map.
//
// Returns nil if info was added; error otherwise.
func (is *infoStore) addInfo(i *info) error {
	// Only replace an existing info if new timestamp is greater, or if
	// timestamps are equal, but new hops is smaller.
	var contentsChanged bool
	if existingInfo, ok := is.Infos[i.Key]; ok {
		if i.Timestamp < existingInfo.Timestamp ||
			(i.Timestamp == existingInfo.Timestamp && i.Hops >= existingInfo.Hops) {
			return util.Errorf("info %+v older than current info %+v", i, existingInfo)
		}
		contentsChanged = !reflect.DeepEqual(existingInfo.Val, i.Val)
	} else {
		// No preexisting info means contentsChanged is true.
		contentsChanged = true
	}
	// Update info map.
	is.Infos[i.Key] = i
	if i.seq > is.MaxSeq {
		is.MaxSeq = i.seq
	}
	is.processCallbacks(i.Key, contentsChanged, i.Val)
	return nil
}

// maxHops returns the maximum hops across all infos in the store.
// This is the maximum number of gossip exchanges between any
// originator and this node.
func (is *infoStore) maxHops() uint32 {
	var maxHops uint32
	// will never error because `return nil` below
	_ = is.visitInfos(func(i *info) error {
		if i.Hops > maxHops {
			maxHops = i.Hops
		}
		return nil
	})
	return maxHops
}

// registerCallback compiles a regexp for pattern and adds it to
// the callbacks slice.
func (is *infoStore) registerCallback(pattern string, method Callback) {
	re := regexp.MustCompile(pattern)
	is.callbacks = append(is.callbacks, callback{pattern: re, method: method})
	var infos []*info
	if err := is.visitInfos(func(i *info) error {
		if re.MatchString(i.Key) {
			infos = append(infos, i)
		}
		return nil
	}); err != nil {
		log.Errorf("failed to properly run registered callback while visiting pre-existing info: %s", err)
	}
	// Run callbacks in a goroutine to avoid mutex reentry.
	go func() {
		for _, i := range infos {
			method(i.Key, true /* contentsChanged */, i.Val)
		}
	}()
}

// processCallbacks processes callbacks for the specified key by
// matching callback regular expression against the key and invoking
// the corresponding callback method on a match.
func (is *infoStore) processCallbacks(key string, contentsChanged bool, content interface{}) {
	var matches []callback
	for _, cb := range is.callbacks {
		if cb.pattern.MatchString(key) {
			matches = append(matches, cb)
		}
	}
	// Run callbacks in a goroutine to avoid mutex reentry.
	go func() {
		for _, cb := range matches {
			cb.method(key, contentsChanged, content)
		}
	}()
}

// visitInfos implements a visitor pattern to run the visitInfo
// function against each info in turn. Be sure to skip over any expired
// infos.
func (is *infoStore) visitInfos(visitInfo func(*info) error) error {
	now := time.Now().UnixNano()

	if visitInfo != nil {
		for _, i := range is.Infos {
			if i.expired(now) {
				delete(is.Infos, i.Key)
				continue
			}
			if err := visitInfo(i); err != nil {
				return err
			}
		}
	}

	return nil
}

// combine combines an incremental delta with the current infoStore.
// The sequence numbers on all info objects are reset using the info
// store's sequence generator. All hop distances on infos are
// incremented to indicate they've arrived from an external source.
// Returns the count of "fresh" infos in the provided delta.
func (is *infoStore) combine(delta *infoStore) int {
	var freshCount int
	if err := delta.visitInfos(func(i *info) error {
		is.seqGen++
		i.seq = is.seqGen
		i.Hops++
		i.peerID = delta.NodeID
		// Errors from addInfo here are not a problem; they simply
		// indicate that the data in *is is newer than in *delta.
		if err := is.addInfo(i); err == nil {
			freshCount++
		}
		return nil
	}); err != nil {
		log.Errorf("failed to properly combine infoStores: %s", err)
	}
	return freshCount
}

// delta returns an incremental delta of infos added to the info store
// since (not including) the specified sequence number. These deltas
// are intended for efficiently updating peer nodes. Any infos passed
// from node requesting delta are ignored.
//
// Returns nil if there are no deltas.
func (is *infoStore) delta(nodeID proto.NodeID, seq int64) *infoStore {
	if seq >= is.MaxSeq {
		return nil
	}

	delta := newInfoStore(is.NodeID, is.NodeAddr)

	// Compute delta of infos.
	if err := is.visitInfos(func(i *info) error {
		if i.isFresh(nodeID, seq) {
			return delta.addInfo(i)
		}
		return nil
	}); err != nil {
		log.Errorf("failed to properly create delta infoStore: %s", err)
	}

	delta.MaxSeq = is.MaxSeq
	return delta
}

// distant returns a nodeSet for gossip peers which originated infos
// with info.Hops > maxHops.
func (is *infoStore) distant(maxHops uint32) *nodeSet {
	ns := newNodeSet(0)
	// will never error because `return nil` below
	_ = is.visitInfos(func(i *info) error {
		if i.Hops > maxHops {
			ns.addNode(i.NodeID)
		}
		return nil
	})
	return ns
}

// leastUseful determines which node ID from amongst the set is
// currently contributing the least. Returns 0 if nodes is empty.
func (is *infoStore) leastUseful(nodes *nodeSet) proto.NodeID {
	contrib := make(map[proto.NodeID]int, nodes.len())
	for node := range nodes.nodes {
		contrib[node] = 0
	}
	// will never error because `return nil` below
	_ = is.visitInfos(func(i *info) error {
		contrib[i.peerID]++
		return nil
	})

	least := math.MaxInt32
	var leastNode proto.NodeID
	for id, count := range contrib {
		if nodes.hasNode(id) {
			if count < least {
				least = count
				leastNode = id
			}
		}
	}
	return leastNode
}
<commit_msg>Fix comment<commit_after>// Copyright 2014 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License. See the AUTHORS file
// for names of contributors.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

package gossip

import (
	"bytes"
	"fmt"
	"math"
	"reflect"
	"regexp"
	"sync"
	"time"

	"github.com/cockroachdb/cockroach/proto"
	"github.com/cockroachdb/cockroach/util"
	"github.com/cockroachdb/cockroach/util/log"
)

// callback holds regexp pattern match and GossipCallback method.
type callback struct {
	pattern *regexp.Regexp
	method  Callback
}

// infoStore objects manage maps of Info objects. They maintain a
// sequence number generator which they use to allocate new info
// objects.
//
// infoStores can be queried for incremental updates occurring since a
// specified sequence number.
//
// infoStores can be combined using deltas from peer nodes.
//
// infoStores are not thread safe.
type infoStore struct {
	Infos     infoMap             `json:"infos,omitempty"` // Map from key to info
	NodeID    proto.NodeID        `json:"-"`               // Owning node's ID
	NodeAddr  util.UnresolvedAddr `json:"-"`               // Address of node owning this info store: "host:port"
	MaxSeq    int64               `json:"-"`               // Maximum sequence number inserted
	seqGen    int64               // Sequence generator incremented each time info is added
	callbacks []callback
}

// monotonicUnixNano returns a monotonically increasing value for
// nanoseconds in Unix time. Since equal times are ignored with
// updates to infos, we're careful to avoid incorrectly ignoring a
// newly created value in the event one is created within the same
// nanosecond. Really unlikely except for the case of unittests, but
// better safe than sorry.
func monotonicUnixNano() int64 {
	monoTimeMu.Lock()
	defer monoTimeMu.Unlock()

	now := time.Now().UnixNano()
	if now <= lastTime {
		now = lastTime + 1
	}
	lastTime = now
	return now
}

// String returns a string representation of an infostore.
func (is *infoStore) String() string {
	buf := bytes.Buffer{}
	if infoCount := len(is.Infos); infoCount > 0 {
		buf.WriteString(fmt.Sprintf("infostore with %d info(s): ", infoCount))
	} else {
		return "infostore (empty)"
	}

	prepend := ""

	if err := is.visitInfos(func(i *info) error {
		str := fmt.Sprintf("%sinfo %q: %+v", prepend, i.Key, i.Val)
		prepend = ", "
		_, err := buf.WriteString(str)
		return err
	}); err != nil {
		log.Errorf("failed to properly construct string representation of infoStore: %s", err)
	}
	return buf.String()
}

var (
	monoTimeMu sync.Mutex
	lastTime   int64
)

// newInfoStore allocates and returns a new infoStore.
// "NodeAddr" is the address of the node owning the infostore.
func newInfoStore(nodeID proto.NodeID, nodeAddr util.UnresolvedAddr) *infoStore {
	return &infoStore{
		Infos:    infoMap{},
		NodeID:   nodeID,
		NodeAddr: nodeAddr,
	}
}

// newInfo allocates and returns a new info object using specified key,
// value, and time-to-live.
func (is *infoStore) newInfo(key string, val interface{}, ttl time.Duration) *info {
	is.seqGen++
	now := monotonicUnixNano()
	ttlStamp := now + int64(ttl)
	if ttl == 0 {
		ttlStamp = math.MaxInt64
	}
	return &info{
		Key:       key,
		Val:       val,
		Timestamp: now,
		TTLStamp:  ttlStamp,
		NodeID:    is.NodeID,
		peerID:    is.NodeID,
		seq:       is.seqGen,
	}
}

// getInfo returns an info object by key or nil if it doesn't exist.
func (is *infoStore) getInfo(key string) *info {
	if info, ok := is.Infos[key]; ok {
		// Check TTL and discard if too old.
		if info.expired(time.Now().UnixNano()) {
			delete(is.Infos, key)
			return nil
		}
		return info
	}
	return nil
}

// addInfo adds or updates an info in the infos map.
//
// Returns nil if info was added; error otherwise.
func (is *infoStore) addInfo(i *info) error {
	// Only replace an existing info if new timestamp is greater, or if
	// timestamps are equal, but new hops is smaller.
	var contentsChanged bool
	if existingInfo, ok := is.Infos[i.Key]; ok {
		if i.Timestamp < existingInfo.Timestamp ||
			(i.Timestamp == existingInfo.Timestamp && i.Hops >= existingInfo.Hops) {
			return util.Errorf("info %+v older than current info %+v", i, existingInfo)
		}
		contentsChanged = !reflect.DeepEqual(existingInfo.Val, i.Val)
	} else {
		// No preexisting info means contentsChanged is true.
		contentsChanged = true
	}
	// Update info map.
	is.Infos[i.Key] = i
	if i.seq > is.MaxSeq {
		is.MaxSeq = i.seq
	}
	is.processCallbacks(i.Key, contentsChanged, i.Val)
	return nil
}

// maxHops returns the maximum hops across all infos in the store.
// This is the maximum number of gossip exchanges between any
// originator and this node.
func (is *infoStore) maxHops() uint32 {
	var maxHops uint32
	// will never error because `return nil` below
	_ = is.visitInfos(func(i *info) error {
		if i.Hops > maxHops {
			maxHops = i.Hops
		}
		return nil
	})
	return maxHops
}

// registerCallback compiles a regexp for pattern and adds it to
// the callbacks slice.
func (is *infoStore) registerCallback(pattern string, method Callback) {
	re := regexp.MustCompile(pattern)
	is.callbacks = append(is.callbacks, callback{pattern: re, method: method})
	var infos []*info
	if err := is.visitInfos(func(i *info) error {
		if re.MatchString(i.Key) {
			infos = append(infos, i)
		}
		return nil
	}); err != nil {
		log.Errorf("failed to properly run registered callback while visiting pre-existing info: %s", err)
	}
	// Run callbacks in a goroutine to avoid mutex reentry.
	go func() {
		for _, i := range infos {
			method(i.Key, true /* contentsChanged */, i.Val)
		}
	}()
}

// processCallbacks processes callbacks for the specified key by
// matching callback regular expression against the key and invoking
// the corresponding callback method on a match.
func (is *infoStore) processCallbacks(key string, contentsChanged bool, content interface{}) {
	var matches []callback
	for _, cb := range is.callbacks {
		if cb.pattern.MatchString(key) {
			matches = append(matches, cb)
		}
	}
	// Run callbacks in a goroutine to avoid mutex reentry.
	go func() {
		for _, cb := range matches {
			cb.method(key, contentsChanged, content)
		}
	}()
}

// visitInfos implements a visitor pattern to run the visitInfo
// function against each info in turn. Be sure to skip over any expired
// infos.
func (is *infoStore) visitInfos(visitInfo func(*info) error) error {
	now := time.Now().UnixNano()

	if visitInfo != nil {
		for _, i := range is.Infos {
			if i.expired(now) {
				delete(is.Infos, i.Key)
				continue
			}
			if err := visitInfo(i); err != nil {
				return err
			}
		}
	}

	return nil
}

// combine combines an incremental delta with the current infoStore.
// The sequence numbers on all info objects are reset using the info
// store's sequence generator. All hop distances on infos are
// incremented to indicate they've arrived from an external source.
// Returns the count of "fresh" infos in the provided delta.
func (is *infoStore) combine(delta *infoStore) int {
	var freshCount int
	if err := delta.visitInfos(func(i *info) error {
		is.seqGen++
		i.seq = is.seqGen
		i.Hops++
		i.peerID = delta.NodeID
		// Errors from addInfo here are not a problem; they simply
		// indicate that the data in *is is newer than in *delta.
		if err := is.addInfo(i); err == nil {
			freshCount++
		}
		return nil
	}); err != nil {
		log.Errorf("failed to properly combine infoStores: %s", err)
	}
	return freshCount
}

// delta returns an incremental delta of infos added to the info store
// since (not including) the specified sequence number. These deltas
// are intended for efficiently updating peer nodes. Any infos passed
// from node requesting delta are ignored.
//
// Returns nil if there are no deltas.
func (is *infoStore) delta(nodeID proto.NodeID, seq int64) *infoStore {
	if seq >= is.MaxSeq {
		return nil
	}

	delta := newInfoStore(is.NodeID, is.NodeAddr)

	// Compute delta of infos.
	if err := is.visitInfos(func(i *info) error {
		if i.isFresh(nodeID, seq) {
			return delta.addInfo(i)
		}
		return nil
	}); err != nil {
		log.Errorf("failed to properly create delta infoStore: %s", err)
	}

	delta.MaxSeq = is.MaxSeq
	return delta
}

// distant returns a nodeSet for gossip peers which originated infos
// with info.Hops > maxHops.
func (is *infoStore) distant(maxHops uint32) *nodeSet {
	ns := newNodeSet(0)
	// will never error because `return nil` below
	_ = is.visitInfos(func(i *info) error {
		if i.Hops > maxHops {
			ns.addNode(i.NodeID)
		}
		return nil
	})
	return ns
}

// leastUseful determines which node ID from amongst the set is
// currently contributing the least. Returns 0 if nodes is empty.
func (is *infoStore) leastUseful(nodes *nodeSet) proto.NodeID {
	contrib := make(map[proto.NodeID]int, nodes.len())
	for node := range nodes.nodes {
		contrib[node] = 0
	}
	// will never error because `return nil` below
	_ = is.visitInfos(func(i *info) error {
		contrib[i.peerID]++
		return nil
	})

	least := math.MaxInt32
	var leastNode proto.NodeID
	for id, count := range contrib {
		if nodes.hasNode(id) {
			if count < least {
				least = count
				leastNode = id
			}
		}
	}
	return leastNode
}
<|endoftext|>


<commit_before>// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package gcsblob provides an implementation of using blob API on GCS.
// It is a wrapper around GCS client library.
package gcsblob

import (
	"context"
	"errors"
	"fmt"
	"regexp"
	"unicode/utf8"

	"cloud.google.com/go/storage"
	"github.com/google/go-cloud/blob/driver"
	"github.com/google/go-cloud/gcp"
	"google.golang.org/api/googleapi"
	"google.golang.org/api/option"
)

var _ driver.Bucket = (*Bucket)(nil)

// New returns a GCS Bucket. It handles creation of a client used to communicate
// to GCS service.
func New(ctx context.Context, bucketName string, opts *BucketOptions) (*Bucket, error) {
	if err := validateBucketChar(bucketName); err != nil {
		return nil, err
	}
	var o []option.ClientOption
	if opts != nil {
		o = append(o, option.WithTokenSource(opts.TokenSource))
	}
	c, err := storage.NewClient(ctx, o...)
	if err != nil {
		return nil, err
	}
	return &Bucket{name: bucketName, client: c}, nil
}

// Bucket represents a GCS bucket, which handles read, write and delete operations
// on objects within it.
type Bucket struct {
	name   string
	client *storage.Client
}

// BucketOptions provides information settings during bucket initialization.
type BucketOptions struct {
	TokenSource gcp.TokenSource
}

// NewRangeReader returns a Reader that reads part of an object, reading at most
// length bytes starting at the given offset. If length is 0, it will read only
// the metadata. If length is negative, it will read till the end of the object.
func (b *Bucket) NewRangeReader(ctx context.Context, key string, offset, length int64) (driver.Reader, error) {
	bkt := b.client.Bucket(b.name)
	obj := bkt.Object(key)
	return obj.NewRangeReader(ctx, offset, length)
}

// NewWriter returns Writer that writes to an object associated with key.
//
// A new object will be created unless an object with this key already exists.
// Otherwise any previous object with the same name will be replaced.
// The object will not be available (and any previous object will remain)
// until Close has been called.
//
// A WriterOptions can be given to change the default behavior of the Writer.
//
// The caller must call Close on the returned Writer when done writing.
func (b *Bucket) NewWriter(ctx context.Context, key string, opts *driver.WriterOptions) (driver.Writer, error) {
	if err := validateObjectChar(key); err != nil {
		return nil, err
	}
	bkt := b.client.Bucket(b.name)
	obj := bkt.Object(key)
	w := obj.NewWriter(ctx)
	if opts != nil {
		w.ChunkSize = bufferSize(opts.BufferSize)
	}
	return w, nil
}

// Delete deletes the object associated with key. It is a no-op if that object
// does not exist.
func (b *Bucket) Delete(ctx context.Context, key string) error {
	bkt := b.client.Bucket(b.name)
	obj := bkt.Object(key)
	return obj.Delete(ctx)
}

const namingRuleURL = "https://cloud.google.com/storage/docs/naming"

// validateBucketChar checks whether character set and length meet the general requirement
// of bucket naming rule. See https://cloud.google.com/storage/docs/naming for
// the full requirements and best practice.
func validateBucketChar(name string) error {
	v := regexp.MustCompile(`^[a-z0-9][a-z0-9-_.]{1,220}[a-z0-9]$`)
	if !v.MatchString(name) {
		return fmt.Errorf("invalid bucket name, see %s for detailed requirements", namingRuleURL)
	}
	return nil
}

// validateObjectChar checks whether name is a valid UTF-8 encoded string, and its
// length is between 1-1024 bytes. See https://cloud.google.com/storage/docs/naming
// for the full requirements and best practice.
func validateObjectChar(name string) error {
	if name == "" {
		return errors.New("object name is empty")
	}
	if !utf8.ValidString(name) {
		return fmt.Errorf("object name is not valid UTF-8, see %s for detailed requirements", namingRuleURL)
	}
	if len(name) > 1024 {
		return fmt.Errorf("object name is longer than 1024 bytes, see %s for detailed requirements", namingRuleURL)
	}
	return nil
}

func bufferSize(size int) int {
	if size == 0 {
		return googleapi.DefaultUploadChunkSize
	} else if size > 0 {
		return size
	}
	return 0 // disable buffering
}
<commit_msg>blob/gcsblob: reduce surface to NewBucket<commit_after>// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package gcsblob provides an implementation of using blob API on GCS.
// It is a wrapper around GCS client library.
package gcsblob

import (
	"context"
	"errors"
	"fmt"
	"regexp"
	"unicode/utf8"

	"github.com/google/go-cloud/blob"
	"github.com/google/go-cloud/blob/driver"
	"github.com/google/go-cloud/gcp"

	"cloud.google.com/go/storage"
	"google.golang.org/api/googleapi"
	"google.golang.org/api/option"
)

// New returns a GCS Bucket. It handles creation of a client used to communicate
// to GCS service.
func New(ctx context.Context, bucketName string, opts *BucketOptions) (*blob.Bucket, error) {
	if err := validateBucketChar(bucketName); err != nil {
		return nil, err
	}
	var o []option.ClientOption
	if opts != nil {
		o = append(o, option.WithTokenSource(opts.TokenSource))
	}
	c, err := storage.NewClient(ctx, o...)
	if err != nil {
		return nil, err
	}
	return blob.NewBucket(&bucket{name: bucketName, client: c}), nil
}

// bucket represents a GCS bucket, which handles read, write and delete operations
// on objects within it.
type bucket struct {
	name   string
	client *storage.Client
}

// BucketOptions provides information settings during bucket initialization.
type BucketOptions struct {
	TokenSource gcp.TokenSource
}

// NewRangeReader returns a Reader that reads part of an object, reading at most
// length bytes starting at the given offset. If length is 0, it will read only
// the metadata. If length is negative, it will read till the end of the object.
func (b *bucket) NewRangeReader(ctx context.Context, key string, offset, length int64) (driver.Reader, error) {
	bkt := b.client.Bucket(b.name)
	obj := bkt.Object(key)
	return obj.NewRangeReader(ctx, offset, length)
}

// NewWriter returns Writer that writes to an object associated with key.
//
// A new object will be created unless an object with this key already exists.
// Otherwise any previous object with the same name will be replaced.
// The object will not be available (and any previous object will remain)
// until Close has been called.
//
// A WriterOptions can be given to change the default behavior of the Writer.
//
// The caller must call Close on the returned Writer when done writing.
func (b *bucket) NewWriter(ctx context.Context, key string, opts *driver.WriterOptions) (driver.Writer, error) {
	if err := validateObjectChar(key); err != nil {
		return nil, err
	}
	bkt := b.client.Bucket(b.name)
	obj := bkt.Object(key)
	w := obj.NewWriter(ctx)
	if opts != nil {
		w.ChunkSize = bufferSize(opts.BufferSize)
	}
	return w, nil
}

// Delete deletes the object associated with key. It is a no-op if that object
// does not exist.
func (b *bucket) Delete(ctx context.Context, key string) error {
	bkt := b.client.Bucket(b.name)
	obj := bkt.Object(key)
	return obj.Delete(ctx)
}

const namingRuleURL = "https://cloud.google.com/storage/docs/naming"

// validateBucketChar checks whether character set and length meet the general requirement
// of bucket naming rule. See https://cloud.google.com/storage/docs/naming for
// the full requirements and best practice.
func validateBucketChar(name string) error {
	v := regexp.MustCompile(`^[a-z0-9][a-z0-9-_.]{1,220}[a-z0-9]$`)
	if !v.MatchString(name) {
		return fmt.Errorf("invalid bucket name, see %s for detailed requirements", namingRuleURL)
	}
	return nil
}

// validateObjectChar checks whether name is a valid UTF-8 encoded string, and its
// length is between 1-1024 bytes. See https://cloud.google.com/storage/docs/naming
// for the full requirements and best practice.
func validateObjectChar(name string) error {
	if name == "" {
		return errors.New("object name is empty")
	}
	if !utf8.ValidString(name) {
		return fmt.Errorf("object name is not valid UTF-8, see %s for detailed requirements", namingRuleURL)
	}
	if len(name) > 1024 {
		return fmt.Errorf("object name is longer than 1024 bytes, see %s for detailed requirements", namingRuleURL)
	}
	return nil
}

func bufferSize(size int) int {
	if size == 0 {
		return googleapi.DefaultUploadChunkSize
	} else if size > 0 {
		return size
	}
	return 0 // disable buffering
}
<|endoftext|>


<commit_before>package core

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"chain/core/query"
	"chain/core/query/filter"
	"chain/errors"
	"chain/net/http/httpjson"
	"chain/protocol/bc"
)

// These types enforce the ordering of JSON fields in API output.
type (
	txinResp struct {
		Action          interface{} `json:"action"`
		AssetID         interface{} `json:"asset_id"`
		AssetAlias      interface{} `json:"asset_alias"`
		AssetTags       interface{} `json:"asset_tags"`
		Amount          interface{} `json:"amount"`
		IssuanceProgram interface{} `json:"issuance_program"`
		SpentOutput     interface{} `json:"spent_output"`
		AccountID       interface{} `json:"account_id"`
		AccountAlias    interface{} `json:"account_alias"`
		AccountTags     interface{} `json:"account_tags"`
		ReferenceData   interface{} `json:"reference_data"`
		AssetDefinition interface{} `json:"asset_definition"`
	}
	txoutResp struct {
		Action         interface{} `json:"action"`
		Position       interface{} `json:"position"`
		AssetID        interface{} `json:"asset_id"`
		AssetAlias     interface{} `json:"asset_alias"`
		AssetTags      interface{} `json:"asset_tags"`
		Amount         interface{} `json:"amount"`
		AccountID      interface{} `json:"account_id"`
		AccountAlias   interface{} `json:"account_alias"`
		AccountTags    interface{} `json:"account_tags"`
		ControlProgram interface{} `json:"control_program"`
		ReferenceData  interface{} `json:"reference_data"`
	}
	txResp struct {
		ID            interface{} `json:"id"`
		Timestamp     interface{} `json:"timestamp"`
		BlockID       interface{} `json:"block_id"`
		BlockHeight   interface{} `json:"block_height"`
		Position      interface{} `json:"position"`
		ReferenceData interface{} `json:"reference_data"`
		Inputs        interface{} `json:"inputs"`
		Outputs       interface{} `json:"outputs"`
	}
)

// listTransactions is an http handler for listing transactions matching
// an index or an ad-hoc filter.
//
// POST /list-transactions
func (a *api) listTransactions(ctx context.Context, in requestQuery) (result page, err error) {
	var c context.CancelFunc
	timeout := in.Timeout.Duration
	if timeout != 0 {
		ctx, c = context.WithTimeout(ctx, timeout)
		defer c()
	}

	if in.EndTimeMS == 0 {
		in.EndTimeMS = bc.Millis(time.Now())
	}

	var (
		p     filter.Predicate
		after query.TxAfter
	)

	// Build the filter predicate.
	p, err = filter.Parse(in.Filter)
	if err != nil {
		return result, err
	}

	// Either parse the provided `after` or look one up for the time range.
	if in.After != "" {
		after, err = query.DecodeTxAfter(in.After)
		if err != nil {
			return result, errors.Wrap(err, "decoding `after`")
		}
	} else {
		after, err = a.indexer.LookupTxAfter(ctx, in.StartTimeMS, in.EndTimeMS)
		if err != nil {
			return result, err
		}
	}

	var asc bool
	if in.Order == "asc" {
		asc = true
	}

	limit := defGenericPageSize
	txns, nextAfter, err := a.indexer.Transactions(ctx, p, in.FilterParams, after, limit, asc)
	if err != nil {
		return result, errors.Wrap(err, "running tx query")
	}

	resp := make([]*txResp, 0, len(txns))
	for _, t := range txns {
		tjson, ok := t.(*json.RawMessage)
		if !ok {
			return result, fmt.Errorf("unexpected type %T in Indexer.Transactions output", t)
		}
		if tjson == nil {
			return result, fmt.Errorf("unexpected nil in Indexer.Transactions output")
		}
		var tx map[string]interface{}
		err = json.Unmarshal(*tjson, &tx)
		if err != nil {
			return result, errors.Wrap(err, "decoding Indexer.Transactions output")
		}

		inp, ok := tx["inputs"].([]interface{})
		if !ok {
			return result, fmt.Errorf("unexpected type %T for inputs in Indexer.Transactions output", tx["inputs"])
		}

		var inputs []map[string]interface{}
		for i, in := range inp {
			input, ok := in.(map[string]interface{})
			if !ok {
				return result, fmt.Errorf("unexpected type %T for input %d in Indexer.Transactions output", in, i)
			}
			inputs = append(inputs, input)
		}

		outp, ok := tx["outputs"].([]interface{})
		if !ok {
			return result, fmt.Errorf("unexpected type %T for outputs in Indexer.Transactions output", tx["outputs"])
		}

		var outputs []map[string]interface{}
		for i, out := range outp {
			output, ok := out.(map[string]interface{})
			if !ok {
				return result, fmt.Errorf("unexpected type %T for output %d in Indexer.Transactions output", out, i)
			}
			outputs = append(outputs, output)
		}

		inResps := make([]*txinResp, 0, len(inputs))
		for _, in := range inputs {
			r := &txinResp{
				Action:          in["action"],
				AssetID:         in["asset_id"],
				AssetAlias:      in["asset_alias"],
				AssetTags:       in["asset_tags"],
				Amount:          in["amount"],
				IssuanceProgram: in["issuance_program"],
				SpentOutput:     in["spent_output"],
				AccountID:       in["account_id"],
				AccountAlias:    in["account_alias"],
				AccountTags:     in["account_tags"],
				ReferenceData:   in["reference_data"],
				AssetDefinition: in["asset_definition"],
			}
			inResps = append(inResps, r)
		}
		outResps := make([]*txoutResp, 0, len(outputs))
		for _, out := range outputs {
			r := &txoutResp{
				Action:         out["action"],
				Position:       out["position"],
				AssetID:        out["asset_id"],
				AssetAlias:     out["asset_alias"],
				AssetTags:      out["asset_tags"],
				Amount:         out["amount"],
				AccountID:      out["account_id"],
				AccountAlias:   out["account_alias"],
				AccountTags:    out["account_tags"],
				ControlProgram: out["control_program"],
				ReferenceData:  out["reference_data"],
			}
			outResps = append(outResps, r)
		}
		r := &txResp{
			ID:            tx["id"],
			Timestamp:     tx["timestamp"],
			BlockID:       tx["block_id"],
			BlockHeight:   tx["block_height"],
			Position:      tx["position"],
			ReferenceData: tx["reference_data"],
			Inputs:        inResps,
			Outputs:       outResps,
		}
		resp = append(resp, r)
	}

	out := in
	out.After = nextAfter.String()
	return page{
		Items:    httpjson.Array(resp),
		LastPage: len(resp) < limit,
		Next:     out,
	}, nil
}

// listAccounts is an http handler for listing accounts matching
// an index or an ad-hoc filter.
//
// POST /list-accounts
func (a *api) listAccounts(ctx context.Context, in requestQuery) (page, error) {
	limit := defGenericPageSize

	// Build the filter predicate.
	p, err := filter.Parse(in.Filter)
	if err != nil {
		return page{}, errors.Wrap(err, "parsing acc query")
	}
	after := in.After

	// Use the filter engine for querying account tags.
	accounts, after, err := a.indexer.Accounts(ctx, p, in.FilterParams, after, limit)
	if err != nil {
		return page{}, errors.Wrap(err, "running acc query")
	}

	result := make([]*accountResponse, 0, len(accounts))
	for _, a := range accounts {
		var orderedKeys []accountKey
		keys, ok := a["keys"].([]interface{})
		if ok {
			for _, key := range keys {
				mapKey, ok := key.(map[string]interface{})
				if !ok {
					continue
				}
				orderedKeys = append(orderedKeys, accountKey{
					RootXPub:              mapKey["root_xpub"],
					AccountXPub:           mapKey["account_xpub"],
					AccountDerivationPath: mapKey["account_derivation_path"],
				})
			}
		}
		r := &accountResponse{
			ID:     a["id"],
			Alias:  a["alias"],
			Keys:   orderedKeys,
			Quorum: a["quorum"],
			Tags:   a["tags"],
		}
		result = append(result, r)
	}

	// Pull in the accounts by the IDs
	out := in
	out.After = after
	return page{
		Items:    httpjson.Array(result),
		LastPage: len(result) < limit,
		Next:     out,
	}, nil
}

// POST /list-balances
func (a *api) listBalances(ctx context.Context, in requestQuery) (result page, err error) {
	if in.TimestampMS == 0 {
		in.TimestampMS = bc.Millis(time.Now())
	}

	var p filter.Predicate
	var sumBy []filter.Field
	p, err = filter.Parse(in.Filter)
	if err != nil {
		return result, err
	}

	for _, field := range in.SumBy {
		f, err := filter.ParseField(field)
		if err != nil {
			return result, err
		}
		sumBy = append(sumBy, f)
	}

	// TODO(jackson): paginate this endpoint.
	balances, err := a.indexer.Balances(ctx, p, in.FilterParams, sumBy, in.TimestampMS)
	if err != nil {
		return result, err
	}

	result.Items = httpjson.Array(balances)
	result.LastPage = true
	result.Next = in
	return result, nil
}

// This type enforces the ordering of JSON fields in API output.
type utxoResp struct {
	Action         interface{} `json:"action"`
	TransactionID  interface{} `json:"transaction_id"`
	Position       interface{} `json:"position"`
	AssetID        interface{} `json:"asset_id"`
	AssetAlias     interface{} `json:"asset_alias"`
	AssetTags      interface{} `json:"asset_tags"`
	Amount         interface{} `json:"amount"`
	AccountID      interface{} `json:"account_id"`
	AccountAlias   interface{} `json:"account_alias"`
	AccountTags    interface{} `json:"account_tags"`
	ControlProgram interface{} `json:"control_program"`
	ReferenceData  interface{} `json:"reference_data"`
}

// POST /list-unspent-outputs
func (a *api) listUnspentOutputs(ctx context.Context, in requestQuery) (result page, err error) {
	if in.TimestampMS == 0 {
		in.TimestampMS = bc.Millis(time.Now())
	}
	var p filter.Predicate
	p, err = filter.Parse(in.Filter)
	if err != nil {
		return result, err
	}

	var after *query.OutputsAfter
	if in.After != "" {
		after, err = query.DecodeOutputsAfter(in.After)
		if err != nil {
			return result, errors.Wrap(err, "decoding `after`")
		}
	}

	limit := defGenericPageSize
	outputs, nextAfter, err := a.indexer.Outputs(ctx, p, in.FilterParams, in.TimestampMS, after, limit)
	if err != nil {
		return result, errors.Wrap(err, "querying outputs")
	}

	resp := make([]*utxoResp, 0, len(outputs))
	for _, o := range outputs {
		ojson, ok := o.(*json.RawMessage)
		if !ok {
			return result, fmt.Errorf("unexpected type %T in Indexer.Outputs output", o)
		}
		if ojson == nil {
			return result, fmt.Errorf("unexpected nil in Indexer.Outputs output")
		}
		var out map[string]interface{}
		err = json.Unmarshal(*ojson, &out)
		if err != nil {
			return result, errors.Wrap(err, "decoding Indexer.Outputs output")
		}
		r := &utxoResp{
			Action:         out["action"],
			TransactionID:  out["transaction_id"],
			Position:       out["position"],
			AssetID:        out["asset_id"],
			AssetAlias:     out["asset_alias"],
			AssetTags:      out["asset_tags"],
			Amount:         out["amount"],
			AccountID:      out["account_id"],
			AccountAlias:   out["account_alias"],
			AccountTags:    out["account_tags"],
			ControlProgram: out["control_program"],
			ReferenceData:  out["reference_data"],
		}
		resp = append(resp, r)
	}

	outQuery := in
	outQuery.After = nextAfter.String()
	return page{
		Items:    resp,
		LastPage: len(resp) < limit,
		Next:     outQuery,
	}, nil
}

// listAssets is an http handler for listing assets matching
// an index or an ad-hoc filter.
//
// POST /list-assets
func (a *api) listAssets(ctx context.Context, in requestQuery) (page, error) {
	limit := defGenericPageSize

	// Build the filter predicate.
	p, err := filter.Parse(in.Filter)
	if err != nil {
		return page{}, err
	}
	after := in.After

	// Use the query engine for querying asset tags.
	var assets []map[string]interface{}
	assets, after, err = a.indexer.Assets(ctx, p, in.FilterParams, after, limit)
	if err != nil {
		return page{}, errors.Wrap(err, "running asset query")
	}

	result := make([]*assetResponse, 0, len(assets))
	for _, a := range assets {
		var orderedKeys []assetKey
		keys, ok := a["keys"].([]interface{})
		if ok {
			for _, key := range keys {
				mapKey, ok := key.(map[string]interface{})
				if !ok {
					continue
				}
				orderedKeys = append(orderedKeys, assetKey{
					AssetPubkey:         mapKey["asset_pubkey"],
					RootXPub:            mapKey["root_xpub"],
					AssetDerivationPath: mapKey["asset_derivation_path"],
				})
			}
		}
		r := &assetResponse{
			ID:              a["id"],
			IssuanceProgram: a["issuance_program"],
			Keys:            orderedKeys,
			Quorum:          a["quorum"],
			Definition:      a["definition"],
			Tags:            a["tags"],
			Origin:          a["origin"],
		}
		if alias, ok := a["alias"].(string); ok && alias != "" {
			r.Alias = &alias
		}
		result = append(result, r)
	}

	out := in
	out.After = after
	return page{
		Items:    httpjson.Array(result),
		LastPage: len(result) < limit,
		Next:     out,
	}, nil
}
<commit_msg>core: add asset_origin to transaction response object<commit_after>package core

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"chain/core/query"
	"chain/core/query/filter"
	"chain/errors"
	"chain/net/http/httpjson"
	"chain/protocol/bc"
)

// These types enforce the ordering of JSON fields in API output.
type (
	txinResp struct {
		Action          interface{} `json:"action"`
		AssetID         interface{} `json:"asset_id"`
		AssetAlias      interface{} `json:"asset_alias"`
		AssetTags       interface{} `json:"asset_tags"`
		AssetOrigin     interface{} `json:"asset_origin"`
		Amount          interface{} `json:"amount"`
		IssuanceProgram interface{} `json:"issuance_program"`
		SpentOutput     interface{} `json:"spent_output"`
		AccountID       interface{} `json:"account_id"`
		AccountAlias    interface{} `json:"account_alias"`
		AccountTags     interface{} `json:"account_tags"`
		ReferenceData   interface{} `json:"reference_data"`
		AssetDefinition interface{} `json:"asset_definition"`
	}
	txoutResp struct {
		Action         interface{} `json:"action"`
		Position       interface{} `json:"position"`
		AssetID        interface{} `json:"asset_id"`
		AssetAlias     interface{} `json:"asset_alias"`
		AssetTags      interface{} `json:"asset_tags"`
		AssetOrigin    interface{} `json:"asset_origin"`
		Amount         interface{} `json:"amount"`
		AccountID      interface{} `json:"account_id"`
		AccountAlias   interface{} `json:"account_alias"`
		AccountTags    interface{} `json:"account_tags"`
		ControlProgram interface{} `json:"control_program"`
		ReferenceData  interface{} `json:"reference_data"`
	}
	txResp struct {
		ID            interface{} `json:"id"`
		Timestamp     interface{} `json:"timestamp"`
		BlockID       interface{} `json:"block_id"`
		BlockHeight   interface{} `json:"block_height"`
		Position      interface{} `json:"position"`
		ReferenceData interface{} `json:"reference_data"`
		Inputs        interface{} `json:"inputs"`
		Outputs       interface{} `json:"outputs"`
	}
)

// listTransactions is an http handler for listing transactions matching
// an index or an ad-hoc filter.
//
// POST /list-transactions
func (a *api) listTransactions(ctx context.Context, in requestQuery) (result page, err error) {
	var c context.CancelFunc
	timeout := in.Timeout.Duration
	if timeout != 0 {
		ctx, c = context.WithTimeout(ctx, timeout)
		defer c()
	}

	if in.EndTimeMS == 0 {
		in.EndTimeMS = bc.Millis(time.Now())
	}

	var (
		p     filter.Predicate
		after query.TxAfter
	)

	// Build the filter predicate.
	p, err = filter.Parse(in.Filter)
	if err != nil {
		return result, err
	}

	// Either parse the provided `after` or look one up for the time range.
	if in.After != "" {
		after, err = query.DecodeTxAfter(in.After)
		if err != nil {
			return result, errors.Wrap(err, "decoding `after`")
		}
	} else {
		after, err = a.indexer.LookupTxAfter(ctx, in.StartTimeMS, in.EndTimeMS)
		if err != nil {
			return result, err
		}
	}

	var asc bool
	if in.Order == "asc" {
		asc = true
	}

	limit := defGenericPageSize
	txns, nextAfter, err := a.indexer.Transactions(ctx, p, in.FilterParams, after, limit, asc)
	if err != nil {
		return result, errors.Wrap(err, "running tx query")
	}

	resp := make([]*txResp, 0, len(txns))
	for _, t := range txns {
		tjson, ok := t.(*json.RawMessage)
		if !ok {
			return result, fmt.Errorf("unexpected type %T in Indexer.Transactions output", t)
		}
		if tjson == nil {
			return result, fmt.Errorf("unexpected nil in Indexer.Transactions output")
		}
		var tx map[string]interface{}
		err = json.Unmarshal(*tjson, &tx)
		if err != nil {
			return result, errors.Wrap(err, "decoding Indexer.Transactions output")
		}

		inp, ok := tx["inputs"].([]interface{})
		if !ok {
			return result, fmt.Errorf("unexpected type %T for inputs in Indexer.Transactions output", tx["inputs"])
		}

		var inputs []map[string]interface{}
		for i, in := range inp {
			input, ok := in.(map[string]interface{})
			if !ok {
				return result, fmt.Errorf("unexpected type %T for input %d in Indexer.Transactions output", in, i)
			}
			inputs = append(inputs, input)
		}

		outp, ok := tx["outputs"].([]interface{})
		if !ok {
			return result, fmt.Errorf("unexpected type %T for outputs in Indexer.Transactions output", tx["outputs"])
		}

		var outputs []map[string]interface{}
		for i, out := range outp {
			output, ok := out.(map[string]interface{})
			if !ok {
				return result, fmt.Errorf("unexpected type %T for output %d in Indexer.Transactions output", out, i)
			}
			outputs = append(outputs, output)
		}

		inResps := make([]*txinResp, 0, len(inputs))
		for _, in := range inputs {
			r := &txinResp{
				Action:          in["action"],
				AssetID:         in["asset_id"],
				AssetAlias:      in["asset_alias"],
				AssetTags:       in["asset_tags"],
				AssetOrigin:     in["asset_origin"],
				Amount:          in["amount"],
				IssuanceProgram: in["issuance_program"],
				SpentOutput:     in["spent_output"],
				AccountID:       in["account_id"],
				AccountAlias:    in["account_alias"],
				AccountTags:     in["account_tags"],
				ReferenceData:   in["reference_data"],
				AssetDefinition: in["asset_definition"],
			}
			inResps = append(inResps, r)
		}
		outResps := make([]*txoutResp, 0, len(outputs))
		for _, out := range outputs {
			r := &txoutResp{
				Action:         out["action"],
				Position:       out["position"],
				AssetID:        out["asset_id"],
				AssetAlias:     out["asset_alias"],
				AssetTags:      out["asset_tags"],
				AssetOrigin:    out["asset_origin"],
				Amount:         out["amount"],
				AccountID:      out["account_id"],
				AccountAlias:   out["account_alias"],
				AccountTags:    out["account_tags"],
				ControlProgram: out["control_program"],
				ReferenceData:  out["reference_data"],
			}
			outResps = append(outResps, r)
		}
		r := &txResp{
			ID:            tx["id"],
			Timestamp:     tx["timestamp"],
			BlockID:       tx["block_id"],
			BlockHeight:   tx["block_height"],
			Position:      tx["position"],
			ReferenceData: tx["reference_data"],
			Inputs:        inResps,
			Outputs:       outResps,
		}
		resp = append(resp, r)
	}

	out := in
	out.After = nextAfter.String()
	return page{
		Items:    httpjson.Array(resp),
		LastPage: len(resp) < limit,
		Next:     out,
	}, nil
}

// listAccounts is an http handler for listing accounts matching
// an index or an ad-hoc filter.
//
// POST /list-accounts
func (a *api) listAccounts(ctx context.Context, in requestQuery) (page, error) {
	limit := defGenericPageSize

	// Build the filter predicate.
	p, err := filter.Parse(in.Filter)
	if err != nil {
		return page{}, errors.Wrap(err, "parsing acc query")
	}
	after := in.After

	// Use the filter engine for querying account tags.
	accounts, after, err := a.indexer.Accounts(ctx, p, in.FilterParams, after, limit)
	if err != nil {
		return page{}, errors.Wrap(err, "running acc query")
	}

	result := make([]*accountResponse, 0, len(accounts))
	for _, a := range accounts {
		var orderedKeys []accountKey
		keys, ok := a["keys"].([]interface{})
		if ok {
			for _, key := range keys {
				mapKey, ok := key.(map[string]interface{})
				if !ok {
					continue
				}
				orderedKeys = append(orderedKeys, accountKey{
					RootXPub:              mapKey["root_xpub"],
					AccountXPub:           mapKey["account_xpub"],
					AccountDerivationPath: mapKey["account_derivation_path"],
				})
			}
		}
		r := &accountResponse{
			ID:     a["id"],
			Alias:  a["alias"],
			Keys:   orderedKeys,
			Quorum: a["quorum"],
			Tags:   a["tags"],
		}
		result = append(result, r)
	}

	// Pull in the accounts by the IDs
	out := in
	out.After = after
	return page{
		Items:    httpjson.Array(result),
		LastPage: len(result) < limit,
		Next:     out,
	}, nil
}

// POST /list-balances
func (a *api) listBalances(ctx context.Context, in requestQuery) (result page, err error) {
	if in.TimestampMS == 0 {
		in.TimestampMS = bc.Millis(time.Now())
	}

	var p filter.Predicate
	var sumBy []filter.Field
	p, err = filter.Parse(in.Filter)
	if err != nil {
		return result, err
	}

	for _, field := range in.SumBy {
		f, err := filter.ParseField(field)
		if err != nil {
			return result, err
		}
		sumBy = append(sumBy, f)
	}

	// TODO(jackson): paginate this endpoint.
	balances, err := a.indexer.Balances(ctx, p, in.FilterParams, sumBy, in.TimestampMS)
	if err != nil {
		return result, err
	}

	result.Items = httpjson.Array(balances)
	result.LastPage = true
	result.Next = in
	return result, nil
}

// This type enforces the ordering of JSON fields in API output.
type utxoResp struct {
	Action         interface{} `json:"action"`
	TransactionID  interface{} `json:"transaction_id"`
	Position       interface{} `json:"position"`
	AssetID        interface{} `json:"asset_id"`
	AssetAlias     interface{} `json:"asset_alias"`
	AssetTags      interface{} `json:"asset_tags"`
	AssetOrigin    interface{} `json:"asset_origin"`
	Amount         interface{} `json:"amount"`
	AccountID      interface{} `json:"account_id"`
	AccountAlias   interface{} `json:"account_alias"`
	AccountTags    interface{} `json:"account_tags"`
	ControlProgram interface{} `json:"control_program"`
	ReferenceData  interface{} `json:"reference_data"`
}

// POST /list-unspent-outputs
func (a *api) listUnspentOutputs(ctx context.Context, in requestQuery) (result page, err error) {
	if in.TimestampMS == 0 {
		in.TimestampMS = bc.Millis(time.Now())
	}
	var p filter.Predicate
	p, err = filter.Parse(in.Filter)
	if err != nil {
		return result, err
	}

	var after *query.OutputsAfter
	if in.After != "" {
		after, err = query.DecodeOutputsAfter(in.After)
		if err != nil {
			return result, errors.Wrap(err, "decoding `after`")
		}
	}

	limit := defGenericPageSize
	outputs, nextAfter, err := a.indexer.Outputs(ctx, p, in.FilterParams, in.TimestampMS, after, limit)
	if err != nil {
		return result, errors.Wrap(err, "querying outputs")
	}

	resp := make([]*utxoResp, 0, len(outputs))
	for _, o := range outputs {
		ojson, ok := o.(*json.RawMessage)
		if !ok {
			return result, fmt.Errorf("unexpected type %T in Indexer.Outputs output", o)
		}
		if ojson == nil {
			return result, fmt.Errorf("unexpected nil in Indexer.Outputs output")
		}
		var out map[string]interface{}
		err = json.Unmarshal(*ojson, &out)
		if err != nil {
			return result, errors.Wrap(err, "decoding Indexer.Outputs output")
		}
		r := &utxoResp{
			Action:         out["action"],
			TransactionID:  out["transaction_id"],
			Position:       out["position"],
			AssetID:        out["asset_id"],
			AssetAlias:     out["asset_alias"],
			AssetTags:      out["asset_tags"],
			AssetOrigin:    out["asset_origin"],
			Amount:         out["amount"],
			AccountID:      out["account_id"],
			AccountAlias:   out["account_alias"],
			AccountTags:    out["account_tags"],
			ControlProgram: out["control_program"],
			ReferenceData:  out["reference_data"],
		}
		resp = append(resp, r)
	}

	outQuery := in
	outQuery.After = nextAfter.String()
	return page{
		Items:    resp,
		LastPage: len(resp) < limit,
		Next:     outQuery,
	}, nil
}

// listAssets is an http handler for listing assets matching
// an index or an ad-hoc filter.
//
// POST /list-assets
func (a *api) listAssets(ctx context.Context, in requestQuery) (page, error) {
	limit := defGenericPageSize

	// Build the filter predicate.
	p, err := filter.Parse(in.Filter)
	if err != nil {
		return page{}, err
	}
	after := in.After

	// Use the query engine for querying asset tags.
	var assets []map[string]interface{}
	assets, after, err = a.indexer.Assets(ctx, p, in.FilterParams, after, limit)
	if err != nil {
		return page{}, errors.Wrap(err, "running asset query")
	}

	result := make([]*assetResponse, 0, len(assets))
	for _, a := range assets {
		var orderedKeys []assetKey
		keys, ok := a["keys"].([]interface{})
		if ok {
			for _, key := range keys {
				mapKey, ok := key.(map[string]interface{})
				if !ok {
					continue
				}
				orderedKeys = append(orderedKeys, assetKey{
					AssetPubkey:         mapKey["asset_pubkey"],
					RootXPub:            mapKey["root_xpub"],
					AssetDerivationPath: mapKey["asset_derivation_path"],
				})
			}
		}
		r := &assetResponse{
			ID:              a["id"],
			IssuanceProgram: a["issuance_program"],
			Keys:            orderedKeys,
			Quorum:          a["quorum"],
			Definition:      a["definition"],
			Tags:            a["tags"],
			Origin:          a["origin"],
		}
		if alias, ok := a["alias"].(string); ok && alias != "" {
			r.Alias = &alias
		}
		result = append(result, r)
	}

	out := in
	out.After = after
	return page{
		Items:    httpjson.Array(result),
		LastPage: len(result) < limit,
		Next:     out,
	}, nil
}
<|endoftext|>