<commit_before>package gocd

import (
	"bytes"
	"context"
	"fmt"
	log "github.com/Sirupsen/logrus"
)

// PropertiesService describes Actions which can be performed on agents
type PropertiesService service

// PropertyRequest describes the parameters to be submitted when calling/creating properties.
type PropertyRequest struct {
	Pipeline        string
	PipelineCounter int
	Stage           string
	StageCounter    int
	Job             string
	LimitPipeline   string
	Limit           int
	Single          bool
}

// PropertyCreateResponse handles the parsing of the response when creating a property
type PropertyCreateResponse struct {
	Name  string
	Value string
}

// List the properties for the given job/pipeline/stage run.
func (ps *PropertiesService) List(ctx context.Context, pr *PropertyRequest) (*Properties, *APIResponse, error) {
	path := fmt.Sprintf("/properties/%s/%d/%s/%d/%s",
		pr.Pipeline, pr.PipelineCounter,
		pr.Stage, pr.StageCounter,
		pr.Job,
	)
	log.Info("Calling `PropertiesServices.List`")
	return ps.commonPropertiesAction(ctx, path, pr.Single)
}

// Get a specific property for the given job/pipeline/stage run.
func (ps *PropertiesService) Get(ctx context.Context, name string, pr *PropertyRequest) (*Properties, *APIResponse, error) {
	path := fmt.Sprintf("/properties/%s/%d/%s/%d/%s/%s",
		pr.Pipeline, pr.PipelineCounter,
		pr.Stage, pr.StageCounter,
		pr.Job, name,
	)
	return ps.commonPropertiesAction(ctx, path, true)
}

// Create a specific property for the given job/pipeline/stage run.
func (ps *PropertiesService) Create(ctx context.Context, name string, value string, pr *PropertyRequest) (bool, *APIResponse, error) {
	path := fmt.Sprintf("/properties/%s/%d/%s/%d/%s/%s",
		pr.Pipeline, pr.PipelineCounter,
		pr.Stage, pr.StageCounter,
		pr.Job, name,
	)

	log.Info("Calling `PropertiesServices.Create`")
	responseBuffer := bytes.NewBuffer([]byte(""))
	_, resp, err := ps.client.postAction(ctx, &APIClientRequest{
		Path:         path,
		ResponseType: responseTypeText,
		ResponseBody: responseBuffer,
		RequestBody:  fmt.Sprintf("%s=%s", name, value),
		Headers: map[string]string{
			"Confirm": "true",
		},
	})
	responseString := responseBuffer.String()
	resp.Body = responseString

	r := fmt.Sprintf("Property '%s' created with value '%s'", name, value)

	return responseString == r, resp, err
}

// ListHistorical properties for a given pipeline, stage, job.
func (ps *PropertiesService) ListHistorical(ctx context.Context, pr *PropertyRequest) (*Properties, *APIResponse, error) {
	u := ps.client.BaseURL
	q := u.Query()
	q.Set("pipelineName", pr.Pipeline)
	q.Set("stageName", pr.Stage)
	q.Set("jobName", pr.Job)
	if pr.Limit >= 0 && pr.LimitPipeline != "" {
		q.Set("limitCount", fmt.Sprintf("%d", pr.Limit))
		q.Set("limitPipeline", pr.LimitPipeline)
	}
	u.RawQuery = q.Encode()
	return ps.commonPropertiesAction(ctx, "/properties/search", false)
}

func (ps *PropertiesService) commonPropertiesAction(ctx context.Context, path string, isDatum bool) (*Properties, *APIResponse, error) {
	p := Properties{
		UnmarshallWithHeader: true,
		IsDatum:              isDatum,
	}
	_, resp, err := ps.client.getAction(ctx, &APIClientRequest{
		Path:         path,
		ResponseBody: &p,
	})

	return &p, resp, err
}
<commit_msg>Reduced number of assignments<commit_after>package gocd

import (
	"bytes"
	"context"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"strings"
)

// PropertiesService describes Actions which can be performed on agents
type PropertiesService service

// PropertyRequest describes the parameters to be submitted when calling/creating properties.
type PropertyRequest struct {
	Pipeline        string
	PipelineCounter int
	Stage           string
	StageCounter    int
	Job             string
	LimitPipeline   string
	Limit           int
	Single          bool
}

// PropertyCreateResponse handles the parsing of the response when creating a property
type PropertyCreateResponse struct {
	Name  string
	Value string
}

// List the properties for the given job/pipeline/stage run.
func (ps *PropertiesService) List(ctx context.Context, pr *PropertyRequest) (*Properties, *APIResponse, error) {
	path := fmt.Sprintf("/properties/%s/%d/%s/%d/%s",
		pr.Pipeline, pr.PipelineCounter,
		pr.Stage, pr.StageCounter,
		pr.Job,
	)
	log.Info("Calling `PropertiesServices.List`")
	return ps.commonPropertiesAction(ctx, path, pr.Single)
}

// Get a specific property for the given job/pipeline/stage run.
func (ps *PropertiesService) Get(ctx context.Context, name string, pr *PropertyRequest) (*Properties, *APIResponse, error) {
	path := fmt.Sprintf("/properties/%s/%d/%s/%d/%s/%s",
		pr.Pipeline, pr.PipelineCounter,
		pr.Stage, pr.StageCounter,
		pr.Job, name,
	)
	return ps.commonPropertiesAction(ctx, path, true)
}

// Create a specific property for the given job/pipeline/stage run.
func (ps *PropertiesService) Create(ctx context.Context, name string, value string, pr *PropertyRequest) (bool, *APIResponse, error) {

	log.Info("Calling `PropertiesServices.Create`")
	responseBuffer := bytes.NewBuffer([]byte(""))
	_, resp, err := ps.client.postAction(ctx, &APIClientRequest{
		Path: fmt.Sprintf("/properties/%s/%d/%s/%d/%s/%s",
			pr.Pipeline, pr.PipelineCounter,
			pr.Stage, pr.StageCounter,
			pr.Job, name,
		),
		ResponseType: responseTypeText,
		ResponseBody: responseBuffer,
		RequestBody: strings.Join(
			[]string{name, value},
			"=",
		),
		Headers: map[string]string{
			"Confirm": "true",
		},
	})
	resp.Body = responseBuffer.String()
	responseIsValid := resp.Body == fmt.Sprintf("Property '%s' created with value '%s'", name, value)

	return responseIsValid, resp, err
}

// ListHistorical properties for a given pipeline, stage, job.
func (ps *PropertiesService) ListHistorical(ctx context.Context, pr *PropertyRequest) (*Properties, *APIResponse, error) {
	u := ps.client.BaseURL
	q := u.Query()
	q.Set("pipelineName", pr.Pipeline)
	q.Set("stageName", pr.Stage)
	q.Set("jobName", pr.Job)
	if pr.Limit >= 0 && pr.LimitPipeline != "" {
		q.Set("limitCount", fmt.Sprintf("%d", pr.Limit))
		q.Set("limitPipeline", pr.LimitPipeline)
	}
	u.RawQuery = q.Encode()
	return ps.commonPropertiesAction(ctx, "/properties/search", false)
}

func (ps *PropertiesService) commonPropertiesAction(ctx context.Context, path string, isDatum bool) (*Properties, *APIResponse, error) {
	p := Properties{
		UnmarshallWithHeader: true,
		IsDatum:              isDatum,
	}
	_, resp, err := ps.client.getAction(ctx, &APIClientRequest{
		Path:         path,
		ResponseBody: &p,
	})

	return &p, resp, err
}
<|endoftext|>


