<commit_before>package api

import (
	"encoding/json"
	"fmt"
	log "github.com/golang/glog"
	"github.com/jrupac/goliath/auth"
	"github.com/jrupac/goliath/models"
	"github.com/jrupac/goliath/storage"
	"github.com/jrupac/goliath/utils"
	"github.com/prometheus/client_golang/prometheus"
	"net/http"
	"strconv"
	"strings"
	"time"
)

const apiVersion = 3

var (
	feverLatencyMetric = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Name:       "fever_server_latency",
			Help:       "Server-side latency of Fever API operations.",
			Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
		},
		[]string{"method"},
	)
)

type itemType struct {
	ID          int64  `json:"id"`
	FeedID      int64  `json:"feed_id"`
	Title       string `json:"title"`
	Author      string `json:"author"`
	HTML        string `json:"html"`
	URL         string `json:"url"`
	IsSaved     int64  `json:"is_saved"`
	IsRead      int64  `json:"is_read"`
	CreatedTime int64  `json:"created_on_time"`
}

type feedType struct {
	ID          int64  `json:"id"`
	FaviconID   int64  `json:"favicon_id"`
	Title       string `json:"title"`
	URL         string `json:"url"`
	SiteURL     string `json:"site_url"`
	IsSpark     int64  `json:"is_spark"`
	LastUpdated int64  `json:"last_updated_on_time"`
}

type groupType struct {
	ID    int64  `json:"id"`
	Title string `json:"title"`
}

type faviconType struct {
	ID   int64  `json:"id"`
	Data string `json:"data"`
}

type feedsGroupType struct {
	GroupID int64  `json:"group_id"`
	FeedIDs string `json:"feed_ids"`
}

type responseType map[string]interface{}

func init() {
	prometheus.MustRegister(feverLatencyMetric)
}

// Fever is an implementation of the Fever API.
type Fever struct {
}

// FeverHandler returns a new Fever handler.
func FeverHandler(d *storage.Database) http.HandlerFunc {
	return Fever{}.Handler(d)
}

// Handler returns a handler function that implements the Fever API.
func (a Fever) Handler(d *storage.Database) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		a.handle(d, w, r)
	}
}

func (a Fever) recordLatency(t time.Time, label string) {
	utils.Elapsed(t, func(d time.Duration) {
		// Record latency measurements in microseconds.
		freshrssLatencyMetric.WithLabelValues(label).Observe(float64(d) / float64(time.Microsecond))
	})
}

func (a Fever) handle(d *storage.Database, w http.ResponseWriter, r *http.Request) {
	// Record the total server latency of each call.
	defer a.recordLatency(time.Now(), "server")

	// These two fields must always be set on responses.
	resp := responseType{
		"api_version":            apiVersion,
		"last_refreshed_on_time": time.Now().Unix(),
	}

	err := r.ParseForm()
	if err != nil {
		a.returnError(w, "Failed to parse request: %s", err)
		return
	}

	log.Infof("Fever request URL: %s", r.URL.String())
	log.Infof("Fever request body: %s", r.PostForm.Encode())

	switch r.Form.Get("api") {
	case "":
		w.Header().Set("Content-Type", "application/json")
	case "xml":
		// TODO: Implement XML response type.
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	user, authStatus := a.handleAuth(d, r)
	resp["auth"] = authStatus
	if resp["auth"] == 0 {
		a.returnSuccess(w, resp)
		return
	}

	if _, ok := r.Form["groups"]; ok {
		err := a.handleGroups(d, user, &resp)
		if err != nil {
			a.returnError(w, "Failed request 'groups': %s", err)
			return
		}
	}
	if _, ok := r.Form["feeds"]; ok {
		err := a.handleFeeds(d, user, &resp)
		if err != nil {
			a.returnError(w, "Failed request 'feeds': %s", err)
			return
		}
	}
	if _, ok := r.Form["favicons"]; ok {
		err := a.handleFavicons(d, user, &resp)
		if err != nil {
			a.returnError(w, "Failed request 'favicons': %s", err)
			return
		}
	}
	if _, ok := r.Form["items"]; ok {
		err := a.handleItems(d, user, &resp, r)
		if err != nil {
			a.returnError(w, "Failed request 'items': %s", err)
			return
		}
	}
	if _, ok := r.Form["links"]; ok {
		err := a.handleLinks(d, user, &resp)
		if err != nil {
			a.returnError(w, "Failed request 'links': %s", err)
			return
		}
	}
	if _, ok := r.Form["unread_item_ids"]; ok {
		err := a.handleUnreadItemIDs(d, user, &resp)
		if err != nil {
			a.returnError(w, "Failed request 'unread_item_ids': %s", err)
			return
		}
	}
	if _, ok := r.Form["saved_item_ids"]; ok {
		err := a.handleSavedItemIDs(d, user, &resp)
		if err != nil {
			a.returnError(w, "Failed request 'saved_item_ids': %s", err)
			return
		}
	}
	if _, ok := r.Form["mark"]; ok {
		err := a.handleMark(d, user, &resp, r)
		if err != nil {
			a.returnError(w, "Failed request 'mark': %s", err)
			return
		}
	}

	a.returnSuccess(w, resp)
}

func (a Fever) returnError(w http.ResponseWriter, msg string, err error) {
	log.Warningf(msg, err)
	if fe, ok := err.(*apiError); ok {
		if fe.internal {
			w.WriteHeader(http.StatusInternalServerError)
		} else {
			w.WriteHeader(http.StatusBadRequest)
		}
		return
	}
	w.WriteHeader(http.StatusInternalServerError)
}

func (a Fever) returnSuccess(w http.ResponseWriter, resp map[string]interface{}) {
	w.WriteHeader(http.StatusOK)
	enc := json.NewEncoder(w)
	// HTML content is escaped already during fetch time (e.g., ' --> '), so
	// do not HTML escape it further (e.g., ' --> \u0026#39;), which would
	// render incorrectly on a webpage.
	enc.SetEscapeHTML(false)
	if err := enc.Encode(resp); err != nil {
		a.returnError(w, "Failed to encode response JSON: %s", err)
	}
}

func (a Fever) handleAuth(d *storage.Database, r *http.Request) (models.User, int) {
	defer a.recordLatency(time.Now(), "auth")

	// A request can be authenticated by cookie or api key in request.
	if user, err := auth.VerifyCookie(d, r); err == nil {
		log.V(2).Infof("Verified cookie: %+v", r)
		return user, 1
	} else if user, err := d.GetUserByKey(r.FormValue("api_key")); err != nil {
		utils.HttpRequestPrint("Received unauthenticated request", r)
		log.Warningf("Failed because: %s", err)
		return user, 0
	} else {
		log.V(2).Infof("Successfully authenticated by key: %+v", r)
		return user, 1
	}
}

func (a Fever) handleGroups(d *storage.Database, u models.User, resp *responseType) error {
	defer a.recordLatency(time.Now(), "groups")

	folders, err := d.GetAllFoldersForUser(u)
	if err != nil {
		return &apiError{err, true}
	}
	var groups []groupType
	for _, f := range folders {
		g := groupType{
			ID:    f.ID,
			Title: f.Name,
		}
		groups = append(groups, g)
	}
	(*resp)["groups"] = groups
	(*resp)["feeds_groups"], err = a.constructFeedsGroups(d, u)
	if err != nil {
		return &apiError{err, true}
	}
	return nil
}

func (a Fever) handleFeeds(d *storage.Database, u models.User, resp *responseType) error {
	defer a.recordLatency(time.Now(), "feeds")

	fetchedFeeds, err := d.GetAllFeedsForUser(u)
	if err != nil {
		return &apiError{err, true}
	}
	var feeds []feedType
	for _, ff := range fetchedFeeds {
		f := feedType{
			ID:          ff.ID,
			FaviconID:   ff.ID,
			Title:       ff.Title,
			URL:         ff.URL,
			SiteURL:     ff.Link,
			IsSpark:     0,
			LastUpdated: time.Now().Unix(),
		}
		feeds = append(feeds, f)
	}
	(*resp)["feeds"] = feeds
	(*resp)["feeds_groups"], err = a.constructFeedsGroups(d, u)
	if err != nil {
		return &apiError{err, true}
	}
	return nil
}

func (a Fever) handleFavicons(d *storage.Database, u models.User, resp *responseType) error {
	faviconMap, err := d.GetAllFaviconsForUser(u)
	if err != nil {
		return &apiError{err, true}
	}
	var favicons []faviconType
	for k, v := range faviconMap {
		f := faviconType{
			ID:   k,
			Data: v,
		}
		favicons = append(favicons, f)
	}
	(*resp)["favicons"] = favicons
	return nil
}

func (a Fever) handleItems(d *storage.Database, u models.User, resp *responseType, r *http.Request) error {
	defer a.recordLatency(time.Now(), "items")

	// TODO: support "max_id" and "with_ids".
	sinceID := int64(-1)
	var err error

	if _, ok := r.Form["since_id"]; ok {
		sinceID, err = strconv.ParseInt(r.FormValue("since_id"), 10, 64)
		if err != nil {
			return &apiError{err, false}
		}
	}

	articles, err := d.GetUnreadArticlesForUser(u, 50, sinceID)
	if err != nil {
		return &apiError{err, true}
	}
	// Make an empty (not nil) slice because their JSON encodings are different.
	items := make([]itemType, 0)
	var content string
	for _, a := range articles {
		if *serveParsedArticles && a.Parsed != "" {
			log.Infof("Serving parsed content for title: %s", a.Title)
			content = a.Parsed
		} else if a.Content != "" {
			// The "content" field usually has more text, but is not always set.
			content = a.Content
		} else {
			content = a.Summary
		}

		i := itemType{
			ID:          a.ID,
			FeedID:      a.FeedID,
			Title:       a.Title,
			Author:      "",
			HTML:        content,
			URL:         a.Link,
			IsSaved:     0,
			IsRead:      0,
			CreatedTime: a.Date.Unix(),
		}
		items = append(items, i)
	}
	(*resp)["items"] = items
	return nil
}

func (a Fever) handleLinks(_ *storage.Database, _ models.User, resp *responseType) error {
	defer a.recordLatency(time.Now(), "links")

	// Perhaps add support for links in the future.
	(*resp)["links"] = ""
	return nil
}

func (a Fever) handleUnreadItemIDs(d *storage.Database, u models.User, resp *responseType) error {
	defer a.recordLatency(time.Now(), "unread_item_ids")

	articles, err := d.GetUnreadArticlesForUser(u, -1, -1)
	if err != nil {
		return &apiError{err, true}
	}
	var unreadItemIds []string
	for _, a := range articles {
		unreadItemIds = append(unreadItemIds, strconv.FormatInt(a.ID, 10))
	}
	(*resp)["unread_item_ids"] = strings.Join(unreadItemIds, ",")
	return nil
}

func (a Fever) handleSavedItemIDs(_ *storage.Database, _ models.User, resp *responseType) error {
	defer a.recordLatency(time.Now(), "saved_item_ids")

	// Perhaps add support for saving items in the future.
	(*resp)["saved_item_ids"] = ""
	return nil
}

func (a Fever) handleMark(d *storage.Database, u models.User, _ *responseType, r *http.Request) error {
	defer a.recordLatency(time.Now(), "mark")

	// TODO: Support "before" argument.
	var as string
	switch r.FormValue("as") {
	case "read", "unread", "saved", "unsaved":
		as = r.FormValue("as")
	default:
		return &apiError{fmt.Errorf("unknown 'as' value: %s", r.FormValue("as")), false}
	}

	id, err := strconv.ParseInt(r.FormValue("id"), 10, 64)
	if err != nil {
		return err
	}

	switch r.FormValue("mark") {
	case "item":
		if err = d.MarkArticleForUser(u, id, as); err != nil {
			return &apiError{err, true}
		}
	case "feed":
		if err = d.MarkFeedForUser(u, id, as); err != nil {
			return &apiError{err, true}
		}
	case "group":
		if err = d.MarkFolderForUser(u, id, as); err != nil {
			return &apiError{err, true}
		}
	default:
		return &apiError{fmt.Errorf("malformed 'mark' value: %s", r.FormValue("mark")), false}
	}
	return nil
}

func (a Fever) constructFeedsGroups(d *storage.Database, u models.User) ([]feedsGroupType, error) {
	var feedGroups []feedsGroupType
	feedsPerFolder, err := d.GetFeedsPerFolderForUser(u)
	if err != nil {
		log.Warningf("Failed to fetch feeds per folder: %s", err)
		return feedGroups, err
	}
	for k, v := range feedsPerFolder {
		feedGroup := feedsGroupType{
			GroupID: k,
			FeedIDs: v,
		}
		feedGroups = append(feedGroups, feedGroup)
	}
	return feedGroups, nil
}
<commit_msg>core/api: Fix wrong metric name in Fever implementation.<commit_after>package api

import (
	"encoding/json"
	"fmt"
	log "github.com/golang/glog"
	"github.com/jrupac/goliath/auth"
	"github.com/jrupac/goliath/models"
	"github.com/jrupac/goliath/storage"
	"github.com/jrupac/goliath/utils"
	"github.com/prometheus/client_golang/prometheus"
	"net/http"
	"strconv"
	"strings"
	"time"
)

const apiVersion = 3

var (
	feverLatencyMetric = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Name:       "fever_server_latency",
			Help:       "Server-side latency of Fever API operations.",
			Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
		},
		[]string{"method"},
	)
)