<commit_before>/*
Copyright 2015 Google Inc. 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 arcanist

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/google/git-phabricator-mirror/mirror/repository"
	"github.com/google/git-phabricator-mirror/mirror/review/request"
	"log"
	"sort"
	"strconv"
)

type differentialCreateRawDiffRequest struct {
	Diff string `json:"diff"`
}

type rawDiff struct {
	ID int `json:"id"`
}

type differentialCreateRawDiffResponse struct {
	Error        string  `json:"error,omitempty"`
	ErrorMessage string  `json:"errorMessage,omitempty"`
	Response     rawDiff `json:"response,omitempty"`
}

type differentialQueryDiffsRequest struct {
	IDs []int `json:"ids"`
}

type queryDiffItem struct {
	ID         string        `json:"id"`
	Changes    []interface{} `json:"changes"`
	Properties interface{}   `json:"properties"`
}

type differentialQueryDiffsResponse struct {
	Error        string                   `json:"error,omitempty"`
	ErrorMessage string                   `json:"errorMessage,omitempty"`
	Response     map[string]queryDiffItem `json:"response"`
}

func readDiff(diffID int) (*queryDiffItem, error) {
	queryRequest := differentialQueryDiffsRequest{IDs: []int{diffID}}
	var queryResponse differentialQueryDiffsResponse
	runArcCommandOrDie("differential.querydiffs", queryRequest, &queryResponse)
	if queryResponse.Error != "" {
		return nil, fmt.Errorf(queryResponse.ErrorMessage)
	}
	if diff, ok := queryResponse.Response[strconv.Itoa(diffID)]; ok {
		return &diff, nil
	}
	return nil, nil
}

// Differential does not actually store the commit hash for the right hand side of a diff.
// As such, if we have to do some deep inspection to find it. What Differential *does*
// store is a map of "local commits". The last such local commit (by timestamp) is the
// one that was actually used to generate the right hand side of the diff.
func findLastCommit(commitsMap map[string]interface{}) string {
	var timestamps []int
	timestampCommitMap := make(map[int]string)
	for commit, commitData := range commitsMap {
		commitProperties, ok := commitData.(map[string]interface{})
		if ok {
			timestampString, ok := commitProperties["time"].(string)
			if ok {
				timestamp, err := strconv.Atoi(timestampString)
				if err != nil {
					log.Fatal(err)
				}
				timestamps = append(timestamps, timestamp)
				timestampCommitMap[timestamp] = commit
			}
		}
	}
	if len(timestamps) == 0 {
		return ""
	}
	sort.Sort(sort.Reverse(sort.IntSlice(timestamps)))
	return timestampCommitMap[timestamps[0]]
}

// findLastCommit returns the last commit included in a Differential diff.
func (diff *queryDiffItem) findLastCommit() string {
	propertiesMap, ok := diff.Properties.(map[string]interface{})
	if ok {
		commitsMap, ok := propertiesMap["local:commits"].(map[string]interface{})
		if ok {
			return findLastCommit(commitsMap)
		}
	}
	return ""
}

// getDiffChanges takes two revisions from which to generate a "git diff", and returns a
// slice of "changes" objects that represent that diff as parsed by Phabricator.
func (arc Arcanist) getDiffChanges(repo repository.Repo, from, to repository.Revision) ([]interface{}, error) {
	// TODO(ojarjur): This is a big hack, but so far there does not seem to be a better solution:
	// We need to pass a list of "changes" JSON objects that contain the parsed diff contents.
	// The simplest way to do that parsing seems to be to create a rawDiff and have Phabricator
	// parse it on the server side. We then read back that diff, and return the changes from it.
	rawDiff, err := repo.GetRawDiff(from, to)
	if err != nil {
		return nil, err
	}
	createRequest := differentialCreateRawDiffRequest{Diff: rawDiff}
	var createResponse differentialCreateRawDiffResponse
	runArcCommandOrDie("differential.createrawdiff", createRequest, &createResponse)
	if createResponse.Error != "" {
		return nil, fmt.Errorf(createResponse.ErrorMessage)
	}
	diffID := createResponse.Response.ID

	diff, err := readDiff(diffID)
	if err != nil {
		return nil, err
	}
	if diff != nil {
		return diff.Changes, nil
	}
	return nil, fmt.Errorf("Failed to retrieve the raw diff for %s..%s", from, to)
}

type differentialCreateDiffRequest struct {
	Branch                    string        `json:"branch,omitempty"`
	SourceControlBaseRevision string        `json:"sourceControlBaseRevision,omitempty"`
	SourceControlPath         string        `json:"sourceControlPath,omitempty"`
	SourceControlSystem       string        `json:"sourceControlSystem,omitempty"`
	SourceMachine             string        `json:"sourceMachine,omitempty"`
	SourcePath                string        `json:"sourcePath,omitempty"`
	LintStatus                string        `json:"lintStatus,omitempty"`
	UnitStatus                string        `json:"unitStatus,omitempty"`
	Changes                   []interface{} `json:"changes,omitempty"`
}

type differentialDiff struct {
	ID  int    `json:"diffid,omitempty"`
	URI string `json:"uri,omitempty"`
}

type differentialCreateDiffResponse struct {
	Error        string           `json:"error,omitempty"`
	ErrorMessage string           `json:"errorMessage,omitempty"`
	Response     differentialDiff `json:"response,omitempty"`
}

type differentialSetDiffPropertyRequest struct {
	ID   int    `json:"diff_id"`
	Name string `json:"name"`
	Data string `json:"data"`
}

type differentialSetDiffPropertyResponse struct {
	Error        string `json:"error,omitempty"`
	ErrorMessage string `json:"errorMessage,omitempty"`
}

func (arc Arcanist) setDiffProperty(diffID int, name, value string) error {
	setPropertyRequest := differentialSetDiffPropertyRequest{
		ID:   diffID,
		Name: name,
		Data: value,
	}
	var setPropertyResponse differentialSetDiffPropertyResponse
	runArcCommandOrDie("differential.setdiffproperty", setPropertyRequest, &setPropertyResponse)
	if setPropertyResponse.Error != "" {
		return errors.New(setPropertyResponse.ErrorMessage)
	}
	return nil
}

// createDifferentialDiff generates a Phabricator resource that represents a diff between two revisions.
//
// The generated resource includes metadata about how the diff was generated, and a JSON representation
// of the changes from the diff, as parsed by Phabricator.
func (arc Arcanist) createDifferentialDiff(repo repository.Repo, mergeBase, revision repository.Revision, req request.Request, priorDiffs []string) (*differentialDiff, error) {
	revisionDetails, err := repo.GetDetails(revision)
	if err != nil {
		return nil, err
	}
	changes, err := arc.getDiffChanges(repo, mergeBase, revision)
	if err != nil {
		return nil, err
	}
	createRequest := differentialCreateDiffRequest{
		Branch:                    abbreviateRefName(req.ReviewRef),
		SourceControlSystem:       "git",
		SourceControlBaseRevision: string(mergeBase),
		SourcePath:                repo.GetPath(),
		LintStatus:                "6", // Status code 6 means "linter auto-skipped"
		UnitStatus:                "6", // Status code 6 means "unit tests have been auto-skipped"
		Changes:                   changes,
	}
	var createResponse differentialCreateDiffResponse
	runArcCommandOrDie("differential.creatediff", createRequest, &createResponse)
	if createResponse.Error != "" {
		return nil, fmt.Errorf(createResponse.ErrorMessage)
	}

	localCommits := make(map[string]interface{})
	for _, priorDiff := range priorDiffs {
		diffID, err := strconv.Atoi(priorDiff)
		if err != nil {
			return nil, err
		}
		queryRequest := differentialQueryDiffsRequest{[]int{diffID}}
		var queryResponse differentialQueryDiffsResponse
		runArcCommandOrDie("differential.querydiffs", queryRequest, &queryResponse)
		if queryResponse.Error != "" {
			return nil, fmt.Errorf(queryResponse.ErrorMessage)
		}
		priorProperty := queryResponse.Response[priorDiff].Properties
		if priorPropertyMap, ok := priorProperty.(map[string]interface{}); ok {
			if localCommitsProperty, ok := priorPropertyMap["local:commits"]; ok {
				if priorLocalCommits, ok := localCommitsProperty.(map[string]interface{}); ok {
					for id, val := range priorLocalCommits {
						localCommits[id] = val
					}
				}
			}
		}
	}
	localCommits[string(revision)] = *revisionDetails
	localCommitsProperty, err := json.Marshal(localCommits)
	if err != nil {
		return nil, err
	}
	if err := arc.setDiffProperty(createResponse.Response.ID, "local:commits", string(localCommitsProperty)); err != nil {
		return nil, err
	}
	if err := arc.setDiffProperty(createResponse.Response.ID, "arc:unit", "{}"); err != nil {
		return nil, err
	}

	return &createResponse.Response, nil
}
<commit_msg>Removed logic that wrote empty unit results<commit_after>/*
Copyright 2015 Google Inc. 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 arcanist

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/google/git-phabricator-mirror/mirror/repository"
	"github.com/google/git-phabricator-mirror/mirror/review/request"
	"log"
	"sort"
	"strconv"
)

type differentialCreateRawDiffRequest struct {
	Diff string `json:"diff"`
}

type rawDiff struct {
	ID int `json:"id"`
}

type differentialCreateRawDiffResponse struct {
	Error        string  `json:"error,omitempty"`
	ErrorMessage string  `json:"errorMessage,omitempty"`
	Response     rawDiff `json:"response,omitempty"`
}

type differentialQueryDiffsRequest struct {
	IDs []int `json:"ids"`
}

type queryDiffItem struct {
	ID         string        `json:"id"`
	Changes    []interface{} `json:"changes"`
	Properties interface{}   `json:"properties"`
}

type differentialQueryDiffsResponse struct {
	Error        string                   `json:"error,omitempty"`
	ErrorMessage string                   `json:"errorMessage,omitempty"`
	Response     map[string]queryDiffItem `json:"response"`
}

func readDiff(diffID int) (*queryDiffItem, error) {
	queryRequest := differentialQueryDiffsRequest{IDs: []int{diffID}}
	var queryResponse differentialQueryDiffsResponse
	runArcCommandOrDie("differential.querydiffs", queryRequest, &queryResponse)
	if queryResponse.Error != "" {
		return nil, fmt.Errorf(queryResponse.ErrorMessage)
	}
	if diff, ok := queryResponse.Response[strconv.Itoa(diffID)]; ok {
		return &diff, nil
	}
	return nil, nil
}

// Differential does not actually store the commit hash for the right hand side of a diff.
// As such, if we have to do some deep inspection to find it. What Differential *does*
// store is a map of "local commits". The last such local commit (by timestamp) is the
// one that was actually used to generate the right hand side of the diff.
func findLastCommit(commitsMap map[string]interface{}) string {
	var timestamps []int
	timestampCommitMap := make(map[int]string)
	for commit, commitData := range commitsMap {
		commitProperties, ok := commitData.(map[string]interface{})
		if ok {
			timestampString, ok := commitProperties["time"].(string)
			if ok {
				timestamp, err := strconv.Atoi(timestampString)
				if err != nil {
					log.Fatal(err)
				}
				timestamps = append(timestamps, timestamp)
				timestampCommitMap[timestamp] = commit
			}
		}
	}
	if len(timestamps) == 0 {
		return ""
	}
	sort.Sort(sort.Reverse(sort.IntSlice(timestamps)))
	return timestampCommitMap[timestamps[0]]
}

// findLastCommit returns the last commit included in a Differential diff.
func (diff *queryDiffItem) findLastCommit() string {
	propertiesMap, ok := diff.Properties.(map[string]interface{})
	if ok {
		commitsMap, ok := propertiesMap["local:commits"].(map[string]interface{})
		if ok {
			return findLastCommit(commitsMap)
		}
	}
	return ""
}

// getDiffChanges takes two revisions from which to generate a "git diff", and returns a
// slice of "changes" objects that represent that diff as parsed by Phabricator.
func (arc Arcanist) getDiffChanges(repo repository.Repo, from, to repository.Revision) ([]interface{}, error) {
	// TODO(ojarjur): This is a big hack, but so far there does not seem to be a better solution:
	// We need to pass a list of "changes" JSON objects that contain the parsed diff contents.
	// The simplest way to do that parsing seems to be to create a rawDiff and have Phabricator
	// parse it on the server side. We then read back that diff, and return the changes from it.
	rawDiff, err := repo.GetRawDiff(from, to)
	if err != nil {
		return nil, err
	}
	createRequest := differentialCreateRawDiffRequest{Diff: rawDiff}
	var createResponse differentialCreateRawDiffResponse
	runArcCommandOrDie("differential.createrawdiff", createRequest, &createResponse)
	if createResponse.Error != "" {
		return nil, fmt.Errorf(createResponse.ErrorMessage)
	}
	diffID := createResponse.Response.ID

	diff, err := readDiff(diffID)
	if err != nil {
		return nil, err
	}
	if diff != nil {
		return diff.Changes, nil
	}
	return nil, fmt.Errorf("Failed to retrieve the raw diff for %s..%s", from, to)
}

type differentialCreateDiffRequest struct {
	Branch                    string        `json:"branch,omitempty"`
	SourceControlBaseRevision string        `json:"sourceControlBaseRevision,omitempty"`
	SourceControlPath         string        `json:"sourceControlPath,omitempty"`
	SourceControlSystem       string        `json:"sourceControlSystem,omitempty"`
	SourceMachine             string        `json:"sourceMachine,omitempty"`
	SourcePath                string        `json:"sourcePath,omitempty"`
	LintStatus                string        `json:"lintStatus,omitempty"`
	UnitStatus                string        `json:"unitStatus,omitempty"`
	Changes                   []interface{} `json:"changes,omitempty"`
}

type differentialDiff struct {
	ID  int    `json:"diffid,omitempty"`
	URI string `json:"uri,omitempty"`
}

type differentialCreateDiffResponse struct {
	Error        string           `json:"error,omitempty"`
	ErrorMessage string           `json:"errorMessage,omitempty"`
	Response     differentialDiff `json:"response,omitempty"`
}

type differentialSetDiffPropertyRequest struct {
	ID   int    `json:"diff_id"`
	Name string `json:"name"`
	Data string `json:"data"`
}

type differentialSetDiffPropertyResponse struct {
	Error        string `json:"error,omitempty"`
	ErrorMessage string `json:"errorMessage,omitempty"`
}

func (arc Arcanist) setDiffProperty(diffID int, name, value string) error {
	setPropertyRequest := differentialSetDiffPropertyRequest{
		ID:   diffID,
		Name: name,
		Data: value,
	}
	var setPropertyResponse differentialSetDiffPropertyResponse
	runArcCommandOrDie("differential.setdiffproperty", setPropertyRequest, &setPropertyResponse)
	if setPropertyResponse.Error != "" {
		return errors.New(setPropertyResponse.ErrorMessage)
	}
	return nil
}

// createDifferentialDiff generates a Phabricator resource that represents a diff between two revisions.
//
// The generated resource includes metadata about how the diff was generated, and a JSON representation
// of the changes from the diff, as parsed by Phabricator.
func (arc Arcanist) createDifferentialDiff(repo repository.Repo, mergeBase, revision repository.Revision, req request.Request, priorDiffs []string) (*differentialDiff, error) {
	revisionDetails, err := repo.GetDetails(revision)
	if err != nil {
		return nil, err
	}
	changes, err := arc.getDiffChanges(repo, mergeBase, revision)
	if err != nil {
		return nil, err
	}
	createRequest := differentialCreateDiffRequest{
		Branch:                    abbreviateRefName(req.ReviewRef),
		SourceControlSystem:       "git",
		SourceControlBaseRevision: string(mergeBase),
		SourcePath:                repo.GetPath(),
		LintStatus:                "6", // Status code 6 means "linter auto-skipped"
		UnitStatus:                "6", // Status code 6 means "unit tests have been auto-skipped"
		Changes:                   changes,
	}
	var createResponse differentialCreateDiffResponse
	runArcCommandOrDie("differential.creatediff", createRequest, &createResponse)
	if createResponse.Error != "" {
		return nil, fmt.Errorf(createResponse.ErrorMessage)
	}

	localCommits := make(map[string]interface{})
	for _, priorDiff := range priorDiffs {
		diffID, err := strconv.Atoi(priorDiff)
		if err != nil {
			return nil, err
		}
		queryRequest := differentialQueryDiffsRequest{[]int{diffID}}
		var queryResponse differentialQueryDiffsResponse
		runArcCommandOrDie("differential.querydiffs", queryRequest, &queryResponse)
		if queryResponse.Error != "" {
			return nil, fmt.Errorf(queryResponse.ErrorMessage)
		}
		priorProperty := queryResponse.Response[priorDiff].Properties
		if priorPropertyMap, ok := priorProperty.(map[string]interface{}); ok {
			if localCommitsProperty, ok := priorPropertyMap["local:commits"]; ok {
				if priorLocalCommits, ok := localCommitsProperty.(map[string]interface{}); ok {
					for id, val := range priorLocalCommits {
						localCommits[id] = val
					}
				}
			}
		}
	}
	localCommits[string(revision)] = *revisionDetails
	localCommitsProperty, err := json.Marshal(localCommits)
	if err != nil {
		return nil, err
	}
	if err := arc.setDiffProperty(createResponse.Response.ID, "local:commits", string(localCommitsProperty)); err != nil {
		return nil, err
	}

	return &createResponse.Response, nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package kbfsblock

import "github.com/keybase/kbfs/kbfscodec"

// UsageType indicates the type of usage that quota manager is keeping stats of
type UsageType int

const (
	// UsageWrite indicates a data block is written (written blocks include archived blocks)
	UsageWrite UsageType = iota
	// UsageArchive indicates an existing (data) block is archived
	UsageArchive
	// UsageRead indicates a block is read
	UsageRead
	// UsageMDWrite indicates a MD block is written
	UsageMDWrite
	// UsageGitWrite indicates a git block is written
	UsageGitWrite
	// NumUsage indicates the number of usage types
	NumUsage
)

// UsageStat tracks the amount of bytes/blocks used, broken down by usage types
type UsageStat struct {
	Bytes  map[UsageType]int64
	Blocks map[UsageType]int64
	// Mtime is in unix nanoseconds
	Mtime int64
}

// NewUsageStat creates a new UsageStat
func NewUsageStat() *UsageStat {
	return &UsageStat{
		Bytes:  make(map[UsageType]int64),
		Blocks: make(map[UsageType]int64),
	}
}

// NonZero checks whether UsageStat has accumulated any usage info
func (u *UsageStat) NonZero() bool {
	for i := UsageType(0); i < NumUsage; i++ {
		if u.Bytes[i] != 0 {
			return true
		}
	}
	return false
}

//AccumOne records the usage of one block, whose size is denoted by change
//A positive change means the block is newly added, negative means the block
//is deleted. If archive is true, it means the block is archived.
func (u *UsageStat) AccumOne(change int, usage UsageType) {
	if change == 0 {
		return
	}
	if usage < UsageWrite || usage > UsageRead {
		return
	}
	u.Bytes[usage] += int64(change)
	if change > 0 {
		u.Blocks[usage]++
	} else {
		u.Blocks[usage]--
	}
}

// Accum combines changes to the existing QuotaInfo object using accumulation function accumF.
func (u *UsageStat) Accum(another *UsageStat, accumF func(int64, int64) int64) {
	if another == nil {
		return
	}
	for k, v := range another.Bytes {
		u.Bytes[k] = accumF(u.Bytes[k], v)
	}
	for k, v := range another.Blocks {
		u.Blocks[k] = accumF(u.Blocks[k], v)
	}
}

// QuotaInfo contains a user's quota usage information
type QuotaInfo struct {
	Folders  map[string]*UsageStat
	Total    *UsageStat
	Limit    int64
	GitLimit int64
}

// NewQuotaInfo returns a newly constructed QuotaInfo.
func NewQuotaInfo() *QuotaInfo {
	return &QuotaInfo{
		Folders: make(map[string]*UsageStat),
		Total:   NewUsageStat(),
	}
}

// AccumOne combines one quota charge to the existing QuotaInfo
func (u *QuotaInfo) AccumOne(change int, folder string, usage UsageType) {
	if _, ok := u.Folders[folder]; !ok {
		u.Folders[folder] = NewUsageStat()
	}
	u.Folders[folder].AccumOne(change, usage)
	u.Total.AccumOne(change, usage)
}

// Accum combines changes to the existing QuotaInfo object using accumulation function accumF.
func (u *QuotaInfo) Accum(another *QuotaInfo, accumF func(int64, int64) int64) {
	if another == nil {
		return
	}
	if u.Total == nil {
		u.Total = NewUsageStat()
	}
	u.Total.Accum(another.Total, accumF)
	for f, change := range another.Folders {
		if _, ok := u.Folders[f]; !ok {
			u.Folders[f] = NewUsageStat()
		}
		u.Folders[f].Accum(change, accumF)
	}
}

// ToBytes marshals this QuotaInfo
func (u *QuotaInfo) ToBytes(codec kbfscodec.Codec) ([]byte, error) {
	return codec.Encode(u)
}

// QuotaInfoDecode decodes b into a QuotaInfo
func QuotaInfoDecode(b []byte, codec kbfscodec.Codec) (
	*QuotaInfo, error) {
	var info QuotaInfo
	err := codec.Decode(b, &info)
	if err != nil {
		return nil, err
	}

	return &info, nil
}
<commit_msg>kbfsblock: git blocks can be archived too<commit_after>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package kbfsblock

import "github.com/keybase/kbfs/kbfscodec"

// UsageType indicates the type of usage that quota manager is keeping stats of
type UsageType int

const (
	// UsageWrite indicates a data block is written (written blocks include archived blocks)
	UsageWrite UsageType = iota
	// UsageArchive indicates an existing (data) block is archived
	UsageArchive
	// UsageRead indicates a block is read
	UsageRead
	// UsageMDWrite indicates a MD block is written
	UsageMDWrite
	// UsageGitWrite indicates a git block is written
	UsageGitWrite
	// UsageGitArchive indicates an existing git block is archived
	UsageGitArchive
	// NumUsage indicates the number of usage types
	NumUsage
)

// UsageStat tracks the amount of bytes/blocks used, broken down by usage types
type UsageStat struct {
	Bytes  map[UsageType]int64
	Blocks map[UsageType]int64
	// Mtime is in unix nanoseconds
	Mtime int64
}

// NewUsageStat creates a new UsageStat
func NewUsageStat() *UsageStat {
	return &UsageStat{
		Bytes:  make(map[UsageType]int64),
		Blocks: make(map[UsageType]int64),
	}
}

// NonZero checks whether UsageStat has accumulated any usage info
func (u *UsageStat) NonZero() bool {
	for i := UsageType(0); i < NumUsage; i++ {
		if u.Bytes[i] != 0 {
			return true
		}
	}
	return false
}

//AccumOne records the usage of one block, whose size is denoted by change
//A positive change means the block is newly added, negative means the block
//is deleted. If archive is true, it means the block is archived.
func (u *UsageStat) AccumOne(change int, usage UsageType) {
	if change == 0 {
		return
	}
	if usage < UsageWrite || usage > UsageRead {
		return
	}
	u.Bytes[usage] += int64(change)
	if change > 0 {
		u.Blocks[usage]++
	} else {
		u.Blocks[usage]--
	}
}

// Accum combines changes to the existing QuotaInfo object using accumulation function accumF.
func (u *UsageStat) Accum(another *UsageStat, accumF func(int64, int64) int64) {
	if another == nil {
		return
	}
	for k, v := range another.Bytes {
		u.Bytes[k] = accumF(u.Bytes[k], v)
	}
	for k, v := range another.Blocks {
		u.Blocks[k] = accumF(u.Blocks[k], v)
	}
}

// QuotaInfo contains a user's quota usage information
type QuotaInfo struct {
	Folders  map[string]*UsageStat
	Total    *UsageStat
	Limit    int64
	GitLimit int64
}

// NewQuotaInfo returns a newly constructed QuotaInfo.
func NewQuotaInfo() *QuotaInfo {
	return &QuotaInfo{
		Folders: make(map[string]*UsageStat),
		Total:   NewUsageStat(),
	}
}

// AccumOne combines one quota charge to the existing QuotaInfo
func (u *QuotaInfo) AccumOne(change int, folder string, usage UsageType) {
	if _, ok := u.Folders[folder]; !ok {
		u.Folders[folder] = NewUsageStat()
	}
	u.Folders[folder].AccumOne(change, usage)
	u.Total.AccumOne(change, usage)
}

// Accum combines changes to the existing QuotaInfo object using accumulation function accumF.
func (u *QuotaInfo) Accum(another *QuotaInfo, accumF func(int64, int64) int64) {
	if another == nil {
		return
	}
	if u.Total == nil {
		u.Total = NewUsageStat()
	}
	u.Total.Accum(another.Total, accumF)
	for f, change := range another.Folders {
		if _, ok := u.Folders[f]; !ok {
			u.Folders[f] = NewUsageStat()
		}
		u.Folders[f].Accum(change, accumF)
	}
}

// ToBytes marshals this QuotaInfo
func (u *QuotaInfo) ToBytes(codec kbfscodec.Codec) ([]byte, error) {
	return codec.Encode(u)
}

// QuotaInfoDecode decodes b into a QuotaInfo
func QuotaInfoDecode(b []byte, codec kbfscodec.Codec) (
	*QuotaInfo, error) {
	var info QuotaInfo
	err := codec.Decode(b, &info)
	if err != nil {
		return nil, err
	}

	return &info, nil
}
<|endoftext|>


<commit_before>// Copyright 2020 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 main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/GoogleCloudPlatform/cloud-build-notifiers/lib/notifiers"
	log "github.com/golang/glog"
	chat "google.golang.org/api/chat/v1"

	cbpb "google.golang.org/genproto/googleapis/devtools/cloudbuild/v1"
)

const (
	webhookURLSecretName = "webhookUrl"
)

func main() {
	if err := notifiers.Main(new(googlechatNotifier)); err != nil {
		log.Fatalf("fatal error: %v", err)
	}
}

type googlechatNotifier struct {
	filter notifiers.EventFilter

	webhookURL string
}

func (g *googlechatNotifier) SetUp(ctx context.Context, cfg *notifiers.Config, sg notifiers.SecretGetter, _ notifiers.BindingResolver) error {
	prd, err := notifiers.MakeCELPredicate(cfg.Spec.Notification.Filter)
	if err != nil {
		return fmt.Errorf("failed to make a CEL predicate: %w", err)
	}
	g.filter = prd

	wuRef, err := notifiers.GetSecretRef(cfg.Spec.Notification.Delivery, webhookURLSecretName)
	if err != nil {
		return fmt.Errorf("failed to get Secret ref from delivery config (%v) field %q: %w", cfg.Spec.Notification.Delivery, webhookURLSecretName, err)
	}
	wuResource, err := notifiers.FindSecretResourceName(cfg.Spec.Secrets, wuRef)
	if err != nil {
		return fmt.Errorf("failed to find Secret for ref %q: %w", wuRef, err)
	}
	wu, err := sg.GetSecret(ctx, wuResource)
	if err != nil {
		return fmt.Errorf("failed to get token secret: %w", err)
	}
	g.webhookURL = wu

	return nil
}

func (g *googlechatNotifier) SendNotification(ctx context.Context, build *cbpb.Build) error {
	if !g.filter.Apply(ctx, build) {
		return nil
	}

	log.Infof("sending Google Chat webhook for Build %q (status: %q)", build.Id, build.Status)
	msg, err := g.writeMessage(build)
	if err != nil {
		return fmt.Errorf("failed to write Google Chat message: %w", err)
	}
	//TODO(glasnt) unsure if this is best practice.
	payload := new(bytes.Buffer)
	json.NewEncoder(payload).Encode(msg)

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, g.webhookURL, payload)
	if err != nil {
		return fmt.Errorf("failed to create a new HTTP request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("User-Agent", "GCB-Notifier/0.1 (http)")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to make HTTP request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		log.Warningf("got a non-OK response status %q (%d) from %q", resp.Status, resp.StatusCode, g.webhookURL)
	}

	log.V(2).Infoln("send HTTP request successfully")
	return nil
}

func (g *googlechatNotifier) writeMessage(build *cbpb.Build) (*chat.Message, error) {

	var icon string

	switch build.Status {
	case cbpb.Build_SUCCESS:
		icon = "https://www.gstatic.com/images/icons/material/system/2x/check_circle_googgreen_48dp.png"
	case cbpb.Build_FAILURE, cbpb.Build_INTERNAL_ERROR:
		icon = "https://www.gstatic.com/images/icons/material/system/2x/error_red_48dp.png"
	case cbpb.Build_TIMEOUT:
		icon = "https://www.gstatic.com/images/icons/material/system/2x/hourglass_empty_black_48dp.png"
	default:
		icon = "https://www.gstatic.com/images/icons/material/system/2x/question_mark_black_48dp.png"
	}

	logURL, err := notifiers.AddUTMParams(build.LogUrl, notifiers.ChatMedium)
	if err != nil {
		return nil, fmt.Errorf("failed to add UTM params: %w", err)
	}

	// Basic card setup
	duration := build.GetFinishTime().AsTime().Sub(build.GetStartTime().AsTime())
	duration_min, duration_sec := int(duration.Minutes()), int(duration.Seconds())-int(duration.Minutes())*60
	duration_fmt := fmt.Sprintf("%d min %d sec", duration_min, duration_sec)

	card := &chat.Card{
		Header: &chat.CardHeader{
			Title:    fmt.Sprintf("Build %s Status: %s", build.Id[:8], build.Status),
			Subtitle: build.ProjectId,
			ImageUrl: icon,
		},
		Sections: []*chat.Section{
			{
				Widgets: []*chat.WidgetMarkup{
					{
						KeyValue: &chat.KeyValue{
							TopLabel: "Duration",
							Content:  duration_fmt,
						},
					},
				},
			},
		},
	}

	// Optional section: display trigger information
	if build.BuildTriggerId != "" {

		log.Infof("Detected a build trigger id: %s", build.BuildTriggerId)

		/*
			//TODO(glasnt): Get trigger information for Uri links.
			//  The repo name in `build` does not include the owner information
			//  You need to inspect the trigger object to get the full repo name and/or the git URI.

			ctx := context.Background()
			cbapi, _ := cloudbuild.NewClient(ctx)
			trigger_info := cbapi.GetBuildTrigger(ctx, &cbpb.GetBuildTriggerRequest{ProjectId: build.ProjectId, TriggerId: build.BuildTriggerId,})
			log.Infof("Trigger Repo URI: %s", trigger_info.??)
		*/

		repo_name := build.Substitutions["REPO_NAME"]
		trigger_name := build.Substitutions["TRIGGER_NAME"]
		branch_name := build.Substitutions["BRANCH_NAME"]
		commit := build.Substitutions["SHORT_SHA"]

		card.Header.Subtitle = fmt.Sprintf("%s on %s", trigger_name, build.ProjectId)

		build_info := &chat.Section{
			Header: "Trigger information",
			Widgets: []*chat.WidgetMarkup{
				{

					KeyValue: &chat.KeyValue{
						TopLabel: "Trigger",
						Content:  trigger_name,
					},
				},
				{
					KeyValue: &chat.KeyValue{
						TopLabel: `Repo`,
						Content:  repo_name,
					},
				},
				{
					KeyValue: &chat.KeyValue{
						TopLabel: "Branch",
						Content:  branch_name,
					},
				},
				{
					KeyValue: &chat.KeyValue{
						TopLabel: "Commit",
						Content:  commit,
					},
				},
			},
		}

		card.Sections = append(card.Sections, build_info)
	}

	// Optional section: display information about errors
	if build.FailureInfo != nil {
		failure_info := &chat.Section{
			Header: "Error information",
			Widgets: []*chat.WidgetMarkup{
				{
					TextParagraph: &chat.TextParagraph{
						Text: build.FailureInfo.GetDetail(),
					},
				},
			},
		}
		card.Sections = append(card.Sections, failure_info)
	}

	// Append action button
	action_section := &chat.Section{
		Widgets: []*chat.WidgetMarkup{
			{
				Buttons: []*chat.Button{
					{
						TextButton: &chat.TextButton{
							Text: "open logs",
							OnClick: &chat.OnClick{
								OpenLink: &chat.OpenLink{
									Url: logURL,
								},
							},
						},
					},
				},
			},
		},
	}

	card.Sections = append(card.Sections, action_section)

	msg := chat.Message{Cards: []*chat.Card{card}}
	return &msg, nil
}
<commit_msg>Add error handling on message construction<commit_after>// Copyright 2020 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 main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/GoogleCloudPlatform/cloud-build-notifiers/lib/notifiers"
	log "github.com/golang/glog"
	chat "google.golang.org/api/chat/v1"

	cbpb "google.golang.org/genproto/googleapis/devtools/cloudbuild/v1"
)

const (
	webhookURLSecretName = "webhookUrl"
)

func main() {
	if err := notifiers.Main(new(googlechatNotifier)); err != nil {
		log.Fatalf("fatal error: %v", err)
	}
}

type googlechatNotifier struct {
	filter notifiers.EventFilter

	webhookURL string
}

func (g *googlechatNotifier) SetUp(ctx context.Context, cfg *notifiers.Config, sg notifiers.SecretGetter, _ notifiers.BindingResolver) error {
	prd, err := notifiers.MakeCELPredicate(cfg.Spec.Notification.Filter)
	if err != nil {
		return fmt.Errorf("failed to make a CEL predicate: %w", err)
	}
	g.filter = prd

	wuRef, err := notifiers.GetSecretRef(cfg.Spec.Notification.Delivery, webhookURLSecretName)
	if err != nil {
		return fmt.Errorf("failed to get Secret ref from delivery config (%v) field %q: %w", cfg.Spec.Notification.Delivery, webhookURLSecretName, err)
	}
	wuResource, err := notifiers.FindSecretResourceName(cfg.Spec.Secrets, wuRef)
	if err != nil {
		return fmt.Errorf("failed to find Secret for ref %q: %w", wuRef, err)
	}
	wu, err := sg.GetSecret(ctx, wuResource)
	if err != nil {
		return fmt.Errorf("failed to get token secret: %w", err)
	}
	g.webhookURL = wu

	return nil
}