type itemType struct {
	ID          int64  `json:"id"`
	FeedID      int64  `json:"feed_id"`
	Title       string `json:"title"`
	Author      string `json:"author"`
	HTML        string `json:"html"`
	URL         string `json:"url"`
	IsSaved     int64  `json:"is_saved"`
	IsRead      int64  `json:"is_read"`
	CreatedTime int64  `json:"created_on_time"`
}

type feedType struct {
	ID          int64  `json:"id"`
	FaviconID   int64  `json:"favicon_id"`
	Title       string `json:"title"`
	URL         string `json:"url"`
	SiteURL     string `json:"site_url"`
	IsSpark     int64  `json:"is_spark"`
	LastUpdated int64  `json:"last_updated_on_time"`
}

type groupType struct {
	ID    int64  `json:"id"`
	Title string `json:"title"`
}

type faviconType struct {
	ID   int64  `json:"id"`
	Data string `json:"data"`
}

type feedsGroupType struct {
	GroupID int64  `json:"group_id"`
	FeedIDs string `json:"feed_ids"`
}

type responseType map[string]interface{}

func init() {
	prometheus.MustRegister(feverLatencyMetric)
}

// Fever is an implementation of the Fever API.
type Fever struct {
}

// FeverHandler returns a new Fever handler.
func FeverHandler(d *storage.Database) http.HandlerFunc {
	return Fever{}.Handler(d)
}

// Handler returns a handler function that implements the Fever API.
func (a Fever) Handler(d *storage.Database) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		a.handle(d, w, r)
	}
}

func (a Fever) recordLatency(t time.Time, label string) {
	utils.Elapsed(t, func(d time.Duration) {
		// Record latency measurements in microseconds.
		feverLatencyMetric.WithLabelValues(label).Observe(float64(d) / float64(time.Microsecond))
	})
}