func (g *googlechatNotifier) SendNotification(ctx context.Context, build *cbpb.Build) error {
	if !g.filter.Apply(ctx, build) {
		return nil
	}

	log.Infof("sending Google Chat webhook for Build %q (status: %q)", build.Id, build.Status)
	msg, err := g.writeMessage(build)
	if err != nil {
		return fmt.Errorf("failed to write Google Chat message: %w", err)
	}

	payload := new(bytes.Buffer)
	err = json.NewEncoder(payload).Encode(msg)
	if err != nil {
		return fmt.Errorf("failed to encode payload: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, g.webhookURL, payload)
	if err != nil {
		return fmt.Errorf("failed to create a new HTTP request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("User-Agent", "GCB-Notifier/0.1 (http)")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to make HTTP request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		log.Warningf("got a non-OK response status %q (%d) from %q", resp.Status, resp.StatusCode, g.webhookURL)
	}

	log.V(2).Infoln("send HTTP request successfully")
	return nil
}

func (g *googlechatNotifier) writeMessage(build *cbpb.Build) (*chat.Message, error) {

	var icon string

	switch build.Status {
	case cbpb.Build_SUCCESS:
		icon = "https://www.gstatic.com/images/icons/material/system/2x/check_circle_googgreen_48dp.png"
	case cbpb.Build_FAILURE, cbpb.Build_INTERNAL_ERROR:
		icon = "https://www.gstatic.com/images/icons/material/system/2x/error_red_48dp.png"
	case cbpb.Build_TIMEOUT:
		icon = "https://www.gstatic.com/images/icons/material/system/2x/hourglass_empty_black_48dp.png"
	default:
		icon = "https://www.gstatic.com/images/icons/material/system/2x/question_mark_black_48dp.png"
	}

	logURL, err := notifiers.AddUTMParams(build.LogUrl, notifiers.ChatMedium)
	if err != nil {
		return nil, fmt.Errorf("failed to add UTM params: %w", err)
	}

	// Basic card setup
	duration := build.GetFinishTime().AsTime().Sub(build.GetStartTime().AsTime())
	duration_min, duration_sec := int(duration.Minutes()), int(duration.Seconds())-int(duration.Minutes())*60
	duration_fmt := fmt.Sprintf("%d min %d sec", duration_min, duration_sec)

	card := &chat.Card{
		Header: &chat.CardHeader{
			Title:    fmt.Sprintf("Build %s Status: %s", build.Id[:8], build.Status),
			Subtitle: build.ProjectId,
			ImageUrl: icon,
		},
		Sections: []*chat.Section{
			{
				Widgets: []*chat.WidgetMarkup{
					{
						KeyValue: &chat.KeyValue{
							TopLabel: "Duration",
							Content:  duration_fmt,
						},
					},
				},
			},
		},
	}

	// Optional section: display trigger information
	if build.BuildTriggerId != "" {

		log.Infof("Detected a build trigger id: %s", build.BuildTriggerId)

		/*
			//TODO(glasnt): Get trigger information for Uri links.
			//  The repo name in `build` does not include the owner information
			//  You need to inspect the trigger object to get the full repo name and/or the git URI.

			ctx := context.Background()
			cbapi, _ := cloudbuild.NewClient(ctx)
			trigger_info := cbapi.GetBuildTrigger(ctx, &cbpb.GetBuildTriggerRequest{ProjectId: build.ProjectId, TriggerId: build.BuildTriggerId,})
			log.Infof("Trigger Repo URI: %s", trigger_info.??)
		*/

		repo_name := build.Substitutions["REPO_NAME"]
		trigger_name := build.Substitutions["TRIGGER_NAME"]
		branch_name := build.Substitutions["BRANCH_NAME"]
		commit := build.Substitutions["SHORT_SHA"]

		card.Header.Subtitle = fmt.Sprintf("%s on %s", trigger_name, build.ProjectId)

		build_info := &chat.Section{
			Header: "Trigger information",
			Widgets: []*chat.WidgetMarkup{
				{

					KeyValue: &chat.KeyValue{
						TopLabel: "Trigger",
						Content:  trigger_name,
					},
				},
				{
					KeyValue: &chat.KeyValue{
						TopLabel: `Repo`,
						Content:  repo_name,
					},
				},
				{
					KeyValue: &chat.KeyValue{
						TopLabel: "Branch",
						Content:  branch_name,
					},
				},
				{
					KeyValue: &chat.KeyValue{
						TopLabel: "Commit",
						Content:  commit,
					},
				},
			},
		}

		card.Sections = append(card.Sections, build_info)
	}

	// Optional section: display information about errors
	if build.FailureInfo != nil {
		failure_info := &chat.Section{
			Header: "Error information",
			Widgets: []*chat.WidgetMarkup{
				{
					TextParagraph: &chat.TextParagraph{
						Text: build.FailureInfo.GetDetail(),
					},
				},
			},
		}
		card.Sections = append(card.Sections, failure_info)
	}

	// Append action button
	action_section := &chat.Section{
		Widgets: []*chat.WidgetMarkup{
			{
				Buttons: []*chat.Button{
					{
						TextButton: &chat.TextButton{
							Text: "open logs",
							OnClick: &chat.OnClick{
								OpenLink: &chat.OpenLink{
									Url: logURL,
								},
							},
						},
					},
				},
			},
		},
	}

	card.Sections = append(card.Sections, action_section)

	msg := chat.Message{Cards: []*chat.Card{card}}
	return &msg, nil
}
<|endoftext|>


<commit_before>package main

import (
	"os"

	"github.com/spf13/cobra"

	"github.com/lxc/lxd/shared/version"
)

type cmdGlobal struct {
	flagVersion bool
	flagHelp    bool
}

func main() {
	app := &cobra.Command{}
	app.Use = "fuidshift"
	app.Short = "UID/GID shifter"
	app.Long = `Description:
  UID/GID shifter

  This tool lets you remap a filesystem tree, switching it from one
  set of UID/GID ranges to another.

  This is mostly useful when retrieving a wrongly shifted filesystem tree
  from a backup or broken system and having to remap everything either to
  the host UID/GID range (uid/gid 0 is root) or to an existing container's
  range.

  A range is represented as <u|b|g>:<first_container_id>:<first_host_id>:<size>.
  Where "u" means shift uid, "g" means shift gid and "b" means shift uid and gid.
`
	app.Example = `  fuidshift my-dir/ b:0:100000:65536 u:10000:1000:1`
	app.SilenceUsage = true

	// Global flags
	globalCmd := cmdGlobal{}
	app.PersistentFlags().BoolVar(&globalCmd.flagVersion, "version", false, "Print version number")
	app.PersistentFlags().BoolVarP(&globalCmd.flagHelp, "help", "h", false, "Print help")

	// Version handling
	app.SetVersionTemplate("{{.Version}}\n")
	app.Version = version.Version

	// shift command (main)
	shiftCmd := cmdShift{global: &globalCmd}
	app.Flags().BoolVarP(&shiftCmd.flagTestMode, "test", "t", false, "Test mode (no change to files)")
	app.Flags().BoolVarP(&shiftCmd.flagReverse, "reverse", "r", false, "Perform a reverse mapping")
	app.Use = "fuidshift <directory> <range> [<range>...]"
	app.RunE = shiftCmd.Run
	app.Args = cobra.ArbitraryArgs

	// Run the main command and handle errors
	err := app.Execute()
	if err != nil {
		os.Exit(1)
	}
}
<commit_msg>fuidshift: Drop duplicate definition of Use<commit_after>package main

import (
	"os"

	"github.com/spf13/cobra"

	"github.com/lxc/lxd/shared/version"
)

type cmdGlobal struct {
	flagVersion bool
	flagHelp    bool
}

func main() {
	app := &cobra.Command{}
	app.Short = "UID/GID shifter"
	app.Long = `Description:
  UID/GID shifter

  This tool lets you remap a filesystem tree, switching it from one
  set of UID/GID ranges to another.

  This is mostly useful when retrieving a wrongly shifted filesystem tree
  from a backup or broken system and having to remap everything either to
  the host UID/GID range (uid/gid 0 is root) or to an existing container's
  range.

  A range is represented as <u|b|g>:<first_container_id>:<first_host_id>:<size>.
  Where "u" means shift uid, "g" means shift gid and "b" means shift uid and gid.
`
	app.Example = `  fuidshift my-dir/ b:0:100000:65536 u:10000:1000:1`
	app.SilenceUsage = true

	// Global flags
	globalCmd := cmdGlobal{}
	app.PersistentFlags().BoolVar(&globalCmd.flagVersion, "version", false, "Print version number")
	app.PersistentFlags().BoolVarP(&globalCmd.flagHelp, "help", "h", false, "Print help")

	// Version handling
	app.SetVersionTemplate("{{.Version}}\n")
	app.Version = version.Version

	// shift command (main)
	shiftCmd := cmdShift{global: &globalCmd}
	app.Flags().BoolVarP(&shiftCmd.flagTestMode, "test", "t", false, "Test mode (no change to files)")
	app.Flags().BoolVarP(&shiftCmd.flagReverse, "reverse", "r", false, "Perform a reverse mapping")
	app.Use = "fuidshift <directory> <range> [<range>...]"
	app.RunE = shiftCmd.Run
	app.Args = cobra.ArbitraryArgs

	// Run the main command and handle errors
	err := app.Execute()
	if err != nil {
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Implements Git Smart HTTP backend using its C implementation as reference:
// https://github.com/git/git/blob/master/http-backend.c
package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/c4milo/handlers/logger"
	"github.com/hashicorp/logutils"
	"github.com/stretchr/graceful"
)

// Version is injected in build time and defined in the Makefile
var Version string

// Name is injected in build time and defined in the Makefile
var Name string

type Config struct {
	Bind            string `toml:"bind"`
	Port            uint   `toml:"port"`
	ReposPath       string `toml:"repos_path"`
	LogLevel        string `toml:"log_level"`
	LogFilePath     string `toml:"log_file"`
	ShutdownTimeout string `toml:"shutdown_timeout"`
}

// Default configuration
var config Config = Config{
	Bind:            "localhost",
	Port:            12345,
	LogLevel:        "WARN",
	ShutdownTimeout: "15s",
}

// Configuration file path
var configFile string

func init() {
	if !checkGitVersion(2, 2, 1) {
		log.Fatalln("Git >= v2.2.1 is required")
	}

	reposPath, err := ioutil.TempDir(os.TempDir(), Name)
	if err != nil {
		log.Fatalf("%v\n", err)
	}
	config.ReposPath = reposPath

	flag.StringVar(&configFile, "f", "", "config file path")
	flag.Parse()

	if _, err := toml.DecodeFile(configFile, &config); err != nil {
		log.Printf("[ERROR] %v", err)
		log.Print("[ERROR] Parsing config file, using default configuration")
	}
}

func Handler(w http.ResponseWriter, req *http.Request) {
	handlers := map[*regexp.Regexp]func(http.ResponseWriter, *http.Request, string){
		regexp.MustCompile("(.*?)/git-upload-pack$"):  UploadPack,
		regexp.MustCompile("(.*?)/git-receive-pack$"): ReceivePack,
		regexp.MustCompile("(.*?)/info/refs$"):        InfoRefs,
	}

	for re, handler := range handlers {
		if m := re.FindStringSubmatch(req.URL.Path); m != nil {
			repoPath := m[1]
			handler(w, req, repoPath)
			return
		}
	}
	w.WriteHeader(http.StatusBadRequest)
	w.Write([]byte("Bad Request"))
}

func main() {
	var logWriter io.Writer
	if config.LogFilePath != "" {
		var err error
		logWriter, err = os.OpenFile(config.LogFilePath, os.O_RDWR|os.O_APPEND, 0660)
		if err != nil {
			log.Printf("[WARN] %v", err)
		}
	}

	if logWriter == nil {
		logWriter = os.Stderr
	}

	filter := &logutils.LevelFilter{
		Levels:   []logutils.LogLevel{"DEBUG", "WARN", "ERROR"},
		MinLevel: logutils.LogLevel(config.LogLevel),
		Writer:   logWriter,
	}

	log.SetOutput(filter)

	mux := http.DefaultServeMux
	mux.HandleFunc("/", Handler)

	address := fmt.Sprintf("%s:%d", config.Bind, config.Port)
	timeout, err := time.ParseDuration(config.ShutdownTimeout)
	if err != nil {
		log.Fatalf("[ERROR] %v", err)
	}

	log.Printf("[INFO] Listening on %s...", address)
	log.Printf("[INFO] Serving Git repositories over HTTP from %s", config.ReposPath)
	graceful.Run(address, timeout, logger.Handler(mux, logger.AppName("gitd")))
}

// Runs git-upload-pack in a safe manner
func UploadPack(w http.ResponseWriter, req *http.Request, repoPath string) {
	if req.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}
	process := "git-upload-pack"
	cwd := filepath.Join(config.ReposPath, repoPath)

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-result", process))
	w.WriteHeader(http.StatusOK)

	cmd := exec.Command(process, "--stateless-rpc", ".")
	cmd.Dir = cwd
	runCommand(w, req.Body, cmd)
}

//Runs git-receive-pack in a safe manner
func ReceivePack(w http.ResponseWriter, req *http.Request, repoPath string) {
	if req.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}
	process := "git-receive-pack"
	cwd := filepath.Join(config.ReposPath, repoPath)

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-result", process))
	w.WriteHeader(http.StatusOK)

	cmd := exec.Command(process, "--stateless-rpc", ".")
	cmd.Dir = cwd
	runCommand(w, req.Body, cmd)
}

func InfoRefs(w http.ResponseWriter, req *http.Request, repoPath string) {
	if req.Method != "GET" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}

	process := req.URL.Query().Get("service")
	cwd := filepath.Join(config.ReposPath, repoPath)

	if process != "git-receive-pack" && process != "git-upload-pack" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Bad Request"))
		return
	}

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-advertisement", process))
	w.WriteHeader(http.StatusOK)

	w.Write(packetWrite(fmt.Sprintf("# service=%s\n", process)))
	w.Write(packetFlush())

	cmd := exec.Command(process, "--stateless-rpc", "--advertise-refs", ".")
	cmd.Dir = cwd
	runCommand(w, req.Body, cmd)
}

// Executes a shell command and pipes its output to HTTP response writer.
// DO NOT expose this function directly to end users as it creates a security breach
func runCommand(w io.Writer, r io.Reader, cmd *exec.Cmd) {
	if cmd.Dir != "" {
		cmd.Dir = sanitize(cmd.Dir)
	}

	log.Printf("[DEBUG] Running command from %s: %s %s ", cmd.Dir, cmd.Path, cmd.Args)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Printf("[ERROR] %v", err)
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Printf("[ERROR] %v", err)
	}

	if err := cmd.Start(); err != nil {
		log.Printf("[ERROR] %v", err)
	}

	io.Copy(stdin, r)
	io.Copy(w, stdout)
	cmd.Wait()
}

// Returns bytes of a git packet containing the given string
func packetWrite(str string) []byte {
	s := strconv.FormatInt(int64((len(str) + 4)), 16)

	m := len(s) % 4
	if m != 0 {
		s = strings.Repeat("0", 4-m) + s
	}

	return []byte(s + str)
}

func packetFlush() []byte {
	return []byte("0000")
}

// Sanitizes name to avoid overwriting sensitive system files
// or executing forbidden binaries
func sanitize(name string) string {
	// Gets rid of volume drive label in Windows
	if len(name) > 1 && name[1] == ':' && runtime.GOOS == "windows" {
		name = name[2:]
	}

	name = filepath.Clean(name)
	name = filepath.ToSlash(name)
	for strings.HasPrefix(name, "../") {
		name = name[3:]
	}
	return name
}