func (a Fever) handle(d *storage.Database, w http.ResponseWriter, r *http.Request) {
	// Record the total server latency of each call.
	defer a.recordLatency(time.Now(), "server")

	// These two fields must always be set on responses.
	resp := responseType{
		"api_version":            apiVersion,
		"last_refreshed_on_time": time.Now().Unix(),
	}

	err := r.ParseForm()
	if err != nil {
		a.returnError(w, "Failed to parse request: %s", err)
		return
	}

	log.Infof("Fever request URL: %s", r.URL.String())
	log.Infof("Fever request body: %s", r.PostForm.Encode())

	switch r.Form.Get("api") {
	case "":
		w.Header().Set("Content-Type", "application/json")
	case "xml":
		// TODO: Implement XML response type.
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	user, authStatus := a.handleAuth(d, r)
	resp["auth"] = authStatus
	if resp["auth"] == 0 {
		a.returnSuccess(w, resp)
		return
	}

	if _, ok := r.Form["groups"]; ok {
		err := a.handleGroups(d, user, &resp)
		if err != nil {
			a.returnError(w, "Failed request 'groups': %s", err)
			return
		}
	}
	if _, ok := r.Form["feeds"]; ok {
		err := a.handleFeeds(d, user, &resp)
		if err != nil {
			a.returnError(w, "Failed request 'feeds': %s", err)
			return
		}
	}
	if _, ok := r.Form["favicons"]; ok {
		err := a.handleFavicons(d, user, &resp)
		if err != nil {
			a.returnError(w, "Failed request 'favicons': %s", err)
			return
		}
	}
	if _, ok := r.Form["items"]; ok {
		err := a.handleItems(d, user, &resp, r)
		if err != nil {
			a.returnError(w, "Failed request 'items': %s", err)
			return
		}
	}
	if _, ok := r.Form["links"]; ok {
		err := a.handleLinks(d, user, &resp)
		if err != nil {
			a.returnError(w, "Failed request 'links': %s", err)
			return
		}
	}
	if _, ok := r.Form["unread_item_ids"]; ok {
		err := a.handleUnreadItemIDs(d, user, &resp)
		if err != nil {
			a.returnError(w, "Failed request 'unread_item_ids': %s", err)
			return
		}
	}
	if _, ok := r.Form["saved_item_ids"]; ok {
		err := a.handleSavedItemIDs(d, user, &resp)
		if err != nil {
			a.returnError(w, "Failed request 'saved_item_ids': %s", err)
			return
		}
	}
	if _, ok := r.Form["mark"]; ok {
		err := a.handleMark(d, user, &resp, r)
		if err != nil {
			a.returnError(w, "Failed request 'mark': %s", err)
			return
		}
	}

	a.returnSuccess(w, resp)
}

func (a Fever) returnError(w http.ResponseWriter, msg string, err error) {
	log.Warningf(msg, err)
	if fe, ok := err.(*apiError); ok {
		if fe.internal {
			w.WriteHeader(http.StatusInternalServerError)
		} else {
			w.WriteHeader(http.StatusBadRequest)
		}
		return
	}
	w.WriteHeader(http.StatusInternalServerError)
}

func (a Fever) returnSuccess(w http.ResponseWriter, resp map[string]interface{}) {
	w.WriteHeader(http.StatusOK)
	enc := json.NewEncoder(w)
	// HTML content is escaped already during fetch time (e.g., ' --> '), so
	// do not HTML escape it further (e.g., ' --> \u0026#39;), which would
	// render incorrectly on a webpage.
	enc.SetEscapeHTML(false)
	if err := enc.Encode(resp); err != nil {
		a.returnError(w, "Failed to encode response JSON: %s", err)
	}
}

func (a Fever) handleAuth(d *storage.Database, r *http.Request) (models.User, int) {
	defer a.recordLatency(time.Now(), "auth")

	// A request can be authenticated by cookie or api key in request.
	if user, err := auth.VerifyCookie(d, r); err == nil {
		log.V(2).Infof("Verified cookie: %+v", r)
		return user, 1
	} else if user, err := d.GetUserByKey(r.FormValue("api_key")); err != nil {
		utils.HttpRequestPrint("Received unauthenticated request", r)
		log.Warningf("Failed because: %s", err)
		return user, 0
	} else {
		log.V(2).Infof("Successfully authenticated by key: %+v", r)
		return user, 1
	}
}

func (a Fever) handleGroups(d *storage.Database, u models.User, resp *responseType) error {
	defer a.recordLatency(time.Now(), "groups")

	folders, err := d.GetAllFoldersForUser(u)
	if err != nil {
		return &apiError{err, true}
	}
	var groups []groupType
	for _, f := range folders {
		g := groupType{
			ID:    f.ID,
			Title: f.Name,
		}
		groups = append(groups, g)
	}
	(*resp)["groups"] = groups
	(*resp)["feeds_groups"], err = a.constructFeedsGroups(d, u)
	if err != nil {
		return &apiError{err, true}
	}
	return nil
}

func (a Fever) handleFeeds(d *storage.Database, u models.User, resp *responseType) error {
	defer a.recordLatency(time.Now(), "feeds")

	fetchedFeeds, err := d.GetAllFeedsForUser(u)
	if err != nil {
		return &apiError{err, true}
	}
	var feeds []feedType
	for _, ff := range fetchedFeeds {
		f := feedType{
			ID:          ff.ID,
			FaviconID:   ff.ID,
			Title:       ff.Title,
			URL:         ff.URL,
			SiteURL:     ff.Link,
			IsSpark:     0,
			LastUpdated: time.Now().Unix(),
		}
		feeds = append(feeds, f)
	}
	(*resp)["feeds"] = feeds
	(*resp)["feeds_groups"], err = a.constructFeedsGroups(d, u)
	if err != nil {
		return &apiError{err, true}
	}
	return nil
}

func (a Fever) handleFavicons(d *storage.Database, u models.User, resp *responseType) error {
	faviconMap, err := d.GetAllFaviconsForUser(u)
	if err != nil {
		return &apiError{err, true}
	}
	var favicons []faviconType
	for k, v := range faviconMap {
		f := faviconType{
			ID:   k,
			Data: v,
		}
		favicons = append(favicons, f)
	}
	(*resp)["favicons"] = favicons
	return nil
}

func (a Fever) handleItems(d *storage.Database, u models.User, resp *responseType, r *http.Request) error {
	defer a.recordLatency(time.Now(), "items")

	// TODO: support "max_id" and "with_ids".
	sinceID := int64(-1)
	var err error

	if _, ok := r.Form["since_id"]; ok {
		sinceID, err = strconv.ParseInt(r.FormValue("since_id"), 10, 64)
		if err != nil {
			return &apiError{err, false}
		}
	}

	articles, err := d.GetUnreadArticlesForUser(u, 50, sinceID)
	if err != nil {
		return &apiError{err, true}
	}
	// Make an empty (not nil) slice because their JSON encodings are different.
	items := make([]itemType, 0)
	var content string
	for _, a := range articles {
		if *serveParsedArticles && a.Parsed != "" {
			log.Infof("Serving parsed content for title: %s", a.Title)
			content = a.Parsed
		} else if a.Content != "" {
			// The "content" field usually has more text, but is not always set.
			content = a.Content
		} else {
			content = a.Summary
		}

		i := itemType{
			ID:          a.ID,
			FeedID:      a.FeedID,
			Title:       a.Title,
			Author:      "",
			HTML:        content,
			URL:         a.Link,
			IsSaved:     0,
			IsRead:      0,
			CreatedTime: a.Date.Unix(),
		}
		items = append(items, i)
	}
	(*resp)["items"] = items
	return nil
}

func (a Fever) handleLinks(_ *storage.Database, _ models.User, resp *responseType) error {
	defer a.recordLatency(time.Now(), "links")

	// Perhaps add support for links in the future.
	(*resp)["links"] = ""
	return nil
}

func (a Fever) handleUnreadItemIDs(d *storage.Database, u models.User, resp *responseType) error {
	defer a.recordLatency(time.Now(), "unread_item_ids")

	articles, err := d.GetUnreadArticlesForUser(u, -1, -1)
	if err != nil {
		return &apiError{err, true}
	}
	var unreadItemIds []string
	for _, a := range articles {
		unreadItemIds = append(unreadItemIds, strconv.FormatInt(a.ID, 10))
	}
	(*resp)["unread_item_ids"] = strings.Join(unreadItemIds, ",")
	return nil
}

func (a Fever) handleSavedItemIDs(_ *storage.Database, _ models.User, resp *responseType) error {
	defer a.recordLatency(time.Now(), "saved_item_ids")

	// Perhaps add support for saving items in the future.
	(*resp)["saved_item_ids"] = ""
	return nil
}

func (a Fever) handleMark(d *storage.Database, u models.User, _ *responseType, r *http.Request) error {
	defer a.recordLatency(time.Now(), "mark")

	// TODO: Support "before" argument.
	var as string
	switch r.FormValue("as") {
	case "read", "unread", "saved", "unsaved":
		as = r.FormValue("as")
	default:
		return &apiError{fmt.Errorf("unknown 'as' value: %s", r.FormValue("as")), false}
	}

	id, err := strconv.ParseInt(r.FormValue("id"), 10, 64)
	if err != nil {
		return err
	}

	switch r.FormValue("mark") {
	case "item":
		if err = d.MarkArticleForUser(u, id, as); err != nil {
			return &apiError{err, true}
		}
	case "feed":
		if err = d.MarkFeedForUser(u, id, as); err != nil {
			return &apiError{err, true}
		}
	case "group":
		if err = d.MarkFolderForUser(u, id, as); err != nil {
			return &apiError{err, true}
		}
	default:
		return &apiError{fmt.Errorf("malformed 'mark' value: %s", r.FormValue("mark")), false}
	}
	return nil
}

func (a Fever) constructFeedsGroups(d *storage.Database, u models.User) ([]feedsGroupType, error) {
	var feedGroups []feedsGroupType
	feedsPerFolder, err := d.GetFeedsPerFolderForUser(u)
	if err != nil {
		log.Warningf("Failed to fetch feeds per folder: %s", err)
		return feedGroups, err
	}
	for k, v := range feedsPerFolder {
		feedGroup := feedsGroupType{
			GroupID: k,
			FeedIDs: v,
		}
		feedGroups = append(feedGroups, feedGroup)
	}
	return feedGroups, nil
}
<|endoftext|>


<commit_before>package evented

import (
	"log"

	"github.com/chuckpreslar/emission"
	"github.com/sunspots/tmi"
)
// EventedConnection wraps a tmi Connection together with an EventEmitter
type EventedConnection struct {
  *emission.Emitter
  *tmi.Connection
}

func readLoop(connection *tmi.Connection, emitter *EventedConnection) {
	for {
		evt, err := connection.ReadMessage()
		if err != nil {
			// You could use this to do something useful and
			// handle reconnects, but we're just gonna error out for the sake of simplicity.
			log.Fatal(err)
		} else {
      emitter.Emit("message", evt)
      emitter.Emit(evt.Command, evt)
		}
	}
}

// New instantiates an EventEmittter
func New(tmi *tmi.Connection) *EventedConnection {
	emitter := &EventedConnection{emission.NewEmitter(), tmi}
  go readLoop(tmi, emitter)
	return emitter
}
<commit_msg>naming, formatting<commit_after>package evented

import (
	"log"

	"github.com/chuckpreslar/emission"
	"github.com/sunspots/tmi"
)

// Connection wraps a tmi Connection together with an EventEmitter
type Connection struct {
	*emission.Emitter
	*tmi.Connection
}

func readLoop(connection *tmi.Connection, emitter *Connection) {
	for {
		evt, err := connection.ReadMessage()
		if err != nil {
			// You could use this to do something useful and
			// handle reconnects, but we're just gonna error out for the sake of simplicity.
			log.Fatal(err)
		} else {
			emitter.Emit("message", evt)
			emitter.Emit(evt.Command, evt)
		}
	}
}

// New instantiates an EventEmittter
func New(tmi *tmi.Connection) *Connection {
	emitter := &Connection{emission.NewEmitter(), tmi}
	go readLoop(tmi, emitter)
	return emitter
}
<|endoftext|>


<commit_before>package gval

import (
	"context"
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
)

// Evaluable evaluates given parameter
type Evaluable func(c context.Context, parameter interface{}) (interface{}, error)

//EvalInt evaluates given parameter to an int
func (e Evaluable) EvalInt(c context.Context, parameter interface{}) (int, error) {
	v, err := e(c, parameter)
	if err != nil {
		return 0, err
	}

	f, ok := convertToFloat(v)
	if !ok {
		return 0, fmt.Errorf("expected number but got %v (%T)", v, v)
	}
	return int(f), nil
}

//EvalFloat64 evaluates given parameter to an int
func (e Evaluable) EvalFloat64(c context.Context, parameter interface{}) (float64, error) {
	v, err := e(c, parameter)
	if err != nil {
		return 0, err
	}

	f, ok := convertToFloat(v)
	if !ok {
		return 0, fmt.Errorf("expected number but got %v (%T)", v, v)
	}
	return f, nil
}

//EvalBool evaluates given parameter to a bool
func (e Evaluable) EvalBool(c context.Context, parameter interface{}) (bool, error) {
	v, err := e(c, parameter)
	if err != nil {
		return false, err
	}

	b, ok := convertToBool(v)
	if !ok {
		return false, fmt.Errorf("expected bool but got %v (%T)", v, v)
	}
	return b, nil
}

//EvalString evaluates given parameter to a string
func (e Evaluable) EvalString(c context.Context, parameter interface{}) (string, error) {
	o, err := e(c, parameter)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%v", o), nil
}

//Const Evaluable represents given constant
func (*Parser) Const(value interface{}) Evaluable {
	return constant(value)
}

func constant(value interface{}) Evaluable {
	return func(c context.Context, v interface{}) (interface{}, error) {
		return value, nil
	}
}

//Var Evaluable represents value at given path.
//It supports:
//	map[string]interface{},
// 	[]interface{} and
//	struct fields
func (*Parser) Var(path ...Evaluable) Evaluable {
	return variable(path...)
}

func variable(path ...Evaluable) Evaluable {
	return func(c context.Context, v interface{}) (interface{}, error) {
		keys := make([]string, len(path))
		for i, p := range path {
			k, err := p.EvalString(c, v)
			if err != nil {
				return nil, err
			}
			keys[i] = k
		}
		for i, k := range keys {
			switch o := v.(type) {
			case map[string]interface{}:
				v = o[k]
				continue
			case []interface{}:
				if i, err := strconv.Atoi(k); err == nil && i >= 0 && len(o) > i {
					v = o[i]
					continue
				}
			default:
				vv := reflect.ValueOf(o)

				// if this is a pointer, resolve it.
				if vv.Kind() == reflect.Ptr {
					vv = vv.Elem()
				}

				//check generic for maps and arrays

				if vv.Kind() != reflect.Struct {
					break
				}

				field := vv.FieldByName(k)
				if field.IsValid() {
					v = field.Interface()
					continue
				}

				method := vv.MethodByName(k)
				if method.IsValid() {
					v = method.Interface()
					continue
				}
			}
			return nil, fmt.Errorf("unknown parameter %s", strings.Join(keys[:i+1], "."))
		}
		return v, nil
	}
}

func (*Parser) callFunc(fun function, args ...Evaluable) Evaluable {
	return func(c context.Context, v interface{}) (ret interface{}, err error) {
		a := make([]interface{}, len(args))
		for i, arg := range args {
			ai, err := arg(c, v)
			if err != nil {
				return nil, err
			}
			a[i] = ai
		}
		return fun(a...)
	}
}

func (*Parser) callEvaluable(fullname string, fun Evaluable, args ...Evaluable) Evaluable {
	return func(c context.Context, v interface{}) (ret interface{}, err error) {
		f, err := fun(c, v)

		if err != nil {
			return nil, fmt.Errorf("could not call function: %v", err)
		}

		defer func() {
			if r := recover(); r != nil {
				err = fmt.Errorf("failed to execute function '%s': %s", fullname, r)
				ret = nil
			}
		}()

		ff := reflect.ValueOf(f)

		if ff.Kind() != reflect.Func {
			return nil, fmt.Errorf("could not call '%s' type %T", fullname, f)
		}

		a := make([]reflect.Value, len(args))
		for i := range args {
			arg, err := args[i](c, v)
			if err != nil {
				return nil, err
			}
			a[i] = reflect.ValueOf(arg)
		}

		rr := ff.Call(a)

		r := make([]interface{}, len(rr))
		for i, e := range rr {
			r[i] = e.Interface()
		}

		errorInterface := reflect.TypeOf((*error)(nil)).Elem()
		if len(r) > 0 && ff.Type().Out(len(r)-1).Implements(errorInterface) {
			if r[len(r)-1] != nil {
				err = r[len(r)-1].(error)
			}
			r = r[0 : len(r)-1]
		}

		switch len(r) {
		case 0:
			return err, nil
		case 1:
			return r[0], err
		default:
			return r, err
		}
	}
}

//IsConst returns if the Evaluable is a Parser.Const() value
func (e Evaluable) IsConst() bool {
	pc := reflect.ValueOf(constant(nil)).Pointer()
	pe := reflect.ValueOf(e).Pointer()
	return pc == pe
}

func regEx(a, b Evaluable) (Evaluable, error) {
	if !b.IsConst() {
		return func(c context.Context, o interface{}) (interface{}, error) {
			a, err := a.EvalString(c, o)
			if err != nil {
				return nil, err
			}
			b, err := b.EvalString(c, o)
			if err != nil {
				return nil, err
			}
			matched, err := regexp.MatchString(b, a)
			return matched, err
		}, nil
	}
	s, err := b.EvalString(nil, nil)
	if err != nil {
		return nil, err
	}
	regex, err := regexp.Compile(s)
	if err != nil {
		return nil, err
	}
	return func(c context.Context, v interface{}) (interface{}, error) {
		s, err := a.EvalString(c, v)
		if err != nil {
			return nil, err
		}
		return regex.MatchString(s), nil
	}, nil
}

func notRegEx(a, b Evaluable) (Evaluable, error) {
	if !b.IsConst() {
		return func(c context.Context, o interface{}) (interface{}, error) {
			a, err := a.EvalString(c, o)
			if err != nil {
				return nil, err
			}
			b, err := b.EvalString(c, o)
			if err != nil {
				return nil, err
			}
			matched, err := regexp.MatchString(b, a)
			return !matched, err
		}, nil
	}
	s, err := b.EvalString(nil, nil)
	if err != nil {
		return nil, err
	}
	regex, err := regexp.Compile(s)
	if err != nil {
		return nil, err
	}
	return func(c context.Context, v interface{}) (interface{}, error) {
		s, err := a.EvalString(c, v)
		if err != nil {
			return nil, err
		}
		return !regex.MatchString(s), nil
	}, nil
}
<commit_msg>Fixed method call on pointer type<commit_after>package gval

import (
	"context"
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
)

// Evaluable evaluates given parameter
type Evaluable func(c context.Context, parameter interface{}) (interface{}, error)

//EvalInt evaluates given parameter to an int
func (e Evaluable) EvalInt(c context.Context, parameter interface{}) (int, error) {
	v, err := e(c, parameter)
	if err != nil {
		return 0, err
	}

	f, ok := convertToFloat(v)
	if !ok {
		return 0, fmt.Errorf("expected number but got %v (%T)", v, v)
	}
	return int(f), nil
}

//EvalFloat64 evaluates given parameter to an int
func (e Evaluable) EvalFloat64(c context.Context, parameter interface{}) (float64, error) {
	v, err := e(c, parameter)
	if err != nil {
		return 0, err
	}

	f, ok := convertToFloat(v)
	if !ok {
		return 0, fmt.Errorf("expected number but got %v (%T)", v, v)
	}
	return f, nil
}

//EvalBool evaluates given parameter to a bool
func (e Evaluable) EvalBool(c context.Context, parameter interface{}) (bool, error) {
	v, err := e(c, parameter)
	if err != nil {
		return false, err
	}

	b, ok := convertToBool(v)
	if !ok {
		return false, fmt.Errorf("expected bool but got %v (%T)", v, v)
	}
	return b, nil
}

//EvalString evaluates given parameter to a string
func (e Evaluable) EvalString(c context.Context, parameter interface{}) (string, error) {
	o, err := e(c, parameter)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%v", o), nil
}

//Const Evaluable represents given constant
func (*Parser) Const(value interface{}) Evaluable {
	return constant(value)
}

func constant(value interface{}) Evaluable {
	return func(c context.Context, v interface{}) (interface{}, error) {
		return value, nil
	}
}

//Var Evaluable represents value at given path.
//It supports:
//	map[string]interface{},
// 	[]interface{} and
//	struct fields
func (*Parser) Var(path ...Evaluable) Evaluable {
	return variable(path...)
}

func variable(path ...Evaluable) Evaluable {
	return func(c context.Context, v interface{}) (interface{}, error) {
		keys := make([]string, len(path))
		for i, p := range path {
			k, err := p.EvalString(c, v)
			if err != nil {
				return nil, err
			}
			keys[i] = k
		}
		for i, k := range keys {
			switch o := v.(type) {
			case map[string]interface{}:
				v = o[k]
				continue
			case []interface{}:
				if i, err := strconv.Atoi(k); err == nil && i >= 0 && len(o) > i {
					v = o[i]
					continue
				}
			default:
				vv := reflect.ValueOf(o)
				vvElem := vv

				// if this is a pointer, resolve it.
				if vv.Kind() == reflect.Ptr {
					vvElem = vv.Elem()
				}

				//check generic for maps and arrays

				if vvElem.Kind() != reflect.Struct {
					break
				}

				field := vvElem.FieldByName(k)
				if field.IsValid() {
					v = field.Interface()
					continue
				}

				method := vv.MethodByName(k)
				if method.IsValid() {
					v = method.Interface()
					continue
				}
			}
			return nil, fmt.Errorf("unknown parameter %s", strings.Join(keys[:i+1], "."))
		}
		return v, nil
	}
}

func (*Parser) callFunc(fun function, args ...Evaluable) Evaluable {
	return func(c context.Context, v interface{}) (ret interface{}, err error) {
		a := make([]interface{}, len(args))
		for i, arg := range args {
			ai, err := arg(c, v)
			if err != nil {
				return nil, err
			}
			a[i] = ai
		}
		return fun(a...)
	}
}

func (*Parser) callEvaluable(fullname string, fun Evaluable, args ...Evaluable) Evaluable {
	return func(c context.Context, v interface{}) (ret interface{}, err error) {
		f, err := fun(c, v)

		if err != nil {
			return nil, fmt.Errorf("could not call function: %v", err)
		}

		defer func() {
			if r := recover(); r != nil {
				err = fmt.Errorf("failed to execute function '%s': %s", fullname, r)
				ret = nil
			}
		}()

		ff := reflect.ValueOf(f)

		if ff.Kind() != reflect.Func {
			return nil, fmt.Errorf("could not call '%s' type %T", fullname, f)
		}

		a := make([]reflect.Value, len(args))
		for i := range args {
			arg, err := args[i](c, v)
			if err != nil {
				return nil, err
			}
			a[i] = reflect.ValueOf(arg)
		}

		rr := ff.Call(a)

		r := make([]interface{}, len(rr))
		for i, e := range rr {
			r[i] = e.Interface()
		}

		errorInterface := reflect.TypeOf((*error)(nil)).Elem()
		if len(r) > 0 && ff.Type().Out(len(r)-1).Implements(errorInterface) {
			if r[len(r)-1] != nil {
				err = r[len(r)-1].(error)
			}
			r = r[0 : len(r)-1]
		}

		switch len(r) {
		case 0:
			return err, nil
		case 1:
			return r[0], err
		default:
			return r, err
		}
	}
}

//IsConst returns if the Evaluable is a Parser.Const() value
func (e Evaluable) IsConst() bool {
	pc := reflect.ValueOf(constant(nil)).Pointer()
	pe := reflect.ValueOf(e).Pointer()
	return pc == pe
}

func regEx(a, b Evaluable) (Evaluable, error) {
	if !b.IsConst() {
		return func(c context.Context, o interface{}) (interface{}, error) {
			a, err := a.EvalString(c, o)
			if err != nil {
				return nil, err
			}
			b, err := b.EvalString(c, o)
			if err != nil {
				return nil, err
			}
			matched, err := regexp.MatchString(b, a)
			return matched, err
		}, nil
	}
	s, err := b.EvalString(nil, nil)
	if err != nil {
		return nil, err
	}
	regex, err := regexp.Compile(s)
	if err != nil {
		return nil, err
	}
	return func(c context.Context, v interface{}) (interface{}, error) {
		s, err := a.EvalString(c, v)
		if err != nil {
			return nil, err
		}
		return regex.MatchString(s), nil
	}, nil
}

func notRegEx(a, b Evaluable) (Evaluable, error) {
	if !b.IsConst() {
		return func(c context.Context, o interface{}) (interface{}, error) {
			a, err := a.EvalString(c, o)
			if err != nil {
				return nil, err
			}
			b, err := b.EvalString(c, o)
			if err != nil {
				return nil, err
			}
			matched, err := regexp.MatchString(b, a)
			return !matched, err
		}, nil
	}
	s, err := b.EvalString(nil, nil)
	if err != nil {
		return nil, err
	}
	regex, err := regexp.Compile(s)
	if err != nil {
		return nil, err
	}
	return func(c context.Context, v interface{}) (interface{}, error) {
		s, err := a.EvalString(c, v)
		if err != nil {
			return nil, err
		}
		return !regex.MatchString(s), nil
	}, nil
}
<|endoftext|>


<commit_before>package api

import (
	"context"

	"github.com/bytom/errors"
	"github.com/bytom/protocol/bc"
	"github.com/bytom/protocol/bc/types"
)

// BlockHeaderJSON struct provides support for get work in json format, when it also follows
// protocol/bc/types.BlockHeader
type BlockHeaderJSON struct {
	Version           uint64 					`json:"version"`  // The version of the block.
	Height            uint64 					`json:"height"`  // The height of the block.
	PreviousBlockHash bc.Hash 					`json:"previous_block_hash"` // The hash of the previous block.
	Timestamp         uint64  					`json:"timestamp"` // The time of the block in seconds.
	Nonce             uint64 					`json:"nonce"` // Nonce used to generate the block.
	Bits              uint64 					`json:"bits"` // Difficulty target for the block.
	BlockCommitment   *types.BlockCommitment 	`json:"block_commitment"` //Block commitment
}

// getWork gets work in compressed protobuf format
func (a *API) getWork() Response {
	work, err := a.GetWork()
	if err != nil {
		return NewErrorResponse(err)
	}
	return NewSuccessResponse(work)
}

// getWorkJSON gets work in json format
func (a *API) getWorkJSON() Response {
	work, err := a.GetWorkJSON()
	if err != nil {
		return NewErrorResponse(err)
	}
	return NewSuccessResponse(work)
}

// SubmitWorkJSONReq is req struct for submit-work API
type SubmitWorkReq struct {
	BlockHeader *types.BlockHeader `json:"block_header"`
}

// submitWork submits work in compressed protobuf format
func (a *API) submitWork(ctx context.Context, req *SubmitWorkReq) Response {
	if err := a.SubmitWork(req.BlockHeader); err != nil {
		return NewErrorResponse(err)
	}
	return NewSuccessResponse(true)
}

// SubmitWorkJSONReq is req struct for submit-work-json API
type SubmitWorkJSONReq struct {
	BlockHeader *BlockHeaderJSON 	`json:"block_header"`
}

// submitWorkJSON submits work in json format
func (a *API) submitWorkJSON(ctx context.Context, req *SubmitWorkJSONReq) Response {
	blockCommitment := types.BlockCommitment{
							TransactionsMerkleRoot: req.BlockHeader.BlockCommitment.TransactionsMerkleRoot,
							TransactionStatusHash:  req.BlockHeader.BlockCommitment.TransactionStatusHash,
						}

	bh := &types.BlockHeader{
			Version:			req.BlockHeader.Version,
			Height:				req.BlockHeader.Height,
			PreviousBlockHash:	req.BlockHeader.PreviousBlockHash,
			Timestamp:         	req.BlockHeader.Timestamp,
			Nonce:             	req.BlockHeader.Nonce,
			Bits:              	req.BlockHeader.Bits,
			BlockCommitment:	blockCommitment,
		}

	if err := a.SubmitWork(bh); err != nil {
		return NewErrorResponse(err)
	}
	return NewSuccessResponse(true)
}

// GetWorkResp is resp struct for get-work API
type GetWorkResp struct {
	BlockHeader *types.BlockHeader `json:"block_header"`
	Seed        *bc.Hash           `json:"seed"`
}

// GetWork gets work in compressed protobuf format
func (a *API) GetWork() (*GetWorkResp, error) {
	bh, err := a.miningPool.GetWork()
	if err != nil {
		return nil, err
	}

	seed, err := a.chain.CalcNextSeed(&bh.PreviousBlockHash)
	if err != nil {
		return nil, err
	}

	return &GetWorkResp{
		BlockHeader: bh,
		Seed:        seed,
	}, nil
}

// GetWorkJSONResp is resp struct for get-work-json API
type GetWorkJSONResp struct {
	BlockHeader *BlockHeaderJSON 	`json:"block_header"`
	Seed        *bc.Hash           	`json:"seed"`
}

// GetWorkJSON gets work in json format
func (a *API) GetWorkJSON() (*GetWorkJSONResp, error) {
	bh, err := a.miningPool.GetWork()
	if err != nil {
		return nil, err
	}

	seed, err := a.chain.CalcNextSeed(&bh.PreviousBlockHash)
	if err != nil {
		return nil, err
	}

	blockCommitment := &types.BlockCommitment{
							TransactionsMerkleRoot: bh.BlockCommitment.TransactionsMerkleRoot,
							TransactionStatusHash:  bh.BlockCommitment.TransactionStatusHash,
						}

	return &GetWorkJSONResp{
		BlockHeader: &BlockHeaderJSON{
			Version:			bh.Version,
			Height:				bh.Height,
			PreviousBlockHash:	bh.PreviousBlockHash,
			Timestamp:         	bh.Timestamp,
			Nonce:             	bh.Nonce,
			Bits:              	bh.Bits,
			BlockCommitment:	blockCommitment,
		},
		Seed:        seed,
	}, nil
}

// SubmitWork tries to submit work to the chain
func (a *API) SubmitWork(bh *types.BlockHeader) error {
	return a.miningPool.SubmitWork(bh)
}

func (a *API) setMining(in struct {
	IsMining bool `json:"is_mining"`
}) Response {
	if in.IsMining {
		return a.startMining()
	}
	return a.stopMining()
}

func (a *API) startMining() Response {
	a.cpuMiner.Start()
	if !a.IsMining() {
		return NewErrorResponse(errors.New("Failed to start mining"))
	}
	return NewSuccessResponse("")
}

func (a *API) stopMining() Response {
	a.cpuMiner.Stop()
	if a.IsMining() {
		return NewErrorResponse(errors.New("Failed to stop mining"))
	}
	return NewSuccessResponse("")
}
<commit_msg>go formattted<commit_after>package api

import (
	"context"

	"github.com/bytom/errors"
	"github.com/bytom/protocol/bc"
	"github.com/bytom/protocol/bc/types"
)

// BlockHeaderJSON struct provides support for get work in json format, when it also follows
// protocol/bc/types.BlockHeader structure
type BlockHeaderJSON struct {
	Version           uint64                 `json:"version"`             // The version of the block.
	Height            uint64                 `json:"height"`              // The height of the block.
	PreviousBlockHash bc.Hash                `json:"previous_block_hash"` // The hash of the previous block.
	Timestamp         uint64                 `json:"timestamp"`           // The time of the block in seconds.
	Nonce             uint64                 `json:"nonce"`               // Nonce used to generate the block.
	Bits              uint64                 `json:"bits"`                // Difficulty target for the block.
	BlockCommitment   *types.BlockCommitment `json:"block_commitment"`    //Block commitment
}

// getWork gets work in compressed protobuf format
func (a *API) getWork() Response {
	work, err := a.GetWork()
	if err != nil {
		return NewErrorResponse(err)
	}
	return NewSuccessResponse(work)
}

// getWorkJSON gets work in json format
func (a *API) getWorkJSON() Response {
	work, err := a.GetWorkJSON()
	if err != nil {
		return NewErrorResponse(err)
	}
	return NewSuccessResponse(work)
}

// SubmitWorkJSONReq is req struct for submit-work API
type SubmitWorkReq struct {
	BlockHeader *types.BlockHeader `json:"block_header"`
}

// submitWork submits work in compressed protobuf format
func (a *API) submitWork(ctx context.Context, req *SubmitWorkReq) Response {
	if err := a.SubmitWork(req.BlockHeader); err != nil {
		return NewErrorResponse(err)
	}
	return NewSuccessResponse(true)
}

// SubmitWorkJSONReq is req struct for submit-work-json API
type SubmitWorkJSONReq struct {
	BlockHeader *BlockHeaderJSON `json:"block_header"`
}

// submitWorkJSON submits work in json format
func (a *API) submitWorkJSON(ctx context.Context, req *SubmitWorkJSONReq) Response {
	blockCommitment := types.BlockCommitment{
		TransactionsMerkleRoot: req.BlockHeader.BlockCommitment.TransactionsMerkleRoot,
		TransactionStatusHash:  req.BlockHeader.BlockCommitment.TransactionStatusHash,
	}

	bh := &types.BlockHeader{
		Version:           req.BlockHeader.Version,
		Height:            req.BlockHeader.Height,
		PreviousBlockHash: req.BlockHeader.PreviousBlockHash,
		Timestamp:         req.BlockHeader.Timestamp,
		Nonce:             req.BlockHeader.Nonce,
		Bits:              req.BlockHeader.Bits,
		BlockCommitment:   blockCommitment,
	}

	if err := a.SubmitWork(bh); err != nil {
		return NewErrorResponse(err)
	}
	return NewSuccessResponse(true)
}

// GetWorkResp is resp struct for get-work API
type GetWorkResp struct {
	BlockHeader *types.BlockHeader `json:"block_header"`
	Seed        *bc.Hash           `json:"seed"`
}

// GetWork gets work in compressed protobuf format
func (a *API) GetWork() (*GetWorkResp, error) {
	bh, err := a.miningPool.GetWork()
	if err != nil {
		return nil, err
	}

	seed, err := a.chain.CalcNextSeed(&bh.PreviousBlockHash)
	if err != nil {
		return nil, err
	}

	return &GetWorkResp{
		BlockHeader: bh,
		Seed:        seed,
	}, nil
}

// GetWorkJSONResp is resp struct for get-work-json API
type GetWorkJSONResp struct {
	BlockHeader *BlockHeaderJSON `json:"block_header"`
	Seed        *bc.Hash         `json:"seed"`
}

// GetWorkJSON gets work in json format
func (a *API) GetWorkJSON() (*GetWorkJSONResp, error) {
	bh, err := a.miningPool.GetWork()
	if err != nil {
		return nil, err
	}

	seed, err := a.chain.CalcNextSeed(&bh.PreviousBlockHash)
	if err != nil {
		return nil, err
	}

	blockCommitment := &types.BlockCommitment{
		TransactionsMerkleRoot: bh.BlockCommitment.TransactionsMerkleRoot,
		TransactionStatusHash:  bh.BlockCommitment.TransactionStatusHash,
	}

	return &GetWorkJSONResp{
		BlockHeader: &BlockHeaderJSON{
			Version:           bh.Version,
			Height:            bh.Height,
			PreviousBlockHash: bh.PreviousBlockHash,
			Timestamp:         bh.Timestamp,
			Nonce:             bh.Nonce,
			Bits:              bh.Bits,
			BlockCommitment:   blockCommitment,
		},
		Seed: seed,
	}, nil
}

// SubmitWork tries to submit work to the chain
func (a *API) SubmitWork(bh *types.BlockHeader) error {
	return a.miningPool.SubmitWork(bh)
}

func (a *API) setMining(in struct {
	IsMining bool `json:"is_mining"`
}) Response {
	if in.IsMining {
		return a.startMining()
	}
	return a.stopMining()
}

func (a *API) startMining() Response {
	a.cpuMiner.Start()
	if !a.IsMining() {
		return NewErrorResponse(errors.New("Failed to start mining"))
	}
	return NewSuccessResponse("")
}

func (a *API) stopMining() Response {
	a.cpuMiner.Stop()
	if a.IsMining() {
		return NewErrorResponse(errors.New("Failed to stop mining"))
	}
	return NewSuccessResponse("")
}
<|endoftext|>


<commit_before>package api

import (
	"context"
	"encoding/hex"
	"fmt"

	log "github.com/sirupsen/logrus"

	"github.com/bytom/account"
	"github.com/bytom/blockchain/query"
	"github.com/bytom/blockchain/signers"
	"github.com/bytom/consensus"
	"github.com/bytom/crypto/ed25519/chainkd"
	chainjson "github.com/bytom/encoding/json"
	"github.com/bytom/errors"
	"github.com/bytom/protocol/bc"
	"github.com/bytom/protocol/bc/types"
)

// POST /list-accounts
func (a *API) listAccounts(ctx context.Context, filter struct {
	ID    string `json:"id"`
	Alias string `json:"alias"`
}) Response {
	accountID := filter.ID
	if filter.Alias != "" {
		acc, err := a.wallet.AccountMgr.FindByAlias(filter.Alias)
		if err != nil {
			return NewErrorResponse(err)
		}
		accountID = acc.ID
	}

	accounts, err := a.wallet.AccountMgr.ListAccounts(accountID)
	if err != nil {
		log.Errorf("listAccounts: %v", err)
		return NewErrorResponse(err)
	}

	annotatedAccounts := []query.AnnotatedAccount{}
	for _, acc := range accounts {
		annotatedAccounts = append(annotatedAccounts, *account.Annotated(acc))
	}

	return NewSuccessResponse(annotatedAccounts)
}

// POST /get-asset
func (a *API) getAsset(ctx context.Context, filter struct {
	ID string `json:"id"`
}) Response {
	asset, err := a.wallet.AssetReg.GetAsset(filter.ID)
	if err != nil {
		log.Errorf("getAsset: %v", err)
		return NewErrorResponse(err)
	}

	return NewSuccessResponse(asset)
}

// POST /list-assets
func (a *API) listAssets(ctx context.Context, filter struct {
	ID string `json:"id"`
}) Response {
	assets, err := a.wallet.AssetReg.ListAssets(filter.ID)
	if err != nil {
		log.Errorf("listAssets: %v", err)
		return NewErrorResponse(err)
	}

	return NewSuccessResponse(assets)
}

// POST /list-balances
func (a *API) listBalances(ctx context.Context) Response {
	balances, err := a.wallet.GetAccountBalances("")
	if err != nil {
		return NewErrorResponse(err)
	}
	return NewSuccessResponse(balances)
}

// POST /get-transaction
func (a *API) getTransaction(ctx context.Context, txInfo struct {
	TxID string `json:"tx_id"`
}) Response {
	var annotatedTx *query.AnnotatedTx
	var err error

	annotatedTx, err = a.wallet.GetTransactionByTxID(txInfo.TxID)
	if err != nil {
		// transaction not found in blockchain db, search it from unconfirmed db
		annotatedTx, err = a.wallet.GetUnconfirmedTxByTxID(txInfo.TxID)
		if err != nil {
			return NewErrorResponse(err)
		}
	}

	return NewSuccessResponse(annotatedTx)
}

// POST /list-transactions
func (a *API) listTransactions(ctx context.Context, filter struct {
	ID          string `json:"id"`
	AccountID   string `json:"account_id"`
	Detail      bool   `json:"detail"`
	Unconfirmed bool   `json:"unconfirmed"`
	From        uint   `json:"from"`
	Count       uint   `json:"count"`
}) Response {
	transactions := []*query.AnnotatedTx{}
	var err error
	var transaction *query.AnnotatedTx

	if filter.ID != "" {
		transaction, err = a.wallet.GetTransactionByTxID(filter.ID)
		if err != nil && filter.Unconfirmed {
			transaction, err = a.wallet.GetUnconfirmedTxByTxID(filter.ID)
			if err != nil {
				return NewErrorResponse(err)
			}
		}
		transactions = []*query.AnnotatedTx{transaction}
	} else {
		transactions, err = a.wallet.GetTransactions(filter.AccountID)
		if err != nil {
			return NewErrorResponse(err)
		}

		if filter.Unconfirmed {
			unconfirmedTxs, err := a.wallet.GetUnconfirmedTxs(filter.AccountID)
			if err != nil {
				return NewErrorResponse(err)
			}
			transactions = append(unconfirmedTxs, transactions...)
		}
	}

	if filter.Detail == false {
		txSummary := a.wallet.GetTransactionsSummary(transactions)
		start, end := getPageRange(len(txSummary), filter.From, filter.Count)
		return NewSuccessResponse(txSummary[start:end])
	}
	start, end := getPageRange(len(transactions), filter.From, filter.Count)
	return NewSuccessResponse(transactions[start:end])
}

// POST /get-unconfirmed-transaction
func (a *API) getUnconfirmedTx(ctx context.Context, filter struct {
	TxID chainjson.HexBytes `json:"tx_id"`
}) Response {
	var tmpTxID [32]byte
	copy(tmpTxID[:], filter.TxID[:])

	txHash := bc.NewHash(tmpTxID)
	txPool := a.chain.GetTxPool()
	txDesc, err := txPool.GetTransaction(&txHash)
	if err != nil {
		return NewErrorResponse(err)
	}

	tx := &BlockTx{
		ID:         txDesc.Tx.ID,
		Version:    txDesc.Tx.Version,
		Size:       txDesc.Tx.SerializedSize,
		TimeRange:  txDesc.Tx.TimeRange,
		Inputs:     []*query.AnnotatedInput{},
		Outputs:    []*query.AnnotatedOutput{},
		StatusFail: false,
	}

	for i := range txDesc.Tx.Inputs {
		tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(txDesc.Tx, uint32(i)))
	}
	for i := range txDesc.Tx.Outputs {
		tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(txDesc.Tx, i))
	}

	return NewSuccessResponse(tx)
}