func checkGitVersion(major, minor, patch int) bool {
	git, err := exec.LookPath("git")
	if err != nil {
		log.Printf("[ERROR] %v", err)
		return false
	}

	cmd := exec.Command(git, "--version")
	var stdout string
	if stdout, _, err = runAndLog(cmd); err != nil {
		log.Printf("[ERROR] %v", err)
		return false
	}

	output := strings.Split(stdout, "\n")
	if len(output) < 2 {
		log.Printf("[DEBUG] git version output: %v", output)
		return false
	}

	parts := strings.Split(output[0], " ")
	if len(parts) < 3 {
		log.Printf("[DEBUG] git version parts: %v", parts)
		return false
	}

	version := strings.Split(parts[2], ".")
	major2, _ := strconv.Atoi(version[0])
	minor2, _ := strconv.Atoi(version[1])
	patch2, _ := strconv.Atoi(version[2])

	if major2 < major || minor2 < minor || patch2 < patch {
		log.Printf("[INFO] git version not supported: %d.%d.%d", major2, minor2, patch2)
		return false
	}

	return true
}

// Borrowed from https://github.com/mitchellh/packer/blob/master/builder/vmware/common/driver.go
func runAndLog(cmd *exec.Cmd) (string, string, error) {
	var stdout, stderr bytes.Buffer

	log.Printf("[VMWare] Executing: %s %v", cmd.Path, cmd.Args[1:])
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		message := stderrString
		if message == "" {
			message = stdoutString
		}

		err = fmt.Errorf("[VMWare] error: %s", message)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	// Replace these for Windows, we only want to deal with Unix
	// style line endings.
	returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1)
	returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1)

	return returnStdout, returnStderr, err
}
<commit_msg>Fixes logging tag<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Implements Git Smart HTTP backend using its C implementation as reference:
// https://github.com/git/git/blob/master/http-backend.c
package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/c4milo/handlers/logger"
	"github.com/hashicorp/logutils"
	"github.com/stretchr/graceful"
)

// Version is injected in build time and defined in the Makefile
var Version string

// Name is injected in build time and defined in the Makefile
var Name string

type Config struct {
	Bind            string `toml:"bind"`
	Port            uint   `toml:"port"`
	ReposPath       string `toml:"repos_path"`
	LogLevel        string `toml:"log_level"`
	LogFilePath     string `toml:"log_file"`
	ShutdownTimeout string `toml:"shutdown_timeout"`
}

// Default configuration
var config Config = Config{
	Bind:            "localhost",
	Port:            12345,
	LogLevel:        "WARN",
	ShutdownTimeout: "15s",
}

// Configuration file path
var configFile string

func init() {
	if !checkGitVersion(2, 2, 1) {
		log.Fatalln("Git >= v2.2.1 is required")
	}

	reposPath, err := ioutil.TempDir(os.TempDir(), Name)
	if err != nil {
		log.Fatalf("%v\n", err)
	}
	config.ReposPath = reposPath

	flag.StringVar(&configFile, "f", "", "config file path")
	flag.Parse()

	if _, err := toml.DecodeFile(configFile, &config); err != nil {
		log.Printf("[ERROR] %v", err)
		log.Print("[ERROR] Parsing config file, using default configuration")
	}
}

func Handler(w http.ResponseWriter, req *http.Request) {
	handlers := map[*regexp.Regexp]func(http.ResponseWriter, *http.Request, string){
		regexp.MustCompile("(.*?)/git-upload-pack$"):  UploadPack,
		regexp.MustCompile("(.*?)/git-receive-pack$"): ReceivePack,
		regexp.MustCompile("(.*?)/info/refs$"):        InfoRefs,
	}

	for re, handler := range handlers {
		if m := re.FindStringSubmatch(req.URL.Path); m != nil {
			repoPath := m[1]
			handler(w, req, repoPath)
			return
		}
	}
	w.WriteHeader(http.StatusBadRequest)
	w.Write([]byte("Bad Request"))
}

func main() {
	var logWriter io.Writer
	if config.LogFilePath != "" {
		var err error
		logWriter, err = os.OpenFile(config.LogFilePath, os.O_RDWR|os.O_APPEND, 0660)
		if err != nil {
			log.Printf("[WARN] %v", err)
		}
	}

	if logWriter == nil {
		logWriter = os.Stderr
	}

	filter := &logutils.LevelFilter{
		Levels:   []logutils.LogLevel{"DEBUG", "WARN", "ERROR"},
		MinLevel: logutils.LogLevel(config.LogLevel),
		Writer:   logWriter,
	}

	log.SetOutput(filter)

	mux := http.DefaultServeMux
	mux.HandleFunc("/", Handler)

	address := fmt.Sprintf("%s:%d", config.Bind, config.Port)
	timeout, err := time.ParseDuration(config.ShutdownTimeout)
	if err != nil {
		log.Fatalf("[ERROR] %v", err)
	}

	log.Printf("[INFO] Listening on %s...", address)
	log.Printf("[INFO] Serving Git repositories over HTTP from %s", config.ReposPath)
	graceful.Run(address, timeout, logger.Handler(mux, logger.AppName("gitd")))
}

// Runs git-upload-pack in a safe manner
func UploadPack(w http.ResponseWriter, req *http.Request, repoPath string) {
	if req.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}
	process := "git-upload-pack"
	cwd := filepath.Join(config.ReposPath, repoPath)

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-result", process))
	w.WriteHeader(http.StatusOK)

	cmd := exec.Command(process, "--stateless-rpc", ".")
	cmd.Dir = cwd
	runCommand(w, req.Body, cmd)
}

//Runs git-receive-pack in a safe manner
func ReceivePack(w http.ResponseWriter, req *http.Request, repoPath string) {
	if req.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}
	process := "git-receive-pack"
	cwd := filepath.Join(config.ReposPath, repoPath)

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-result", process))
	w.WriteHeader(http.StatusOK)

	cmd := exec.Command(process, "--stateless-rpc", ".")
	cmd.Dir = cwd
	runCommand(w, req.Body, cmd)
}

func InfoRefs(w http.ResponseWriter, req *http.Request, repoPath string) {
	if req.Method != "GET" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}

	process := req.URL.Query().Get("service")
	cwd := filepath.Join(config.ReposPath, repoPath)

	if process != "git-receive-pack" && process != "git-upload-pack" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Bad Request"))
		return
	}

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-advertisement", process))
	w.WriteHeader(http.StatusOK)

	w.Write(packetWrite(fmt.Sprintf("# service=%s\n", process)))
	w.Write(packetFlush())

	cmd := exec.Command(process, "--stateless-rpc", "--advertise-refs", ".")
	cmd.Dir = cwd
	runCommand(w, req.Body, cmd)
}

// Executes a shell command and pipes its output to HTTP response writer.
// DO NOT expose this function directly to end users as it creates a security breach
func runCommand(w io.Writer, r io.Reader, cmd *exec.Cmd) {
	if cmd.Dir != "" {
		cmd.Dir = sanitize(cmd.Dir)
	}

	log.Printf("[DEBUG] Running command from %s: %s %s ", cmd.Dir, cmd.Path, cmd.Args)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Printf("[ERROR] %v", err)
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Printf("[ERROR] %v", err)
	}

	if err := cmd.Start(); err != nil {
		log.Printf("[ERROR] %v", err)
	}

	io.Copy(stdin, r)
	io.Copy(w, stdout)
	cmd.Wait()
}

// Returns bytes of a git packet containing the given string
func packetWrite(str string) []byte {
	s := strconv.FormatInt(int64((len(str) + 4)), 16)

	m := len(s) % 4
	if m != 0 {
		s = strings.Repeat("0", 4-m) + s
	}

	return []byte(s + str)
}

func packetFlush() []byte {
	return []byte("0000")
}

// Sanitizes name to avoid overwriting sensitive system files
// or executing forbidden binaries
func sanitize(name string) string {
	// Gets rid of volume drive label in Windows
	if len(name) > 1 && name[1] == ':' && runtime.GOOS == "windows" {
		name = name[2:]
	}

	name = filepath.Clean(name)
	name = filepath.ToSlash(name)
	for strings.HasPrefix(name, "../") {
		name = name[3:]
	}
	return name
}

func checkGitVersion(major, minor, patch int) bool {
	git, err := exec.LookPath("git")
	if err != nil {
		log.Printf("[ERROR] %v", err)
		return false
	}

	cmd := exec.Command(git, "--version")
	var stdout string
	if stdout, _, err = runAndLog(cmd); err != nil {
		log.Printf("[ERROR] %v", err)
		return false
	}

	output := strings.Split(stdout, "\n")
	if len(output) < 2 {
		log.Printf("[DEBUG] git version output: %v", output)
		return false
	}

	parts := strings.Split(output[0], " ")
	if len(parts) < 3 {
		log.Printf("[DEBUG] git version parts: %v", parts)
		return false
	}

	version := strings.Split(parts[2], ".")
	major2, _ := strconv.Atoi(version[0])
	minor2, _ := strconv.Atoi(version[1])
	patch2, _ := strconv.Atoi(version[2])

	if major2 < major || minor2 < minor || patch2 < patch {
		log.Printf("[INFO] git version not supported: %d.%d.%d", major2, minor2, patch2)
		return false
	}

	return true
}