type unconfirmedTxsResp struct {
	Total uint64    `json:"total"`
	TxIDs []bc.Hash `json:"tx_ids"`
}

// POST /list-unconfirmed-transactions
func (a *API) listUnconfirmedTxs(ctx context.Context) Response {
	txIDs := []bc.Hash{}

	txPool := a.chain.GetTxPool()
	txs := txPool.GetTransactions()
	for _, txDesc := range txs {
		txIDs = append(txIDs, bc.Hash(txDesc.Tx.ID))
	}

	return NewSuccessResponse(&unconfirmedTxsResp{
		Total: uint64(len(txIDs)),
		TxIDs: txIDs,
	})
}

// RawTx is the tx struct for getRawTransaction
type RawTx struct {
	ID        bc.Hash                  `json:"tx_id"`
	Version   uint64                   `json:"version"`
	Size      uint64                   `json:"size"`
	TimeRange uint64                   `json:"time_range"`
	Inputs    []*query.AnnotatedInput  `json:"inputs"`
	Outputs   []*query.AnnotatedOutput `json:"outputs"`
	Fee       int64                    `json:"fee"`
}

// POST /decode-raw-transaction
func (a *API) decodeRawTransaction(ctx context.Context, ins struct {
	Tx types.Tx `json:"raw_transaction"`
}) Response {
	tx := &RawTx{
		ID:        ins.Tx.ID,
		Version:   ins.Tx.Version,
		Size:      ins.Tx.SerializedSize,
		TimeRange: ins.Tx.TimeRange,
		Inputs:    []*query.AnnotatedInput{},
		Outputs:   []*query.AnnotatedOutput{},
	}

	for i := range ins.Tx.Inputs {
		tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(&ins.Tx, uint32(i)))
	}
	for i := range ins.Tx.Outputs {
		tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(&ins.Tx, i))
	}

	totalInputBtm := uint64(0)
	totalOutputBtm := uint64(0)
	for _, input := range tx.Inputs {
		if input.AssetID.String() == consensus.BTMAssetID.String() {
			totalInputBtm += input.Amount
		}
	}

	for _, output := range tx.Outputs {
		if output.AssetID.String() == consensus.BTMAssetID.String() {
			totalOutputBtm += output.Amount
		}
	}

	tx.Fee = int64(totalInputBtm) - int64(totalOutputBtm)
	return NewSuccessResponse(tx)
}