// Borrowed from https://github.com/mitchellh/packer/blob/master/builder/vmware/common/driver.go
func runAndLog(cmd *exec.Cmd) (string, string, error) {
	var stdout, stderr bytes.Buffer

	log.Printf("[GitD] Executing: %s %v", cmd.Path, cmd.Args[1:])
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		message := stderrString
		if message == "" {
			message = stdoutString
		}

		err = fmt.Errorf("[GitD] error: %s", message)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	// Replace these for Windows, we only want to deal with Unix
	// style line endings.
	returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1)
	returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1)

	return returnStdout, returnStderr, err
}
<|endoftext|>


<commit_before>package geo_skeleton

import (
	"fmt"
	"log"
	"math"
	"os"
	"time"
)

import (
	"github.com/paulmach/go.geojson"
	"github.com/sjsafranek/SkeletonDB"
)

// https://gist.github.com/DavidVaini/10308388
func Round(f float64) float64 {
	return math.Floor(f + .5)
}

func RoundToPrecision(f float64, places int) float64 {
	shift := math.Pow(10, float64(places))
	return Round(f*shift) / shift
}

// DB application Database
var (
	PRECISION        int = 0
	COMMIT_LOG_FILE string = "geo_skeleton_commit.log"
)

func NewGeoSkeletonDB(db_file string) Database {
	var geoDb = Database{
		File:  db_file,
		Table: "GeoJsonDatasources",
		DB:    skeleton.Database{File: db_file}}
	geoDb.Init()
	return geoDb
}

// Database strust for application.
type Database struct {
	Table            string
	File             string
	commit_log_queue chan string
	// Precision        int
	DB               skeleton.Database
	TS 				GeoTimeseriesDB
}

func (self Database) Init() {

	self.TS := GeoTimeseriesDB{}
	self.TS.Init()

	self.DB.Init()

	// Set initial data precision
	PRECISION = 8

	// start commit log
	go self.StartCommitLog()

	// default table
	if "" == self.Table {
		self.Table = "GeoJSONLayers"
	}

	conn := self.DB.Connect()
	defer conn.Close()

	err := self.DB.CreateTable(conn, self.Table)
	if nil != err {
		panic(err)
	}
}

// Starts Database commit log
func (self *Database) StartCommitLog() {
	self.commit_log_queue = make(chan string, 10000)
	// open file to write database commit log
	COMMIT_LOG, err := os.OpenFile(COMMIT_LOG_FILE, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		log.Println(err)
	}
	defer COMMIT_LOG.Close()
	// read from chan and write to file
	for {
		if len(self.commit_log_queue) > 0 {
			line := <-self.commit_log_queue
			if _, err := COMMIT_LOG.WriteString(line + "\n"); err != nil {
				panic(err)
			}
		} else {
			time.Sleep(1000 * time.Millisecond)
		}
	}
}

// CommitQueueLength returns length of database commit_log_queue
// @returns int
func (self *Database) CommitQueueLength() int {
	return len(self.commit_log_queue)
}

// NewLayer creates new datasource layer
// @returns string - datasource id
// @returns Error
// TODO: RENAME TO NewDatasource
func (self *Database) NewLayer() (string, error) {
	// create geojson
	datasource_id, _ := NewUUID()
	geojs := geojson.NewFeatureCollection()
	// convert to bytes
	value, err := geojs.MarshalJSON()
	if err != nil {
		return "", nil
	}
	self.commit_log_queue <- `{"method": "create_datasource", "data": { "datasource": "` + datasource_id + `", "layer": ` + string(value) + `}}`
	// Insert layer into database
	err = self.DB.Insert(self.Table, datasource_id, value)
	if err != nil {
		panic(err)
	}
	return datasource_id, err
}

// InsertLayer inserts layer into database
// @param datasource {string}
// @param geojs {Geojson}
// @returns Error
func (self *Database) InsertLayer(datasource_id string, geojs *geojson.FeatureCollection) error {
	// convert to bytes
	value, err := geojs.MarshalJSON()
	if err != nil {
		return err
	}

	go self.TS.UpdateTimeseriesDatasource(datasource_id, value)

	err = self.DB.Insert(self.Table, datasource_id, value)
	if err != nil {
		panic(err)
	}

	return err
}

// GetLayer returns layer from database
// @param datasource {string}
// @returns Geojson
// @returns Error
func (self *Database) GetLayer(datasource_id string) (*geojson.FeatureCollection, error) {
	val, err := self.DB.Select(self.Table, datasource_id)
	if err != nil {
		return nil, err
	}
	if "" == string(val) {
		return nil, fmt.Errorf("Datasource not found")
	}
	// Read to struct
	geojs, err := geojson.UnmarshalFeatureCollection(val)
	if err != nil {
		return geojs, err
	}
	return geojs, nil
}

// GetLayers returns all datasource_ids from database
// @param datasource {string}
// @returns Geojson
// @returns Error
func (self *Database) GetLayers() ([]string, error) {
	val, err := self.DB.SelectAll(self.Table)
	if err != nil {
		return nil, err
	}
	return val, nil
}

// DeleteLayer deletes layer from database
// @param datasource {string}
// @returns Error
func (self *Database) DeleteLayer(datasource_id string) error {
	self.commit_log_queue <- `{"method": "delete_layer", "data": { "datasource": "` + datasource_id + `"}}`
	err := self.DB.Remove(datasource_id, self.Table)
	return err
}

func (self *Database) normalizeGeometry(feat *geojson.Feature) (*geojson.Feature, error) {
	// FIT TO 7 - 8 DECIMAL PLACES OF PRECISION
	if nil == feat.Geometry {
		return nil, fmt.Errorf("Feature has no geometry!")
	}

	switch feat.Geometry.Type {

	case geojson.GeometryPoint:
		// []float64
		feat.Geometry.Point[0] = RoundToPrecision(feat.Geometry.Point[0], PRECISION)
		feat.Geometry.Point[1] = RoundToPrecision(feat.Geometry.Point[1], PRECISION)

	case geojson.GeometryMultiPoint:
		// [][]float64
		for i := range feat.Geometry.MultiPoint {
			for j := range feat.Geometry.MultiPoint[i] {
				feat.Geometry.MultiPoint[i][j] = RoundToPrecision(feat.Geometry.MultiPoint[i][j], PRECISION)
			}
		}

	case geojson.GeometryLineString:
		// [][]float64
		for i := range feat.Geometry.LineString {
			for j := range feat.Geometry.LineString[i] {
				feat.Geometry.LineString[i][j] = RoundToPrecision(feat.Geometry.LineString[i][j], PRECISION)
			}
		}

	case geojson.GeometryMultiLineString:
		// [][][]float64
		for i := range feat.Geometry.MultiLineString {
			for j := range feat.Geometry.MultiLineString[i] {
				for k := range feat.Geometry.MultiLineString[i][j] {
					feat.Geometry.MultiLineString[i][j][k] = RoundToPrecision(feat.Geometry.MultiLineString[i][j][k], PRECISION)
				}
			}
		}

	case geojson.GeometryPolygon:
		// [][][]float64
		for i := range feat.Geometry.Polygon {
			for j := range feat.Geometry.Polygon[i] {
				for k := range feat.Geometry.Polygon[i][j] {
					feat.Geometry.Polygon[i][j][k] = RoundToPrecision(feat.Geometry.Polygon[i][j][k], PRECISION)
				}
			}
		}

	case geojson.GeometryMultiPolygon:
		// [][][][]float64
		for i := range feat.Geometry.MultiPolygon {
			log.Printf("%v\n", feat.Geometry.MultiPolygon[i])
		}

	}

	/*
		//case GeometryCollection:
		//	geo.Geometries = g.Geometries
		//	// log.Printf("%v\n", feat.Geometry.Geometries)

	*/
	return feat, nil
}

func (self *Database) normalizeProperties(feat *geojson.Feature, featCollection *geojson.FeatureCollection) *geojson.Feature {

	// check if nil map
	if nil == feat.Properties {
		feat.Properties = make(map[string]interface{})
	}

	if 0 == len(featCollection.Features) {
		return feat
	}
	// Standardize properties for new feature
	for j := range featCollection.Features[0].Properties {
		if _, ok := feat.Properties[j]; !ok {
			feat.Properties[j] = ""
		}
	}

	// Standardize properties for existing features
	for i := range featCollection.Features {
		for j := range feat.Properties {
			if _, ok := featCollection.Features[i].Properties[j]; !ok {
				featCollection.Features[i].Properties[j] = ""
			}
		}
	}

	return feat
}

// InsertFeature adds feature to layer. Updates layer in Database
// @param datasource {string}
// @param feat {Geojson Feature}
// @returns Error
func (self *Database) InsertFeature(datasource_id string, feat *geojson.Feature) error {

	if nil == feat {
		return fmt.Errorf("feature value is <nil>!")
	}

	// Get layer from database
	featCollection, err := self.GetLayer(datasource_id)
	if err != nil {
		return err
	}

	// Apply required columns
	now := time.Now().Unix()

	// check if nil map
	if nil == feat.Properties {
		feat.Properties = make(map[string]interface{})
	}

	feat.Properties["is_active"] = true
	feat.Properties["is_deleted"] = false
	feat.Properties["date_created"] = now
	feat.Properties["date_modified"] = now
	feat.Properties["geo_id"] = fmt.Sprintf("%v", now)

	feat, err = self.normalizeGeometry(feat)
	if nil != err {
		return err
	}

	feat = self.normalizeProperties(feat, featCollection)

	// Write to commit log
	value, err := feat.MarshalJSON()
	if err != nil {
		return err
	}
	self.commit_log_queue <- `{"method": "insert_feature", "data": { "datasource": "` + datasource_id + `", "feature": ` + string(value) + `}}`

	// Add new feature to layer
	featCollection.AddFeature(feat)

	// insert layer
	err = self.InsertLayer(datasource_id, featCollection)
	if err != nil {
		panic(err)
	}
	return err
}

// EditFeature Edits feature in layer. Updates layer in Database
// @param datasource {string}
// @param geo_id {string}
// @param feat {Geojson Feature}
// @returns Error
func (self *Database) EditFeature(datasource_id string, geo_id string, feat *geojson.Feature) error {

	// Get layer from database
	featCollection, err := self.GetLayer(datasource_id)
	if err != nil {
		return err
	}

	feature_exists := false

	for i := range featCollection.Features {
		if geo_id == fmt.Sprintf("%v", featCollection.Features[i].Properties["geo_id"]) {

			now := time.Now().Unix()
			feat.Properties["date_modified"] = now

			feat, err = self.normalizeGeometry(feat)
			if nil != err {
				return err
			}

			feat = self.normalizeProperties(feat, featCollection)
			featCollection.Features[i] = feat
			// Write to commit log
			value, err := feat.MarshalJSON()
			if err != nil {
				return err
			}
			self.commit_log_queue <- `{"method": "edit_feature", "data": { "datasource": "` + datasource_id + `", "geo_id": "` + geo_id + `", "feature": ` + string(value) + `}}`
			feature_exists = true
		}
	}

	if !feature_exists {
		return fmt.Errorf("feature not found!")
	}

	// insert layer
	err = self.InsertLayer(datasource_id, featCollection)
	if err != nil {
		panic(err)
	}
	return err
}
<commit_msg>fixed geots<commit_after>package geo_skeleton