// POST /list-unspent-outputs
func (a *API) listUnspentOutputs(ctx context.Context, filter struct {
	ID            string `json:"id"`
	Unconfirmed   bool   `json:"unconfirmed"`
	SmartContract bool   `json:"smart_contract"`
	From          uint   `json:"from"`
	Count         uint   `json:"count"`
}) Response {
	accountUTXOs := a.wallet.GetAccountUtxos(filter.ID, filter.Unconfirmed, filter.SmartContract)

	UTXOs := []query.AnnotatedUTXO{}
	for _, utxo := range accountUTXOs {
		UTXOs = append([]query.AnnotatedUTXO{{
			AccountID:           utxo.AccountID,
			OutputID:            utxo.OutputID.String(),
			SourceID:            utxo.SourceID.String(),
			AssetID:             utxo.AssetID.String(),
			Amount:              utxo.Amount,
			SourcePos:           utxo.SourcePos,
			Program:             fmt.Sprintf("%x", utxo.ControlProgram),
			ControlProgramIndex: utxo.ControlProgramIndex,
			Address:             utxo.Address,
			ValidHeight:         utxo.ValidHeight,
			Alias:               a.wallet.AccountMgr.GetAliasByID(utxo.AccountID),
			AssetAlias:          a.wallet.AssetReg.GetAliasByID(utxo.AssetID.String()),
			Change:              utxo.Change,
		}}, UTXOs...)
	}
	start, end := getPageRange(len(UTXOs), filter.From, filter.Count)
	return NewSuccessResponse(UTXOs[start:end])
}

// return gasRate
func (a *API) gasRate() Response {
	gasrate := map[string]int64{"gas_rate": consensus.VMGasRate}
	return NewSuccessResponse(gasrate)
}

// PubKeyInfo is structure of pubkey info
type PubKeyInfo struct {
	Pubkey string               `json:"pubkey"`
	Path   []chainjson.HexBytes `json:"derivation_path"`
}

// AccountPubkey is detail of account pubkey info
type AccountPubkey struct {
	RootXPub    chainkd.XPub `json:"root_xpub"`
	PubKeyInfos []PubKeyInfo `json:"pubkey_infos"`
}

// POST /list-pubkeys
func (a *API) listPubKeys(ctx context.Context, ins struct {
	AccountID    string `json:"account_id"`
	AccountAlias string `json:"account_alias"`
	PublicKey    string `json:"public_key"`
}) Response {
	var err error
	account := &account.Account{}
	if ins.AccountAlias != "" {
		account, err = a.wallet.AccountMgr.FindByAlias(ins.AccountAlias)
	} else {
		account, err = a.wallet.AccountMgr.FindByID(ins.AccountID)
	}

	if err != nil {
		return NewErrorResponse(err)
	}

	pubKeyInfos := []PubKeyInfo{}
	idx := a.wallet.AccountMgr.GetContractIndex(account.ID)
	for i := uint64(1); i <= idx; i++ {
		rawPath := signers.Path(account.Signer, signers.AccountKeySpace, i)
		derivedXPub := account.XPubs[0].Derive(rawPath)
		pubkey := derivedXPub.PublicKey()

		if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(pubkey) {
			continue
		}

		var path []chainjson.HexBytes
		for _, p := range rawPath {
			path = append(path, chainjson.HexBytes(p))
		}

		pubKeyInfos = append(pubKeyInfos, PubKeyInfo{
			Pubkey: hex.EncodeToString(pubkey),
			Path:   path,
		})
	}

	if len(pubKeyInfos) == 0 {
		return NewErrorResponse(errors.New("Not found publickey for the account"))
	}

	return NewSuccessResponse(&AccountPubkey{
		RootXPub:    account.XPubs[0],
		PubKeyInfos: pubKeyInfos,
	})
}
<commit_msg>fix get-unconfirmed-transaction mux_id 0 bug (#1358)<commit_after>package api

import (
	"context"
	"encoding/hex"
	"fmt"

	log "github.com/sirupsen/logrus"

	"github.com/bytom/account"
	"github.com/bytom/blockchain/query"
	"github.com/bytom/blockchain/signers"
	"github.com/bytom/consensus"
	"github.com/bytom/crypto/ed25519/chainkd"
	chainjson "github.com/bytom/encoding/json"
	"github.com/bytom/errors"
	"github.com/bytom/protocol/bc"
	"github.com/bytom/protocol/bc/types"
)

// POST /list-accounts
func (a *API) listAccounts(ctx context.Context, filter struct {
	ID    string `json:"id"`
	Alias string `json:"alias"`
}) Response {
	accountID := filter.ID
	if filter.Alias != "" {
		acc, err := a.wallet.AccountMgr.FindByAlias(filter.Alias)
		if err != nil {
			return NewErrorResponse(err)
		}
		accountID = acc.ID
	}

	accounts, err := a.wallet.AccountMgr.ListAccounts(accountID)
	if err != nil {
		log.Errorf("listAccounts: %v", err)
		return NewErrorResponse(err)
	}

	annotatedAccounts := []query.AnnotatedAccount{}
	for _, acc := range accounts {
		annotatedAccounts = append(annotatedAccounts, *account.Annotated(acc))
	}

	return NewSuccessResponse(annotatedAccounts)
}

// POST /get-asset
func (a *API) getAsset(ctx context.Context, filter struct {
	ID string `json:"id"`
}) Response {
	asset, err := a.wallet.AssetReg.GetAsset(filter.ID)
	if err != nil {
		log.Errorf("getAsset: %v", err)
		return NewErrorResponse(err)
	}

	return NewSuccessResponse(asset)
}

// POST /list-assets
func (a *API) listAssets(ctx context.Context, filter struct {
	ID string `json:"id"`
}) Response {
	assets, err := a.wallet.AssetReg.ListAssets(filter.ID)
	if err != nil {
		log.Errorf("listAssets: %v", err)
		return NewErrorResponse(err)
	}

	return NewSuccessResponse(assets)
}

// POST /list-balances
func (a *API) listBalances(ctx context.Context) Response {
	balances, err := a.wallet.GetAccountBalances("")
	if err != nil {
		return NewErrorResponse(err)
	}
	return NewSuccessResponse(balances)
}

// POST /get-transaction
func (a *API) getTransaction(ctx context.Context, txInfo struct {
	TxID string `json:"tx_id"`
}) Response {
	var annotatedTx *query.AnnotatedTx
	var err error

	annotatedTx, err = a.wallet.GetTransactionByTxID(txInfo.TxID)
	if err != nil {
		// transaction not found in blockchain db, search it from unconfirmed db
		annotatedTx, err = a.wallet.GetUnconfirmedTxByTxID(txInfo.TxID)
		if err != nil {
			return NewErrorResponse(err)
		}
	}

	return NewSuccessResponse(annotatedTx)
}

// POST /list-transactions
func (a *API) listTransactions(ctx context.Context, filter struct {
	ID          string `json:"id"`
	AccountID   string `json:"account_id"`
	Detail      bool   `json:"detail"`
	Unconfirmed bool   `json:"unconfirmed"`
	From        uint   `json:"from"`
	Count       uint   `json:"count"`
}) Response {
	transactions := []*query.AnnotatedTx{}
	var err error
	var transaction *query.AnnotatedTx

	if filter.ID != "" {
		transaction, err = a.wallet.GetTransactionByTxID(filter.ID)
		if err != nil && filter.Unconfirmed {
			transaction, err = a.wallet.GetUnconfirmedTxByTxID(filter.ID)
			if err != nil {
				return NewErrorResponse(err)
			}
		}
		transactions = []*query.AnnotatedTx{transaction}
	} else {
		transactions, err = a.wallet.GetTransactions(filter.AccountID)
		if err != nil {
			return NewErrorResponse(err)
		}

		if filter.Unconfirmed {
			unconfirmedTxs, err := a.wallet.GetUnconfirmedTxs(filter.AccountID)
			if err != nil {
				return NewErrorResponse(err)
			}
			transactions = append(unconfirmedTxs, transactions...)
		}
	}

	if filter.Detail == false {
		txSummary := a.wallet.GetTransactionsSummary(transactions)
		start, end := getPageRange(len(txSummary), filter.From, filter.Count)
		return NewSuccessResponse(txSummary[start:end])
	}
	start, end := getPageRange(len(transactions), filter.From, filter.Count)
	return NewSuccessResponse(transactions[start:end])
}

// POST /get-unconfirmed-transaction
func (a *API) getUnconfirmedTx(ctx context.Context, filter struct {
	TxID chainjson.HexBytes `json:"tx_id"`
}) Response {
	var tmpTxID [32]byte
	copy(tmpTxID[:], filter.TxID[:])

	txHash := bc.NewHash(tmpTxID)
	txPool := a.chain.GetTxPool()
	txDesc, err := txPool.GetTransaction(&txHash)
	if err != nil {
		return NewErrorResponse(err)
	}

	tx := &BlockTx{
		ID:         txDesc.Tx.ID,
		Version:    txDesc.Tx.Version,
		Size:       txDesc.Tx.SerializedSize,
		TimeRange:  txDesc.Tx.TimeRange,
		Inputs:     []*query.AnnotatedInput{},
		Outputs:    []*query.AnnotatedOutput{},
		StatusFail: txDesc.StatusFail,
	}

	resOutID := txDesc.Tx.ResultIds[0]
	resOut := txDesc.Tx.Entries[*resOutID]
	switch out := resOut.(type) {
	case *bc.Output:
		tx.MuxID = *out.Source.Ref
	case *bc.Retirement:
		tx.MuxID = *out.Source.Ref
	}

	for i := range txDesc.Tx.Inputs {
		tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(txDesc.Tx, uint32(i)))
	}
	for i := range txDesc.Tx.Outputs {
		tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(txDesc.Tx, i))
	}

	return NewSuccessResponse(tx)
}

type unconfirmedTxsResp struct {
	Total uint64    `json:"total"`
	TxIDs []bc.Hash `json:"tx_ids"`
}

// POST /list-unconfirmed-transactions
func (a *API) listUnconfirmedTxs(ctx context.Context) Response {
	txIDs := []bc.Hash{}

	txPool := a.chain.GetTxPool()
	txs := txPool.GetTransactions()
	for _, txDesc := range txs {
		txIDs = append(txIDs, bc.Hash(txDesc.Tx.ID))
	}

	return NewSuccessResponse(&unconfirmedTxsResp{
		Total: uint64(len(txIDs)),
		TxIDs: txIDs,
	})
}

// RawTx is the tx struct for getRawTransaction
type RawTx struct {
	ID        bc.Hash                  `json:"tx_id"`
	Version   uint64                   `json:"version"`
	Size      uint64                   `json:"size"`
	TimeRange uint64                   `json:"time_range"`
	Inputs    []*query.AnnotatedInput  `json:"inputs"`
	Outputs   []*query.AnnotatedOutput `json:"outputs"`
	Fee       int64                    `json:"fee"`
}

// POST /decode-raw-transaction
func (a *API) decodeRawTransaction(ctx context.Context, ins struct {
	Tx types.Tx `json:"raw_transaction"`
}) Response {
	tx := &RawTx{
		ID:        ins.Tx.ID,
		Version:   ins.Tx.Version,
		Size:      ins.Tx.SerializedSize,
		TimeRange: ins.Tx.TimeRange,
		Inputs:    []*query.AnnotatedInput{},
		Outputs:   []*query.AnnotatedOutput{},
	}

	for i := range ins.Tx.Inputs {
		tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(&ins.Tx, uint32(i)))
	}
	for i := range ins.Tx.Outputs {
		tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(&ins.Tx, i))
	}

	totalInputBtm := uint64(0)
	totalOutputBtm := uint64(0)
	for _, input := range tx.Inputs {
		if input.AssetID.String() == consensus.BTMAssetID.String() {
			totalInputBtm += input.Amount
		}
	}

	for _, output := range tx.Outputs {
		if output.AssetID.String() == consensus.BTMAssetID.String() {
			totalOutputBtm += output.Amount
		}
	}

	tx.Fee = int64(totalInputBtm) - int64(totalOutputBtm)
	return NewSuccessResponse(tx)
}

// POST /list-unspent-outputs
func (a *API) listUnspentOutputs(ctx context.Context, filter struct {
	ID            string `json:"id"`
	Unconfirmed   bool   `json:"unconfirmed"`
	SmartContract bool   `json:"smart_contract"`
	From          uint   `json:"from"`
	Count         uint   `json:"count"`
}) Response {
	accountUTXOs := a.wallet.GetAccountUtxos(filter.ID, filter.Unconfirmed, filter.SmartContract)

	UTXOs := []query.AnnotatedUTXO{}
	for _, utxo := range accountUTXOs {
		UTXOs = append([]query.AnnotatedUTXO{{
			AccountID:           utxo.AccountID,
			OutputID:            utxo.OutputID.String(),
			SourceID:            utxo.SourceID.String(),
			AssetID:             utxo.AssetID.String(),
			Amount:              utxo.Amount,
			SourcePos:           utxo.SourcePos,
			Program:             fmt.Sprintf("%x", utxo.ControlProgram),
			ControlProgramIndex: utxo.ControlProgramIndex,
			Address:             utxo.Address,
			ValidHeight:         utxo.ValidHeight,
			Alias:               a.wallet.AccountMgr.GetAliasByID(utxo.AccountID),
			AssetAlias:          a.wallet.AssetReg.GetAliasByID(utxo.AssetID.String()),
			Change:              utxo.Change,
		}}, UTXOs...)
	}
	start, end := getPageRange(len(UTXOs), filter.From, filter.Count)
	return NewSuccessResponse(UTXOs[start:end])
}

// return gasRate
func (a *API) gasRate() Response {
	gasrate := map[string]int64{"gas_rate": consensus.VMGasRate}
	return NewSuccessResponse(gasrate)
}

// PubKeyInfo is structure of pubkey info
type PubKeyInfo struct {
	Pubkey string               `json:"pubkey"`
	Path   []chainjson.HexBytes `json:"derivation_path"`
}

// AccountPubkey is detail of account pubkey info
type AccountPubkey struct {
	RootXPub    chainkd.XPub `json:"root_xpub"`
	PubKeyInfos []PubKeyInfo `json:"pubkey_infos"`
}