import (
	"fmt"
	"log"
	"math"
	"os"
	"time"
)

import (
	"github.com/paulmach/go.geojson"
	"github.com/sjsafranek/SkeletonDB"
)

// https://gist.github.com/DavidVaini/10308388
func Round(f float64) float64 {
	return math.Floor(f + .5)
}

func RoundToPrecision(f float64, places int) float64 {
	shift := math.Pow(10, float64(places))
	return Round(f*shift) / shift
}

// DB application Database
var (
	PRECISION        int = 0
	COMMIT_LOG_FILE string = "geo_skeleton_commit.log"
)

func NewGeoSkeletonDB(db_file string) Database {
	var geoDb = Database{
		File:  db_file,
		Table: "GeoJsonDatasources",
		DB:    skeleton.Database{File: db_file}}
	geoDb.Init()
	return geoDb
}

// Database strust for application.
type Database struct {
	Table            string
	File             string
	commit_log_queue chan string
	// Precision        int
	DB               skeleton.Database
	TS 				GeoTimeseriesDB
}

func (self Database) Init() {

	self.TS = GeoTimeseriesDB{}
	self.TS.Init()

	self.DB.Init()

	// Set initial data precision
	PRECISION = 8

	// start commit log
	go self.StartCommitLog()

	// default table
	if "" == self.Table {
		self.Table = "GeoJSONLayers"
	}

	conn := self.DB.Connect()
	defer conn.Close()

	err := self.DB.CreateTable(conn, self.Table)
	if nil != err {
		panic(err)
	}
}

// Starts Database commit log
func (self *Database) StartCommitLog() {
	self.commit_log_queue = make(chan string, 10000)
	// open file to write database commit log
	COMMIT_LOG, err := os.OpenFile(COMMIT_LOG_FILE, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		log.Println(err)
	}
	defer COMMIT_LOG.Close()
	// read from chan and write to file
	for {
		if len(self.commit_log_queue) > 0 {
			line := <-self.commit_log_queue
			if _, err := COMMIT_LOG.WriteString(line + "\n"); err != nil {
				panic(err)
			}
		} else {
			time.Sleep(1000 * time.Millisecond)
		}
	}
}

// CommitQueueLength returns length of database commit_log_queue
// @returns int
func (self *Database) CommitQueueLength() int {
	return len(self.commit_log_queue)
}

// NewLayer creates new datasource layer
// @returns string - datasource id
// @returns Error
// TODO: RENAME TO NewDatasource
func (self *Database) NewLayer() (string, error) {
	// create geojson
	datasource_id, _ := NewUUID()
	geojs := geojson.NewFeatureCollection()
	// convert to bytes
	value, err := geojs.MarshalJSON()
	if err != nil {
		return "", nil
	}
	self.commit_log_queue <- `{"method": "create_datasource", "data": { "datasource": "` + datasource_id + `", "layer": ` + string(value) + `}}`
	// Insert layer into database
	err = self.DB.Insert(self.Table, datasource_id, value)
	if err != nil {
		panic(err)
	}
	return datasource_id, err
}

// InsertLayer inserts layer into database
// @param datasource {string}
// @param geojs {Geojson}
// @returns Error
func (self *Database) InsertLayer(datasource_id string, geojs *geojson.FeatureCollection) error {
	// convert to bytes
	value, err := geojs.MarshalJSON()
	if err != nil {
		return err
	}

	go self.TS.UpdateTimeseriesDatasource(datasource_id, value)

	err = self.DB.Insert(self.Table, datasource_id, value)
	if err != nil {
		panic(err)
	}

	return err
}

// GetLayer returns layer from database
// @param datasource {string}
// @returns Geojson
// @returns Error
func (self *Database) GetLayer(datasource_id string) (*geojson.FeatureCollection, error) {
	val, err := self.DB.Select(self.Table, datasource_id)
	if err != nil {
		return nil, err
	}
	if "" == string(val) {
		return nil, fmt.Errorf("Datasource not found")
	}
	// Read to struct
	geojs, err := geojson.UnmarshalFeatureCollection(val)
	if err != nil {
		return geojs, err
	}
	return geojs, nil
}

// GetLayers returns all datasource_ids from database
// @param datasource {string}
// @returns Geojson
// @returns Error
func (self *Database) GetLayers() ([]string, error) {
	val, err := self.DB.SelectAll(self.Table)
	if err != nil {
		return nil, err
	}
	return val, nil
}

// DeleteLayer deletes layer from database
// @param datasource {string}
// @returns Error
func (self *Database) DeleteLayer(datasource_id string) error {
	self.commit_log_queue <- `{"method": "delete_layer", "data": { "datasource": "` + datasource_id + `"}}`
	err := self.DB.Remove(datasource_id, self.Table)
	return err
}

func (self *Database) normalizeGeometry(feat *geojson.Feature) (*geojson.Feature, error) {
	// FIT TO 7 - 8 DECIMAL PLACES OF PRECISION
	if nil == feat.Geometry {
		return nil, fmt.Errorf("Feature has no geometry!")
	}

	switch feat.Geometry.Type {

	case geojson.GeometryPoint:
		// []float64
		feat.Geometry.Point[0] = RoundToPrecision(feat.Geometry.Point[0], PRECISION)
		feat.Geometry.Point[1] = RoundToPrecision(feat.Geometry.Point[1], PRECISION)

	case geojson.GeometryMultiPoint:
		// [][]float64
		for i := range feat.Geometry.MultiPoint {
			for j := range feat.Geometry.MultiPoint[i] {
				feat.Geometry.MultiPoint[i][j] = RoundToPrecision(feat.Geometry.MultiPoint[i][j], PRECISION)
			}
		}

	case geojson.GeometryLineString:
		// [][]float64
		for i := range feat.Geometry.LineString {
			for j := range feat.Geometry.LineString[i] {
				feat.Geometry.LineString[i][j] = RoundToPrecision(feat.Geometry.LineString[i][j], PRECISION)
			}
		}

	case geojson.GeometryMultiLineString:
		// [][][]float64
		for i := range feat.Geometry.MultiLineString {
			for j := range feat.Geometry.MultiLineString[i] {
				for k := range feat.Geometry.MultiLineString[i][j] {
					feat.Geometry.MultiLineString[i][j][k] = RoundToPrecision(feat.Geometry.MultiLineString[i][j][k], PRECISION)
				}
			}
		}

	case geojson.GeometryPolygon:
		// [][][]float64
		for i := range feat.Geometry.Polygon {
			for j := range feat.Geometry.Polygon[i] {
				for k := range feat.Geometry.Polygon[i][j] {
					feat.Geometry.Polygon[i][j][k] = RoundToPrecision(feat.Geometry.Polygon[i][j][k], PRECISION)
				}
			}
		}

	case geojson.GeometryMultiPolygon:
		// [][][][]float64
		for i := range feat.Geometry.MultiPolygon {
			log.Printf("%v\n", feat.Geometry.MultiPolygon[i])
		}

	}

	/*
		//case GeometryCollection:
		//	geo.Geometries = g.Geometries
		//	// log.Printf("%v\n", feat.Geometry.Geometries)

	*/
	return feat, nil
}

func (self *Database) normalizeProperties(feat *geojson.Feature, featCollection *geojson.FeatureCollection) *geojson.Feature {

	// check if nil map
	if nil == feat.Properties {
		feat.Properties = make(map[string]interface{})
	}

	if 0 == len(featCollection.Features) {
		return feat
	}
	// Standardize properties for new feature
	for j := range featCollection.Features[0].Properties {
		if _, ok := feat.Properties[j]; !ok {
			feat.Properties[j] = ""
		}
	}

	// Standardize properties for existing features
	for i := range featCollection.Features {
		for j := range feat.Properties {
			if _, ok := featCollection.Features[i].Properties[j]; !ok {
				featCollection.Features[i].Properties[j] = ""
			}
		}
	}

	return feat
}

// InsertFeature adds feature to layer. Updates layer in Database
// @param datasource {string}
// @param feat {Geojson Feature}
// @returns Error
func (self *Database) InsertFeature(datasource_id string, feat *geojson.Feature) error {

	if nil == feat {
		return fmt.Errorf("feature value is <nil>!")
	}

	// Get layer from database
	featCollection, err := self.GetLayer(datasource_id)
	if err != nil {
		return err
	}

	// Apply required columns
	now := time.Now().Unix()

	// check if nil map
	if nil == feat.Properties {
		feat.Properties = make(map[string]interface{})
	}

	feat.Properties["is_active"] = true
	feat.Properties["is_deleted"] = false
	feat.Properties["date_created"] = now
	feat.Properties["date_modified"] = now
	feat.Properties["geo_id"] = fmt.Sprintf("%v", now)

	feat, err = self.normalizeGeometry(feat)
	if nil != err {
		return err
	}

	feat = self.normalizeProperties(feat, featCollection)

	// Write to commit log
	value, err := feat.MarshalJSON()
	if err != nil {
		return err
	}
	self.commit_log_queue <- `{"method": "insert_feature", "data": { "datasource": "` + datasource_id + `", "feature": ` + string(value) + `}}`

	// Add new feature to layer
	featCollection.AddFeature(feat)

	// insert layer
	err = self.InsertLayer(datasource_id, featCollection)
	if err != nil {
		panic(err)
	}
	return err
}

// EditFeature Edits feature in layer. Updates layer in Database
// @param datasource {string}
// @param geo_id {string}
// @param feat {Geojson Feature}
// @returns Error
func (self *Database) EditFeature(datasource_id string, geo_id string, feat *geojson.Feature) error {

	// Get layer from database
	featCollection, err := self.GetLayer(datasource_id)
	if err != nil {
		return err
	}

	feature_exists := false

	for i := range featCollection.Features {
		if geo_id == fmt.Sprintf("%v", featCollection.Features[i].Properties["geo_id"]) {

			now := time.Now().Unix()
			feat.Properties["date_modified"] = now

			feat, err = self.normalizeGeometry(feat)
			if nil != err {
				return err
			}

			feat = self.normalizeProperties(feat, featCollection)
			featCollection.Features[i] = feat
			// Write to commit log
			value, err := feat.MarshalJSON()
			if err != nil {
				return err
			}
			self.commit_log_queue <- `{"method": "edit_feature", "data": { "datasource": "` + datasource_id + `", "geo_id": "` + geo_id + `", "feature": ` + string(value) + `}}`
			feature_exists = true
		}
	}

	if !feature_exists {
		return fmt.Errorf("feature not found!")
	}

	// insert layer
	err = self.InsertLayer(datasource_id, featCollection)
	if err != nil {
		panic(err)
	}
	return err
}
<|endoftext|>