// POST /list-pubkeys
func (a *API) listPubKeys(ctx context.Context, ins struct {
	AccountID    string `json:"account_id"`
	AccountAlias string `json:"account_alias"`
	PublicKey    string `json:"public_key"`
}) Response {
	var err error
	account := &account.Account{}
	if ins.AccountAlias != "" {
		account, err = a.wallet.AccountMgr.FindByAlias(ins.AccountAlias)
	} else {
		account, err = a.wallet.AccountMgr.FindByID(ins.AccountID)
	}

	if err != nil {
		return NewErrorResponse(err)
	}

	pubKeyInfos := []PubKeyInfo{}
	idx := a.wallet.AccountMgr.GetContractIndex(account.ID)
	for i := uint64(1); i <= idx; i++ {
		rawPath := signers.Path(account.Signer, signers.AccountKeySpace, i)
		derivedXPub := account.XPubs[0].Derive(rawPath)
		pubkey := derivedXPub.PublicKey()

		if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(pubkey) {
			continue
		}

		var path []chainjson.HexBytes
		for _, p := range rawPath {
			path = append(path, chainjson.HexBytes(p))
		}

		pubKeyInfos = append(pubKeyInfos, PubKeyInfo{
			Pubkey: hex.EncodeToString(pubkey),
			Path:   path,
		})
	}

	if len(pubKeyInfos) == 0 {
		return NewErrorResponse(errors.New("Not found publickey for the account"))
	}

	return NewSuccessResponse(&AccountPubkey{
		RootXPub:    account.XPubs[0],
		PubKeyInfos: pubKeyInfos,
	})
}
<|endoftext|>


<commit_before>package couchdb

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
)

// This file contains error handling code for couchdb request
// Possible errors in connecting to couchdb
// 503 Service Unavailable when the stack cant connect to couchdb or when
// 		 couchdb response is interrupted mid-stream
// 500 When the viper provided configuration does not allow us to properly
// 		 call http.newRequest, ie. wrong couchdbURL config

// Possible native couchdb errors
// 400 Bad Request : Bad request structure. The error can indicate an error
// 		with the request URL, path or headers. Differences in the supplied MD5
// 		hash and content also trigger this error, as this may indicate message
// 		corruption.
// 401 Unauthorized : The item requested was not available using the supplied
// 		authorization, or authorization was not supplied.
// 		{"error":"unauthorized","reason":"You are not a server admin."}
// 		{"error":"unauthorized","reason":"Name or password is incorrect."}
// 403 Forbidden : The requested item or operation is forbidden.
// 404 Not Found : The requested content could not be found. The content will
// 		include further information, as a JSON object, if available.
// 		**The structure will contain two keys, error and reason.**
//    {"error":"not_found","reason":"deleted"}
//    {"error":"not_found","reason":"missing"}
//    {"error":"not_found","reason":"no_db_file"}
// 405 Resource Not Allowed : A request was made using an invalid HTTP request
// 		type for the URL requested. For example, you have requested a PUT when a
// 		POST is required. Errors of this type can also triggered by invalid URL
// 		strings.
// 406 Not Acceptable : The requested content type is not supported by the
// 		server.
// 409 Conflict : Request resulted in an update conflict.
// 		{"error":"conflict","reason":"Document update conflict."}
// 412 Precondition Failed : The request headers from the client and the
// 		capabilities of the server do not match.
// 415 Bad Content Type : The content consts.supported, and the content type of
// 		the information being requested or submitted indicate that the content
// 		type is not supported.
// 416 Requested Range Not Satisfiable : The range specified in the request
// 		header cannot be satisfied by the server.
// 417 Expectation Failed : When sending documents in bulk, the bulk load
// 		operation failed.
// 500 Internal Server Error : The request was invalid, either because the
// 		supplied JSON was invalid, or invalid information was supplied as part
// 		of the request.

// Error represent an error from couchdb
type Error struct {
	StatusCode  int
	CouchdbJSON []byte `json:"-"`
	Name        string `json:"error"`
	Reason      string `json:"reason"`
	Original    error
}

func (e *Error) Error() string {
	return fmt.Sprintf("CouchDB: %d %s (%s)", e.StatusCode, e.Name, e.Reason)
}

// JSON returns the json representation of this error
func (e *Error) JSON() map[string]interface{} {
	jsonMap := map[string]interface{}{
		"ok":     false,
		"status": strconv.Itoa(e.StatusCode),
		"error":  e.Name,
		"reason": e.Reason,
	}
	if e.Original != nil {
		jsonMap["original"] = e.Original.Error()
	}
	return jsonMap
}

// IsNoDatabaseError checks if the given error is a couch no_db_file
// error
func IsNoDatabaseError(err error) bool {
	if err == nil {
		return false
	}
	couchErr, isCouchErr := err.(*Error)
	if !isCouchErr {
		return false
	}
	return couchErr.Reason == "no_db_file" ||
		couchErr.Reason == "Database does not exist."
}

// IsNotFoundError checks if the given error is a couch not_found
// error
func IsNotFoundError(err error) bool {
	if err == nil {
		return false
	}
	couchErr, isCouchErr := err.(*Error)
	if !isCouchErr {
		return false
	}
	return couchErr.Name == "not_found"
}

// IsConflictError checks if the given error is a couch conflict error
func IsConflictError(err error) bool {
	if err == nil {
		return false
	}
	couchErr, isCouchErr := err.(*Error)
	if !isCouchErr {
		return false
	}
	return couchErr.StatusCode == 409
}

func newRequestError(originalError error) error {
	return &Error{
		StatusCode: http.StatusServiceUnavailable,
		Name:       "no_couch",
		Reason:     "wrong_config",
		Original:   originalError,
	}
}

func newConnectionError(originalError error) error {
	return &Error{
		StatusCode: http.StatusServiceUnavailable,
		Name:       "no_couch",
		Reason:     "cant_connect",
		Original:   originalError,
	}
}

func newIOReadError(originalError error) error {
	return &Error{
		StatusCode: http.StatusServiceUnavailable,
		Name:       "no_couch",
		Reason:     "hangup",
		Original:   originalError,
	}
}

func unoptimalError() error {
	return &Error{
		StatusCode: http.StatusBadRequest,
		Name:       "no_index",
		Reason:     "no matching index found, create an index",
	}
}

func newCouchdbError(statusCode int, couchdbJSON []byte) error {
	var err = &Error{
		CouchdbJSON: couchdbJSON,
	}
	parseErr := json.Unmarshal(couchdbJSON, err)
	if parseErr != nil {
		err.Name = "wrong_json"
		err.Reason = parseErr.Error()
	}
	err.StatusCode = statusCode
	return err
}
<commit_msg>Bad replace<commit_after>package couchdb

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
)

// This file contains error handling code for couchdb request
// Possible errors in connecting to couchdb
// 503 Service Unavailable when the stack cant connect to couchdb or when
// 		 couchdb response is interrupted mid-stream
// 500 When the viper provided configuration does not allow us to properly
// 		 call http.newRequest, ie. wrong couchdbURL config

// Possible native couchdb errors
// 400 Bad Request : Bad request structure. The error can indicate an error
// 		with the request URL, path or headers. Differences in the supplied MD5
// 		hash and content also trigger this error, as this may indicate message
// 		corruption.
// 401 Unauthorized : The item requested was not available using the supplied
// 		authorization, or authorization was not supplied.
// 		{"error":"unauthorized","reason":"You are not a server admin."}
// 		{"error":"unauthorized","reason":"Name or password is incorrect."}
// 403 Forbidden : The requested item or operation is forbidden.
// 404 Not Found : The requested content could not be found. The content will
// 		include further information, as a JSON object, if available.
// 		**The structure will contain two keys, error and reason.**
//    {"error":"not_found","reason":"deleted"}
//    {"error":"not_found","reason":"missing"}
//    {"error":"not_found","reason":"no_db_file"}
// 405 Resource Not Allowed : A request was made using an invalid HTTP request
// 		type for the URL requested. For example, you have requested a PUT when a
// 		POST is required. Errors of this type can also triggered by invalid URL
// 		strings.
// 406 Not Acceptable : The requested content type is not supported by the
// 		server.
// 409 Conflict : Request resulted in an update conflict.
// 		{"error":"conflict","reason":"Document update conflict."}
// 412 Precondition Failed : The request headers from the client and the
// 		capabilities of the server do not match.
// 415 Bad Content Type : The content types supported, and the content type of
// 		the information being requested or submitted indicate that the content
// 		type is not supported.
// 416 Requested Range Not Satisfiable : The range specified in the request
// 		header cannot be satisfied by the server.
// 417 Expectation Failed : When sending documents in bulk, the bulk load
// 		operation failed.
// 500 Internal Server Error : The request was invalid, either because the
// 		supplied JSON was invalid, or invalid information was supplied as part
// 		of the request.

// Error represent an error from couchdb
type Error struct {
	StatusCode  int
	CouchdbJSON []byte `json:"-"`
	Name        string `json:"error"`
	Reason      string `json:"reason"`
	Original    error
}

func (e *Error) Error() string {
	return fmt.Sprintf("CouchDB: %d %s (%s)", e.StatusCode, e.Name, e.Reason)
}

// JSON returns the json representation of this error
func (e *Error) JSON() map[string]interface{} {
	jsonMap := map[string]interface{}{
		"ok":     false,
		"status": strconv.Itoa(e.StatusCode),
		"error":  e.Name,
		"reason": e.Reason,
	}
	if e.Original != nil {
		jsonMap["original"] = e.Original.Error()
	}
	return jsonMap
}

// IsNoDatabaseError checks if the given error is a couch no_db_file
// error
func IsNoDatabaseError(err error) bool {
	if err == nil {
		return false
	}
	couchErr, isCouchErr := err.(*Error)
	if !isCouchErr {
		return false
	}
	return couchErr.Reason == "no_db_file" ||
		couchErr.Reason == "Database does not exist."
}

// IsNotFoundError checks if the given error is a couch not_found
// error
func IsNotFoundError(err error) bool {
	if err == nil {
		return false
	}
	couchErr, isCouchErr := err.(*Error)
	if !isCouchErr {
		return false
	}
	return couchErr.Name == "not_found"
}

// IsConflictError checks if the given error is a couch conflict error
func IsConflictError(err error) bool {
	if err == nil {
		return false
	}
	couchErr, isCouchErr := err.(*Error)
	if !isCouchErr {
		return false
	}
	return couchErr.StatusCode == 409
}

func newRequestError(originalError error) error {
	return &Error{
		StatusCode: http.StatusServiceUnavailable,
		Name:       "no_couch",
		Reason:     "wrong_config",
		Original:   originalError,
	}
}

func newConnectionError(originalError error) error {
	return &Error{
		StatusCode: http.StatusServiceUnavailable,
		Name:       "no_couch",
		Reason:     "cant_connect",
		Original:   originalError,
	}
}

func newIOReadError(originalError error) error {
	return &Error{
		StatusCode: http.StatusServiceUnavailable,
		Name:       "no_couch",
		Reason:     "hangup",
		Original:   originalError,
	}
}

func unoptimalError() error {
	return &Error{
		StatusCode: http.StatusBadRequest,
		Name:       "no_index",
		Reason:     "no matching index found, create an index",
	}
}

func newCouchdbError(statusCode int, couchdbJSON []byte) error {
	var err = &Error{
		CouchdbJSON: couchdbJSON,
	}
	parseErr := json.Unmarshal(couchdbJSON, err)
	if parseErr != nil {
		err.Name = "wrong_json"
		err.Reason = parseErr.Error()
	}
	err.StatusCode = statusCode
	return err
}
<|endoftext|>


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

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

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

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

package drivers

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"

	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/log"
	"github.com/docker/machine/libmachine/mcnflag"
	"github.com/docker/machine/libmachine/mcnutils"
	"github.com/docker/machine/libmachine/ssh"
	"github.com/pkg/errors"

	"k8s.io/klog/v2"
	"k8s.io/minikube/pkg/util"
)

const (
	// LeasesPath is the path to dhcpd leases
	LeasesPath = "/var/db/dhcpd_leases"
)

var (
	leadingZeroRegexp = regexp.MustCompile(`0([A-Fa-f0-9](:|$))`)
)

// This file is for common code shared among internal machine drivers
// Code here should not be called from within minikube

// GetDiskPath returns the path of the machine disk image
func GetDiskPath(d *drivers.BaseDriver) string {
	return filepath.Join(d.ResolveStorePath("."), d.GetMachineName()+".rawdisk")
}

// ExtraDiskPath returns the path of an additional disk suffixed with an ID.
func ExtraDiskPath(d *drivers.BaseDriver, diskID int) string {
	file := fmt.Sprintf("%s-%d.rawdisk", d.GetMachineName(), diskID)
	return filepath.Join(d.ResolveStorePath("."), file)
}

// CommonDriver is the common driver base class
type CommonDriver struct{}

// GetCreateFlags is not implemented yet
func (d *CommonDriver) GetCreateFlags() []mcnflag.Flag {
	return nil
}

// SetConfigFromFlags is not implemented yet
func (d *CommonDriver) SetConfigFromFlags(flags drivers.DriverOptions) error {
	return nil
}

func createRawDiskImage(sshKeyPath, diskPath string, diskSizeMb int) error {
	tarBuf, err := mcnutils.MakeDiskImage(sshKeyPath)
	if err != nil {
		return errors.Wrap(err, "make disk image")
	}

	file, err := os.OpenFile(diskPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
	if err != nil {
		return errors.Wrap(err, "open")
	}
	defer file.Close()
	if _, err := file.Seek(0, io.SeekStart); err != nil {
		return errors.Wrap(err, "seek")
	}

	if _, err := file.Write(tarBuf.Bytes()); err != nil {
		return errors.Wrap(err, "write tar")
	}
	if err := file.Close(); err != nil {
		return errors.Wrapf(err, "closing file %s", diskPath)
	}

	if err := os.Truncate(diskPath, util.ConvertMBToBytes(diskSizeMb)); err != nil {
		return errors.Wrap(err, "truncate")
	}
	return nil
}

func publicSSHKeyPath(d *drivers.BaseDriver) string {
	return d.GetSSHKeyPath() + ".pub"
}

// Restart a host. This may just call Stop(); Start() if the provider does not
// have any special restart behaviour.
func Restart(d drivers.Driver) error {
	if err := d.Stop(); err != nil {
		return err
	}

	return d.Start()
}

// MakeDiskImage makes a boot2docker VM disk image.
func MakeDiskImage(d *drivers.BaseDriver, boot2dockerURL string, diskSize int) error {
	klog.Infof("Making disk image using store path: %s", d.StorePath)
	b2 := mcnutils.NewB2dUtils(d.StorePath)
	if err := b2.CopyIsoToMachineDir(boot2dockerURL, d.MachineName); err != nil {
		return errors.Wrap(err, "copy iso to machine dir")
	}

	keyPath := d.GetSSHKeyPath()
	klog.Infof("Creating ssh key: %s...", keyPath)
	if err := ssh.GenerateSSHKey(keyPath); err != nil {
		return errors.Wrap(err, "generate ssh key")
	}

	diskPath := GetDiskPath(d)
	klog.Infof("Creating raw disk image: %s...", diskPath)
	if _, err := os.Stat(diskPath); os.IsNotExist(err) {
		if err := createRawDiskImage(publicSSHKeyPath(d), diskPath, diskSize); err != nil {
			return errors.Wrapf(err, "createRawDiskImage(%s)", diskPath)
		}
		machPath := d.ResolveStorePath(".")
		if err := fixMachinePermissions(machPath); err != nil {
			return errors.Wrapf(err, "fixing permissions on %s", machPath)
		}
	}
	return nil
}

func fixMachinePermissions(path string) error {
	klog.Infof("Fixing permissions on %s ...", path)
	if err := os.Chown(path, syscall.Getuid(), syscall.Getegid()); err != nil {
		return errors.Wrap(err, "chown dir")
	}
	files, err := os.ReadDir(path)
	if err != nil {
		return errors.Wrap(err, "read dir")
	}
	for _, f := range files {
		fp := filepath.Join(path, f.Name())
		if err := os.Chown(fp, syscall.Getuid(), syscall.Getegid()); err != nil {
			return errors.Wrap(err, "chown file")
		}
	}
	return nil
}

// DHCPEntry holds a parsed DNS entry
type DHCPEntry struct {
	Name      string
	IPAddress string
	HWAddress string
	ID        string
	Lease     string
}

// GetIPAddressByMACAddress gets the IP address of a MAC address
func GetIPAddressByMACAddress(mac string) (string, error) {
	return getIPAddressFromFile(mac, LeasesPath)
}

func getIPAddressFromFile(mac, path string) (string, error) {
	log.Debugf("Searching for %s in %s ...", mac, path)
	file, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer file.Close()

	dhcpEntries, err := parseDHCPdLeasesFile(file)
	if err != nil {
		return "", err
	}
	log.Debugf("Found %d entries in %s!", len(dhcpEntries), path)
	for _, dhcpEntry := range dhcpEntries {
		log.Debugf("dhcp entry: %+v", dhcpEntry)
		if dhcpEntry.HWAddress == mac {
			log.Debugf("Found match: %s", mac)
			return dhcpEntry.IPAddress, nil
		}
	}
	return "", fmt.Errorf("could not find an IP address for %s", mac)
}

func parseDHCPdLeasesFile(file io.Reader) ([]DHCPEntry, error) {
	var (
		dhcpEntry   *DHCPEntry
		dhcpEntries []DHCPEntry
	)

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "{" {
			dhcpEntry = new(DHCPEntry)
			continue
		} else if line == "}" {
			dhcpEntries = append(dhcpEntries, *dhcpEntry)
			continue
		}

		split := strings.SplitN(line, "=", 2)
		if len(split) != 2 {
			return nil, fmt.Errorf("invalid line in dhcp leases file: %s", line)
		}
		key, val := split[0], split[1]
		switch key {
		case "name":
			dhcpEntry.Name = val
		case "ip_address":
			dhcpEntry.IPAddress = val
		case "hw_address":
			// The mac addresses have a '1,' at the start.
			dhcpEntry.HWAddress = val[2:]
		case "identifier":
			dhcpEntry.ID = val
		case "lease":
			dhcpEntry.Lease = val
		default:
			return dhcpEntries, fmt.Errorf("unable to parse line: %s", line)
		}
	}
	return dhcpEntries, scanner.Err()
}

// TrimMacAddress trimming "0" of the ten's digit
func TrimMacAddress(rawUUID string) string {
	return leadingZeroRegexp.ReplaceAllString(rawUUID, "$1")
}
<commit_msg>inline<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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

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

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

package drivers

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"

	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/log"
	"github.com/docker/machine/libmachine/mcnflag"
	"github.com/docker/machine/libmachine/mcnutils"
	"github.com/docker/machine/libmachine/ssh"
	"github.com/pkg/errors"

	"k8s.io/klog/v2"
	"k8s.io/minikube/pkg/util"
)

// LeasesPath is the path to dhcpd leases
const LeasesPath = "/var/db/dhcpd_leases"

var leadingZeroRegexp = regexp.MustCompile(`0([A-Fa-f0-9](:|$))`)

// This file is for common code shared among internal machine drivers
// Code here should not be called from within minikube

// GetDiskPath returns the path of the machine disk image
func GetDiskPath(d *drivers.BaseDriver) string {
	return filepath.Join(d.ResolveStorePath("."), d.GetMachineName()+".rawdisk")
}

// ExtraDiskPath returns the path of an additional disk suffixed with an ID.
func ExtraDiskPath(d *drivers.BaseDriver, diskID int) string {
	file := fmt.Sprintf("%s-%d.rawdisk", d.GetMachineName(), diskID)
	return filepath.Join(d.ResolveStorePath("."), file)
}

// CommonDriver is the common driver base class
type CommonDriver struct{}

// GetCreateFlags is not implemented yet
func (d *CommonDriver) GetCreateFlags() []mcnflag.Flag {
	return nil
}

// SetConfigFromFlags is not implemented yet
func (d *CommonDriver) SetConfigFromFlags(flags drivers.DriverOptions) error {
	return nil
}

func createRawDiskImage(sshKeyPath, diskPath string, diskSizeMb int) error {
	tarBuf, err := mcnutils.MakeDiskImage(sshKeyPath)
	if err != nil {
		return errors.Wrap(err, "make disk image")
	}

	file, err := os.OpenFile(diskPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
	if err != nil {
		return errors.Wrap(err, "open")
	}
	defer file.Close()
	if _, err := file.Seek(0, io.SeekStart); err != nil {
		return errors.Wrap(err, "seek")
	}

	if _, err := file.Write(tarBuf.Bytes()); err != nil {
		return errors.Wrap(err, "write tar")
	}
	if err := file.Close(); err != nil {
		return errors.Wrapf(err, "closing file %s", diskPath)
	}

	if err := os.Truncate(diskPath, util.ConvertMBToBytes(diskSizeMb)); err != nil {
		return errors.Wrap(err, "truncate")
	}
	return nil
}

func publicSSHKeyPath(d *drivers.BaseDriver) string {
	return d.GetSSHKeyPath() + ".pub"
}

// Restart a host. This may just call Stop(); Start() if the provider does not
// have any special restart behaviour.
func Restart(d drivers.Driver) error {
	if err := d.Stop(); err != nil {
		return err
	}

	return d.Start()
}

// MakeDiskImage makes a boot2docker VM disk image.
func MakeDiskImage(d *drivers.BaseDriver, boot2dockerURL string, diskSize int) error {
	klog.Infof("Making disk image using store path: %s", d.StorePath)
	b2 := mcnutils.NewB2dUtils(d.StorePath)
	if err := b2.CopyIsoToMachineDir(boot2dockerURL, d.MachineName); err != nil {
		return errors.Wrap(err, "copy iso to machine dir")
	}

	keyPath := d.GetSSHKeyPath()
	klog.Infof("Creating ssh key: %s...", keyPath)
	if err := ssh.GenerateSSHKey(keyPath); err != nil {
		return errors.Wrap(err, "generate ssh key")
	}

	diskPath := GetDiskPath(d)
	klog.Infof("Creating raw disk image: %s...", diskPath)
	if _, err := os.Stat(diskPath); os.IsNotExist(err) {
		if err := createRawDiskImage(publicSSHKeyPath(d), diskPath, diskSize); err != nil {
			return errors.Wrapf(err, "createRawDiskImage(%s)", diskPath)
		}
		machPath := d.ResolveStorePath(".")
		if err := fixMachinePermissions(machPath); err != nil {
			return errors.Wrapf(err, "fixing permissions on %s", machPath)
		}
	}
	return nil
}

func fixMachinePermissions(path string) error {
	klog.Infof("Fixing permissions on %s ...", path)
	if err := os.Chown(path, syscall.Getuid(), syscall.Getegid()); err != nil {
		return errors.Wrap(err, "chown dir")
	}
	files, err := os.ReadDir(path)
	if err != nil {
		return errors.Wrap(err, "read dir")
	}
	for _, f := range files {
		fp := filepath.Join(path, f.Name())
		if err := os.Chown(fp, syscall.Getuid(), syscall.Getegid()); err != nil {
			return errors.Wrap(err, "chown file")
		}
	}
	return nil
}

// DHCPEntry holds a parsed DNS entry
type DHCPEntry struct {
	Name      string
	IPAddress string
	HWAddress string
	ID        string
	Lease     string
}

// GetIPAddressByMACAddress gets the IP address of a MAC address
func GetIPAddressByMACAddress(mac string) (string, error) {
	return getIPAddressFromFile(mac, LeasesPath)
}

func getIPAddressFromFile(mac, path string) (string, error) {
	log.Debugf("Searching for %s in %s ...", mac, path)
	file, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer file.Close()

	dhcpEntries, err := parseDHCPdLeasesFile(file)
	if err != nil {
		return "", err
	}
	log.Debugf("Found %d entries in %s!", len(dhcpEntries), path)
	for _, dhcpEntry := range dhcpEntries {
		log.Debugf("dhcp entry: %+v", dhcpEntry)
		if dhcpEntry.HWAddress == mac {
			log.Debugf("Found match: %s", mac)
			return dhcpEntry.IPAddress, nil
		}
	}
	return "", fmt.Errorf("could not find an IP address for %s", mac)
}

func parseDHCPdLeasesFile(file io.Reader) ([]DHCPEntry, error) {
	var (
		dhcpEntry   *DHCPEntry
		dhcpEntries []DHCPEntry
	)

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "{" {
			dhcpEntry = new(DHCPEntry)
			continue
		} else if line == "}" {
			dhcpEntries = append(dhcpEntries, *dhcpEntry)
			continue
		}

		split := strings.SplitN(line, "=", 2)
		if len(split) != 2 {
			return nil, fmt.Errorf("invalid line in dhcp leases file: %s", line)
		}
		key, val := split[0], split[1]
		switch key {
		case "name":
			dhcpEntry.Name = val
		case "ip_address":
			dhcpEntry.IPAddress = val
		case "hw_address":
			// The mac addresses have a '1,' at the start.
			dhcpEntry.HWAddress = val[2:]
		case "identifier":
			dhcpEntry.ID = val
		case "lease":
			dhcpEntry.Lease = val
		default:
			return dhcpEntries, fmt.Errorf("unable to parse line: %s", line)
		}
	}
	return dhcpEntries, scanner.Err()
}

// TrimMacAddress trimming "0" of the ten's digit
func TrimMacAddress(rawUUID string) string {
	return leadingZeroRegexp.ReplaceAllString(rawUUID, "$1")
}
<|endoftext|>