<commit_before>package crawler

import (
	"fmt"
	"log"
	"net/http"
	//	"strings"
	"text/template"

	"github.com/AaronGoldman/ccfs/objects"
	//	"github.com/AaronGoldman/ccfs/services"
)

// This function handles web requests for the index of the crawler
func webIndexHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(fmt.Sprintf("Welcome to the web index!\n")))
	index := struct {
		Blobs   interface{}
		Commits interface{}
		Tags    interface{}
	}{
		Blobs:   blobIndex,
		Commits: commitIndex,
		Tags:    tagIndex,
	}

	t, err := template.New("WebIndex template").Parse(
		`{{define "sliceTemplate"}}{{range $lice:= .}}
				{{$lice}}{{end}}{{end}}{{define "mapTemplate"}}{{range $key, $value:= .}}
				Version: {{$key}} HCID: {{$value}}{{end}}{{end}}{{define "blobIndexEntryTemplate"}}
		Type: {{.TypeString}}
		Size: {{.Size}}
		Name Segment: {{range $key, $value:= .NameSeg}}
			{{$key}}{{template "sliceTemplate" $value}}{{end}}
		Descendants: {{range $key, $value:= .Descendants}}
			Version: {{$value}} HCID: {{$key}}{{end}}
{{end}}{{define "commitIndexEntryTemplate"}}
		Aliases: {{range $key, $value:= .NameSeg}}
			{{$key}}{{template "sliceTemplate" $value}}{{end}}
		Collection: {{range $key, $value:= .Version}}
			Version: {{$key}} HCID: {{$value}}{{end}}
{{end}}{{define "tagIndexEntryTemplate"}}
		Aliases: {{range $key, $value:= .NameSeg}}
			{{$key}}{{template "sliceTemplate" $value}}{{end}}
		Collection: {{range $key, $value:= .Version}}
			Name Segment: {{$key}}{{template "mapTemplate" $value}}{{end}}
{{end}}Blobs	{{range $key, $value:= .Blobs}}
	{{$key}}{{template "blobIndexEntryTemplate" $value}}{{end}}
Commits	{{range $key, $value:= .Commits}}
	{{$key}}{{template "commitIndexEntryTemplate" $value}}{{end}}
Tags		{{range $key, $value:= .Tags}}
	{{$key}}{{template "tagIndexEntryTemplate" $value}}{{end}}
`)
	if err != nil {
		log.Println(err)
		http.Error(
			w,
			fmt.Sprintf("HTTP Error 500 Internal Indexer server error\n%s\n", err),
			500,
		)
	} else {
		t.Execute(w, index) //merge template ‘t’ with content of ‘index’
	}
}

type blobIndexEntry struct {
	TypeString  string
	Size        int
	NameSeg     map[ /*nameSeg*/ string] /*referringHID*/ []string
	Descendants map[ /*referringHCID*/ string] /*versionNumber*/ int64
}

func (indexEntry blobIndexEntry) insertSize(size int) blobIndexEntry {
	indexEntry.Size = size
	return indexEntry
}

func (indexEntry blobIndexEntry) insertType(typeString string) blobIndexEntry {
	indexEntry.TypeString = typeString
	return indexEntry
}

func (indexEntry blobIndexEntry) insertNameSegment(
	nameSeg string,
	referHID string,
) blobIndexEntry {
	if indexEntry.NameSeg == nil {
		indexEntry.NameSeg = make(map[string][]string)
	}
	if _, present := indexEntry.NameSeg[nameSeg]; !present {
		indexEntry.NameSeg[nameSeg] = []string{referHID}
	} else {
		indexEntry.NameSeg[nameSeg] = append(
			indexEntry.NameSeg[nameSeg],
			referHID,
		)
	}
	return indexEntry
}

func (indexEntry blobIndexEntry) insertDescendant(
	versionNumber int64,
	descendantHCID objects.HCID,
) blobIndexEntry {
	if indexEntry.Descendants == nil {
		indexEntry.Descendants = make(map[string]int64)
	}
	if _, present := indexEntry.Descendants[descendantHCID.Hex()]; !present {
		indexEntry.Descendants[descendantHCID.Hex()] = versionNumber
	}

	return indexEntry
}

func insertDescendantS(
	parents []objects.HCID,
	descendant objects.HCID,
	version int64,
) {
	if blobIndex == nil {
		blobIndex = make(map[string]blobIndexEntry)
	}
	for _, entryParent := range parents {
		if _, present := blobIndex[entryParent.Hex()]; !present {
			blobIndex[entryParent.Hex()] = blobIndexEntry{}
		}
		blobIndex[entryParent.Hex()] =
			blobIndex[entryParent.Hex()].insertDescendant(version, descendant)
	}
}

type commitIndexEntry struct {
	NameSeg map[string][]string
	Version map[ /*versionNumber*/ int64]objects.HCID
}

func (indexEntry commitIndexEntry) insertVersion(
	versionNumber int64,
	instanceHCID objects.HCID,
) commitIndexEntry {
	if indexEntry.Version == nil {
		indexEntry.Version = make(map[int64]objects.HCID)
	}
	indexEntry.Version[versionNumber] = instanceHCID
	return indexEntry
}

func (indexEntry commitIndexEntry) insertNameSegment(
	nameSeg string,
	referHID string,
) commitIndexEntry {
	if indexEntry.NameSeg == nil {
		indexEntry.NameSeg = make(map[string][]string)
	}
	if _, present := indexEntry.NameSeg[nameSeg]; !present {
		indexEntry.NameSeg[nameSeg] = []string{referHID}
	} else {
		indexEntry.NameSeg[nameSeg] = append(
			indexEntry.NameSeg[nameSeg],
			referHID,
		)
	}
	return indexEntry
}

type tagIndexEntry struct {
	NameSeg map[string][]string
	Version map[ /*nameSeg*/ string]map[ /*versionNumber*/ int64]objects.HCID
}

func (indexEntry tagIndexEntry) insertNameSegment(
	nameSeg string,
	referHID string,
) tagIndexEntry {
	if indexEntry.NameSeg == nil {
		indexEntry.NameSeg = make(map[string][]string)
	}
	if _, present := indexEntry.NameSeg[nameSeg]; !present {
		indexEntry.NameSeg[nameSeg] = []string{referHID}
	} else {
		indexEntry.NameSeg[nameSeg] = append(
			indexEntry.NameSeg[nameSeg],
			referHID,
		)
	}
	return indexEntry
}

func (indexEntry tagIndexEntry) insertVersion(
	versionNumber int64,
	nameSeg string,
	instanceHCID objects.HCID,
) tagIndexEntry {
	if indexEntry.Version == nil {
		indexEntry.Version = make(map[string]map[int64]objects.HCID)
	}
	if _, present := indexEntry.Version[nameSeg]; !present {
		indexEntry.Version[nameSeg] = make(map[int64]objects.HCID)
	}
	indexEntry.Version[nameSeg][versionNumber] = instanceHCID
	return indexEntry
}

func indexNameSegment(typeString, targetHex, referingHex, nameSeg string) {
	if nameSegmentIndex == nil {
		nameSegmentIndex = make(map[string]map[nameSegmentIndexEntry]int)
	}

	if _, present := nameSegmentIndex[nameSeg]; !present {
		nameSegmentIndex[nameSeg] = make(map[nameSegmentIndexEntry]int)
	}
	nameSegmentIndex[nameSeg][nameSegmentIndexEntry{typeString, targetHex}] += 1

	switch typeString {
	case "blob", "list":
		if blobIndex == nil {
			blobIndex = make(map[string]blobIndexEntry)
		}
		if _, present := blobIndex[targetHex]; !present {
			blobIndex[targetHex] = blobIndexEntry{}
		}
		blobIndex[targetHex] = blobIndex[targetHex].insertNameSegment(
			nameSeg,
			referingHex,
		)

	case "commit":
		if commitIndex == nil {
			commitIndex = make(map[string]commitIndexEntry)
		}
		if _, present := commitIndex[targetHex]; !present {
			commitIndex[targetHex] = commitIndexEntry{}
		}
		commitIndex[targetHex] = commitIndex[targetHex].insertNameSegment(
			nameSeg,
			referingHex,
		)
	case "tag":
		if tagIndex == nil {
			tagIndex = make(map[string]tagIndexEntry)
		}
		if _, present := tagIndex[targetHex]; !present {
			tagIndex[targetHex] = tagIndexEntry{}
		}
		tagIndex[targetHex] = tagIndex[targetHex].insertNameSegment(
			nameSeg,
			referingHex,
		)
	default:
		log.Printf("Received invalid typestring: %s\n", typeString)
	}
}

var nameSegmentIndex map[string]map[nameSegmentIndexEntry]int

type nameSegmentIndexEntry struct {
	typeString string
	hash       string
}

var blobIndex map[string]blobIndexEntry

func indexBlob(inBlob objects.Blob) {
	if blobIndex == nil {
		blobIndex = make(map[string]blobIndexEntry)
	}
	hashHex := inBlob.Hash().Hex()
	if _, present := blobIndex[hashHex]; !present {
		blobIndex[hashHex] = blobIndexEntry{}
	}
	blobIndex[hashHex] = blobIndex[hashHex].insertSize(len(inBlob))
	blobIndex[hashHex] = blobIndex[hashHex].insertType("Blob")
}

func indexList(inList objects.List) {
	indexBlob(inList.Bytes()) //Indexing Lists as blobs because they are also blobs
	hashHex := inList.Hash().Hex()
	blobIndex[hashHex] = blobIndex[hashHex].insertType("List")
	for nameSeg, entry := range inList {
		indexNameSegment(
			entry.TypeString,
			entry.Hash.Hex(),
			inList.Hash().Hex(),
			nameSeg,
		)
	}
}

var commitIndex map[string]commitIndexEntry

func indexCommit(inCommit objects.Commit) {
	indexBlob(inCommit.Bytes())
	hashHex := inCommit.Hash().Hex()
	blobIndex[hashHex] = blobIndex[hashHex].insertType("Commit")
	blobIndex[inCommit.Hkid.Hex()] =
		blobIndex[inCommit.Hkid.Hex()].insertType("Repository Key")
	if commitIndex == nil {
		commitIndex = make(map[string]commitIndexEntry)
	}
	if _, present := commitIndex[inCommit.Hkid.Hex()]; !present {
		commitIndex[inCommit.Hkid.Hex()] = commitIndexEntry{}
	}
	commitIndex[inCommit.Hkid.Hex()] =
		commitIndex[inCommit.Hkid.Hex()].insertVersion(
			inCommit.Version,
			inCommit.Hash(),
		)
	insertDescendantS(inCommit.Parents, inCommit.Hash(), inCommit.Version)
}

var tagIndex map[string]tagIndexEntry

func indexTag(inTag objects.Tag) {
	indexBlob(inTag.Bytes())
	hashHex := inTag.Hash().Hex()
	blobIndex[hashHex] = blobIndex[hashHex].insertType("Tag")
	blobIndex[inTag.Hkid.Hex()] =
		blobIndex[inTag.Hkid.Hex()].insertType("Domain Key")
	indexNameSegment(
		inTag.TypeString,
		inTag.HashBytes.Hex(),
		inTag.Hash().Hex(),
		inTag.NameSegment,
	)

	if tagIndex == nil {
		tagIndex = make(map[string]tagIndexEntry)
	}
	if _, present := tagIndex[inTag.Hkid.Hex()]; !present {
		tagIndex[inTag.Hkid.Hex()] = tagIndexEntry{}
	}
	tagIndex[inTag.Hkid.Hex()] = tagIndex[inTag.Hkid.Hex()].insertVersion(
		inTag.Version,
		inTag.NameSegment,
		inTag.Hash(),
	)
	insertDescendantS(inTag.Parents, inTag.Hash(), inTag.Version)
}
<commit_msg>Index Page Reformat<commit_after>package crawler

import (
	"fmt"
	"log"
	"net/http"
	//	"strings"
	"text/template"

	"github.com/AaronGoldman/ccfs/objects"
	//	"github.com/AaronGoldman/ccfs/services"
)

// This function handles web requests for the index of the crawler
func webIndexHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(fmt.Sprintf("Welcome to the web index!\n")))
	index := struct {
		NameSegments interface{}
		Blobs        interface{}
		Commits      interface{}
		Tags         interface{}
	}{
		NameSegments: nameSegmentIndex,
		Blobs:        blobIndex,
		Commits:      commitIndex,
		Tags:         tagIndex,
	}

	t, err := template.New("WebIndex template").Parse(
		`{{define "sliceTemplate"}}{{range $lice:= .}}
				{{$lice}}{{end}}{{end}}{{define "mapTemplate"}}{{range $key, $value:= .}}
				Version: {{$key}} HCID: {{$value}}{{end}}{{end}}{{define "nameSegmentIndexEntryTemplate"}}{{range $key, $value:= .}}
			Type: {{$key.TypeString}} HID: {{$key.Hash}} Count: {{$value}}{{end}}
{{end}}{{define "blobIndexEntryTemplate"}}
		Type: {{.TypeString}}
		Size: {{.Size}}
		Name Segment: {{range $key, $value:= .NameSeg}}
			{{$key}}{{template "sliceTemplate" $value}}{{end}}
		Descendants: {{range $key, $value:= .Descendants}}
			Version: {{$value}} HCID: {{$key}}{{end}}
{{end}}{{define "commitIndexEntryTemplate"}}
		Aliases: {{range $key, $value:= .NameSeg}}
			{{$key}}{{template "sliceTemplate" $value}}{{end}}
		Collection: {{range $key, $value:= .Version}}
			Version: {{$key}} HCID: {{$value}}{{end}}
{{end}}{{define "tagIndexEntryTemplate"}}
		Aliases: {{range $key, $value:= .NameSeg}}
			{{$key}}{{template "sliceTemplate" $value}}{{end}}
		Collection: {{range $key, $value:= .Version}}
			Name Segment: {{$key}}{{template "mapTemplate" $value}}{{end}}
{{end}}Name Segments	{{range $key, $value:= .NameSegments}}
	{{$key}}{{template "nameSegmentIndexEntryTemplate" $value}}{{end}}
Blobs	{{range $key, $value:= .Blobs}}
	{{$key}}{{template "blobIndexEntryTemplate" $value}}{{end}}
Commits	{{range $key, $value:= .Commits}}
	{{$key}}{{template "commitIndexEntryTemplate" $value}}{{end}}
Tags		{{range $key, $value:= .Tags}}
	{{$key}}{{template "tagIndexEntryTemplate" $value}}{{end}}
`)
	if err != nil {
		log.Println(err)
		http.Error(
			w,
			fmt.Sprintf("HTTP Error 500 Internal Indexer server error\n%s\n", err),
			500,
		)
	} else {
		t.Execute(w, index) //merge template ‘t’ with content of ‘index’
	}
}

type blobIndexEntry struct {
	TypeString  string
	Size        int
	NameSeg     map[ /*nameSeg*/ string] /*referringHID*/ []string
	Descendants map[ /*referringHCID*/ string] /*versionNumber*/ int64
}

func (indexEntry blobIndexEntry) insertSize(size int) blobIndexEntry {
	indexEntry.Size = size
	return indexEntry
}

func (indexEntry blobIndexEntry) insertType(typeString string) blobIndexEntry {
	indexEntry.TypeString = typeString
	return indexEntry
}

func (indexEntry blobIndexEntry) insertNameSegment(
	nameSeg string,
	referHID string,
) blobIndexEntry {
	if indexEntry.NameSeg == nil {
		indexEntry.NameSeg = make(map[string][]string)
	}
	if _, present := indexEntry.NameSeg[nameSeg]; !present {
		indexEntry.NameSeg[nameSeg] = []string{referHID}
	} else {
		indexEntry.NameSeg[nameSeg] = append(
			indexEntry.NameSeg[nameSeg],
			referHID,
		)
	}
	return indexEntry
}

func (indexEntry blobIndexEntry) insertDescendant(
	versionNumber int64,
	descendantHCID objects.HCID,
) blobIndexEntry {
	if indexEntry.Descendants == nil {
		indexEntry.Descendants = make(map[string]int64)
	}
	if _, present := indexEntry.Descendants[descendantHCID.Hex()]; !present {
		indexEntry.Descendants[descendantHCID.Hex()] = versionNumber
	}

	return indexEntry
}

func insertDescendantS(
	parents []objects.HCID,
	descendant objects.HCID,
	version int64,
) {
	if blobIndex == nil {
		blobIndex = make(map[string]blobIndexEntry)
	}
	for _, entryParent := range parents {
		if _, present := blobIndex[entryParent.Hex()]; !present {
			blobIndex[entryParent.Hex()] = blobIndexEntry{}
		}
		blobIndex[entryParent.Hex()] =
			blobIndex[entryParent.Hex()].insertDescendant(version, descendant)
	}
}

type commitIndexEntry struct {
	NameSeg map[string][]string
	Version map[ /*versionNumber*/ int64]objects.HCID
}

func (indexEntry commitIndexEntry) insertVersion(
	versionNumber int64,
	instanceHCID objects.HCID,
) commitIndexEntry {
	if indexEntry.Version == nil {
		indexEntry.Version = make(map[int64]objects.HCID)
	}
	indexEntry.Version[versionNumber] = instanceHCID
	return indexEntry
}

func (indexEntry commitIndexEntry) insertNameSegment(
	nameSeg string,
	referHID string,
) commitIndexEntry {
	if indexEntry.NameSeg == nil {
		indexEntry.NameSeg = make(map[string][]string)
	}
	if _, present := indexEntry.NameSeg[nameSeg]; !present {
		indexEntry.NameSeg[nameSeg] = []string{referHID}
	} else {
		indexEntry.NameSeg[nameSeg] = append(
			indexEntry.NameSeg[nameSeg],
			referHID,
		)
	}
	return indexEntry
}

type tagIndexEntry struct {
	NameSeg map[string][]string
	Version map[ /*nameSeg*/ string]map[ /*versionNumber*/ int64]objects.HCID
}

func (indexEntry tagIndexEntry) insertNameSegment(
	nameSeg string,
	referHID string,
) tagIndexEntry {
	if indexEntry.NameSeg == nil {
		indexEntry.NameSeg = make(map[string][]string)
	}
	if _, present := indexEntry.NameSeg[nameSeg]; !present {
		indexEntry.NameSeg[nameSeg] = []string{referHID}
	} else {
		indexEntry.NameSeg[nameSeg] = append(
			indexEntry.NameSeg[nameSeg],
			referHID,
		)
	}
	return indexEntry
}

func (indexEntry tagIndexEntry) insertVersion(
	versionNumber int64,
	nameSeg string,
	instanceHCID objects.HCID,
) tagIndexEntry {
	if indexEntry.Version == nil {
		indexEntry.Version = make(map[string]map[int64]objects.HCID)
	}
	if _, present := indexEntry.Version[nameSeg]; !present {
		indexEntry.Version[nameSeg] = make(map[int64]objects.HCID)
	}
	indexEntry.Version[nameSeg][versionNumber] = instanceHCID
	return indexEntry
}

func indexNameSegment(typeString, targetHex, referingHex, nameSeg string) {
	if nameSegmentIndex == nil {
		nameSegmentIndex = make(map[string]map[nameSegmentIndexEntry]int)
	}

	if _, present := nameSegmentIndex[nameSeg]; !present {
		nameSegmentIndex[nameSeg] = make(map[nameSegmentIndexEntry]int)
	}
	nameSegmentIndex[nameSeg][nameSegmentIndexEntry{typeString, targetHex}] += 1

	switch typeString {
	case "blob", "list":
		if blobIndex == nil {
			blobIndex = make(map[string]blobIndexEntry)
		}
		if _, present := blobIndex[targetHex]; !present {
			blobIndex[targetHex] = blobIndexEntry{}
		}
		blobIndex[targetHex] = blobIndex[targetHex].insertNameSegment(
			nameSeg,
			referingHex,
		)

	case "commit":
		if commitIndex == nil {
			commitIndex = make(map[string]commitIndexEntry)
		}
		if _, present := commitIndex[targetHex]; !present {
			commitIndex[targetHex] = commitIndexEntry{}
		}
		commitIndex[targetHex] = commitIndex[targetHex].insertNameSegment(
			nameSeg,
			referingHex,
		)
	case "tag":
		if tagIndex == nil {
			tagIndex = make(map[string]tagIndexEntry)
		}
		if _, present := tagIndex[targetHex]; !present {
			tagIndex[targetHex] = tagIndexEntry{}
		}
		tagIndex[targetHex] = tagIndex[targetHex].insertNameSegment(
			nameSeg,
			referingHex,
		)
	default:
		log.Printf("Received invalid typestring: %s\n", typeString)
	}
}

var nameSegmentIndex map[string]map[nameSegmentIndexEntry]int

type nameSegmentIndexEntry struct {
	TypeString string
	Hash       string
}

var blobIndex map[string]blobIndexEntry

func indexBlob(inBlob objects.Blob) {
	if blobIndex == nil {
		blobIndex = make(map[string]blobIndexEntry)
	}
	hashHex := inBlob.Hash().Hex()
	if _, present := blobIndex[hashHex]; !present {
		blobIndex[hashHex] = blobIndexEntry{}
	}
	blobIndex[hashHex] = blobIndex[hashHex].insertSize(len(inBlob))
	blobIndex[hashHex] = blobIndex[hashHex].insertType("Blob")
}

func indexList(inList objects.List) {
	indexBlob(inList.Bytes()) //Indexing Lists as blobs because they are also blobs
	hashHex := inList.Hash().Hex()
	blobIndex[hashHex] = blobIndex[hashHex].insertType("List")
	for nameSeg, entry := range inList {
		indexNameSegment(
			entry.TypeString,
			entry.Hash.Hex(),
			inList.Hash().Hex(),
			nameSeg,
		)
	}
}

var commitIndex map[string]commitIndexEntry

func indexCommit(inCommit objects.Commit) {
	indexBlob(inCommit.Bytes())
	hashHex := inCommit.Hash().Hex()
	blobIndex[hashHex] = blobIndex[hashHex].insertType("Commit")
	blobIndex[inCommit.Hkid.Hex()] =
		blobIndex[inCommit.Hkid.Hex()].insertType("Repository Key")
	if commitIndex == nil {
		commitIndex = make(map[string]commitIndexEntry)
	}
	if _, present := commitIndex[inCommit.Hkid.Hex()]; !present {
		commitIndex[inCommit.Hkid.Hex()] = commitIndexEntry{}
	}
	commitIndex[inCommit.Hkid.Hex()] =
		commitIndex[inCommit.Hkid.Hex()].insertVersion(
			inCommit.Version,
			inCommit.Hash(),
		)
	insertDescendantS(inCommit.Parents, inCommit.Hash(), inCommit.Version)
}

var tagIndex map[string]tagIndexEntry

func indexTag(inTag objects.Tag) {
	indexBlob(inTag.Bytes())
	hashHex := inTag.Hash().Hex()
	blobIndex[hashHex] = blobIndex[hashHex].insertType("Tag")
	blobIndex[inTag.Hkid.Hex()] =
		blobIndex[inTag.Hkid.Hex()].insertType("Domain Key")
	indexNameSegment(
		inTag.TypeString,
		inTag.HashBytes.Hex(),
		inTag.Hash().Hex(),
		inTag.NameSegment,
	)

	if tagIndex == nil {
		tagIndex = make(map[string]tagIndexEntry)
	}
	if _, present := tagIndex[inTag.Hkid.Hex()]; !present {
		tagIndex[inTag.Hkid.Hex()] = tagIndexEntry{}
	}
	tagIndex[inTag.Hkid.Hex()] = tagIndex[inTag.Hkid.Hex()].insertVersion(
		inTag.Version,
		inTag.NameSegment,
		inTag.Hash(),
	)
	insertDescendantS(inTag.Parents, inTag.Hash(), inTag.Version)
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2019 the Octant contributors. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package cluster

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"github.com/pkg/errors"
	admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/client-go/discovery"
	"k8s.io/client-go/discovery/cached/disk"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/restmapper"
	"k8s.io/client-go/tools/clientcmd"
	apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"

	internalLog "github.com/vmware-tanzu/octant/internal/log"
	"github.com/vmware-tanzu/octant/internal/util/strings"
	"github.com/vmware-tanzu/octant/pkg/log"

	// auth plugins
	_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
)

//go:generate mockgen -source=cluster.go -destination=./fake/mock_client_interface.go -package=fake github.com/vmware-tanzu/octant/internal/cluster ClientInterface
//go:generate mockgen -source=../../vendor/k8s.io/client-go/informers/generic.go -destination=./fake/mock_genericinformer.go -package=fake k8s.io/client-go/informers GenericInformer
//go:generate mockgen -source=../../vendor/k8s.io/client-go/discovery/discovery_client.go -imports=openapi_v2=github.com/googleapis/gnostic/openapiv2 -destination=./fake/mock_discoveryinterface.go -package=fake k8s.io/client-go/discovery DiscoveryInterface
//go:generate mockgen -source=../../vendor/k8s.io/client-go/kubernetes/clientset.go -destination=./fake/mock_kubernetes_client.go -package=fake -mock_names=Interface=MockKubernetesInterface k8s.io/client-go/kubernetes Interface
//go:generate mockgen -destination=./fake/mock_sharedindexinformer.go -package=fake k8s.io/client-go/tools/cache SharedIndexInformer
//go:generate mockgen -destination=./fake/mock_authorization.go -package=fake k8s.io/client-go/kubernetes/typed/authorization/v1 AuthorizationV1Interface,SelfSubjectAccessReviewInterface,SelfSubjectAccessReviewsGetter,SelfSubjectRulesReviewInterface,SelfSubjectRulesReviewsGetter
//go:generate mockgen -source=../../vendor/k8s.io/client-go/dynamic/interface.go -destination=./fake/mock_dynamic_client.go -package=fake -imports=github.com/vmware-tanzu/octant/vendor/k8s.io/client-go/dynamic=k8s.io/client-go/dynamic -mock_names=Interface=MockDynamicInterface k8s.io/client-go/dynamic Interface

// ClientInterface is a client for cluster operations.
type ClientInterface interface {
	DefaultNamespace() string
	ResourceExists(schema.GroupVersionResource) bool
	Resource(schema.GroupKind) (schema.GroupVersionResource, bool, error)
	ResetMapper()
	KubernetesClient() (kubernetes.Interface, error)
	DynamicClient() (dynamic.Interface, error)
	DiscoveryClient() (discovery.DiscoveryInterface, error)
	NamespaceClient() (NamespaceInterface, error)
	InfoClient() (InfoInterface, error)
	Close()
	RESTInterface
}

type RESTInterface interface {
	RESTClient() (rest.Interface, error)
	RESTConfig() *rest.Config
}

// Cluster is a client for cluster operations
type Cluster struct {
	clientConfig clientcmd.ClientConfig
	restConfig   *rest.Config
	logger       log.Logger

	kubernetesClient kubernetes.Interface
	dynamicClient    dynamic.Interface
	discoveryClient  discovery.DiscoveryInterface

	restMapper *restmapper.DeferredDiscoveryRESTMapper

	closeFn context.CancelFunc

	defaultNamespace   string
	providedNamespaces []string
}

var _ ClientInterface = (*Cluster)(nil)

func newCluster(ctx context.Context, clientConfig clientcmd.ClientConfig, restClient *rest.Config, defaultNamespace string, providedNamespaces []string) (*Cluster, error) {
	logger := internalLog.From(ctx).With("component", "cluster client")

	install.Install(scheme.Scheme)
	admissionregistrationv1beta1.AddToScheme(scheme.Scheme)
	apiregistrationv1.AddToScheme(scheme.Scheme)

	kubernetesClient, err := kubernetes.NewForConfig(restClient)
	if err != nil {
		return nil, errors.Wrap(err, "create kubernetes client")
	}

	dynamicClient, err := dynamic.NewForConfig(restClient)
	if err != nil {
		return nil, errors.Wrap(err, "create dynamic client")
	}

	discoveryClient, err := discovery.NewDiscoveryClientForConfig(restClient)
	if err != nil {
		return nil, errors.Wrap(err, "create discovery client")
	}

	dir, err := ioutil.TempDir("", "octant")
	if err != nil {
		return nil, errors.Wrap(err, "create temp directory")
	}

	logger.With("dir", dir).Debugf("created temp directory")

	cachedDiscoveryClient, err := disk.NewCachedDiscoveryClientForConfig(
		restClient,
		dir,
		dir,
		180*time.Second,
	)
	if err != nil {
		return nil, errors.Wrap(err, "create cached discovery client")
	}

	restMapper := restmapper.NewDeferredDiscoveryRESTMapper(cachedDiscoveryClient)

	c := &Cluster{
		clientConfig:       clientConfig,
		restConfig:         restClient,
		kubernetesClient:   kubernetesClient,
		dynamicClient:      dynamicClient,
		discoveryClient:    discoveryClient,
		restMapper:         restMapper,
		logger:             internalLog.From(ctx),
		defaultNamespace:   defaultNamespace,
		providedNamespaces: providedNamespaces,
	}

	ctx, cancel := context.WithCancel(ctx)
	c.closeFn = cancel

	go func() {
		<-ctx.Done()
		logger.Infof("removing cluster client temporary directory")

		if err := os.RemoveAll(dir); err != nil {
			logger.WithErr(err).Errorf("closing temporary directory")
		}
	}()

	return c, nil
}

func (c *Cluster) Close() {
	if c.closeFn != nil {
		c.closeFn()
	}
}

func (c *Cluster) DefaultNamespace() string {
	return c.defaultNamespace
}

func (c *Cluster) ResourceExists(gvr schema.GroupVersionResource) bool {
	restMapper := c.restMapper
	_, err := restMapper.KindFor(gvr)
	return err == nil
}

func (c *Cluster) Resource(gk schema.GroupKind) (schema.GroupVersionResource, bool, error) {
	restMapping, err := c.restMapper.RESTMapping(gk)
	if err != nil {
		return schema.GroupVersionResource{}, false, err
	}
	return restMapping.Resource, restMapping.Scope.Name() == meta.RESTScopeNameNamespace, nil
}

func (c *Cluster) ResetMapper() {
	c.restMapper.Reset()
}

// KubernetesClient returns a Kubernetes client.
func (c *Cluster) KubernetesClient() (kubernetes.Interface, error) {
	return c.kubernetesClient, nil
}

// NamespaceClient returns a namespace client.
func (c *Cluster) NamespaceClient() (NamespaceInterface, error) {
	rc, err := c.RESTClient()
	if err != nil {
		return nil, err
	}

	dc, err := c.DynamicClient()
	if err != nil {
		return nil, err
	}

	ns, _, err := c.clientConfig.Namespace()
	if err != nil {
		return nil, errors.Wrap(err, "resolving initial namespace")
	}
	return newNamespaceClient(dc, rc, ns, c.providedNamespaces), nil
}

// DynamicClient returns a dynamic client.
func (c *Cluster) DynamicClient() (dynamic.Interface, error) {
	return c.dynamicClient, nil
}

// DiscoveryClient returns a DiscoveryClient for the cluster.
func (c *Cluster) DiscoveryClient() (discovery.DiscoveryInterface, error) {
	return c.discoveryClient, nil
}

// InfoClient returns an InfoClient for the cluster.
func (c *Cluster) InfoClient() (InfoInterface, error) {
	return newClusterInfo(c.clientConfig), nil
}

// RESTClient returns a RESTClient for the cluster.
func (c *Cluster) RESTClient() (rest.Interface, error) {
	return rest.RESTClientFor(c.restConfig)
}

// RESTConfig returns configuration for communicating with the cluster.
func (c *Cluster) RESTConfig() *rest.Config {
	return c.restConfig
}

// Version returns a ServerVersion for the cluster.
func (c *Cluster) Version() (string, error) {
	dc, err := c.DiscoveryClient()
	if err != nil {
		return "", err
	}
	serverVersion, err := dc.ServerVersion()
	if err != nil {
		return "", err
	}
	return fmt.Sprint(serverVersion), nil
}

// FromKubeConfig creates a Cluster from a kubeConfig chain.
func FromKubeConfig(ctx context.Context, kubeConfigList, contextName string, initialNamespace string, providedNamespaces []string, options RESTConfigOptions) (*Cluster, error) {
	chain := strings.Deduplicate(filepath.SplitList(kubeConfigList))
	rules := &clientcmd.ClientConfigLoadingRules{
		Precedence: chain,
	}

	overrides := &clientcmd.ConfigOverrides{}
	if contextName != "" {
		overrides.CurrentContext = contextName
	}
	cc := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
	config, err := cc.ClientConfig()
	if err != nil {
		return nil, err
	}

	var defaultNamespace string

	if initialNamespace == "" {
		defaultNamespace, _, err = cc.Namespace()
		if err != nil {
			return nil, err
		}
	} else {
		defaultNamespace = initialNamespace
	}

	logger := internalLog.From(ctx)
	logger.With("client-qps", options.QPS, "client-burst", options.Burst).
		Debugf("initializing REST client configuration")

	config = withConfigDefaults(config, options)

	return newCluster(ctx, cc, config, defaultNamespace, providedNamespaces)
}

// withConfigDefaults returns an extended rest.Config object with additional defaults applied
// See core_client.go#setConfigDefaults
func withConfigDefaults(inConfig *rest.Config, options RESTConfigOptions) *rest.Config {
	config := rest.CopyConfig(inConfig)
	config.QPS = options.QPS
	config.Burst = options.Burst
	config.APIPath = "/api"
	if config.GroupVersion == nil || config.GroupVersion.Group != scheme.Scheme.PrioritizedVersionsForGroup("")[0].Group {
		gv := scheme.Scheme.PrioritizedVersionsForGroup("")[0]
		config.GroupVersion = &gv
	}
	codec := runtime.NoopEncoder{Decoder: scheme.Codecs.UniversalDecoder()}
	config.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})

	if options.UserAgent != "" {
		config.UserAgent = options.UserAgent
	}

	return config
}

type RESTConfigOptions struct {
	QPS       float32
	Burst     int
	UserAgent string
}
<commit_msg>use cached discovery client<commit_after>/*
Copyright (c) 2019 the Octant contributors. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package cluster

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"github.com/pkg/errors"
	admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/client-go/discovery"
	"k8s.io/client-go/discovery/cached/disk"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/restmapper"
	"k8s.io/client-go/tools/clientcmd"
	apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"

	internalLog "github.com/vmware-tanzu/octant/internal/log"
	"github.com/vmware-tanzu/octant/internal/util/strings"
	"github.com/vmware-tanzu/octant/pkg/log"

	// auth plugins
	_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
)

//go:generate mockgen -source=cluster.go -destination=./fake/mock_client_interface.go -package=fake github.com/vmware-tanzu/octant/internal/cluster ClientInterface
//go:generate mockgen -source=../../vendor/k8s.io/client-go/informers/generic.go -destination=./fake/mock_genericinformer.go -package=fake k8s.io/client-go/informers GenericInformer
//go:generate mockgen -source=../../vendor/k8s.io/client-go/discovery/discovery_client.go -imports=openapi_v2=github.com/googleapis/gnostic/openapiv2 -destination=./fake/mock_discoveryinterface.go -package=fake k8s.io/client-go/discovery DiscoveryInterface
//go:generate mockgen -source=../../vendor/k8s.io/client-go/kubernetes/clientset.go -destination=./fake/mock_kubernetes_client.go -package=fake -mock_names=Interface=MockKubernetesInterface k8s.io/client-go/kubernetes Interface
//go:generate mockgen -destination=./fake/mock_sharedindexinformer.go -package=fake k8s.io/client-go/tools/cache SharedIndexInformer
//go:generate mockgen -destination=./fake/mock_authorization.go -package=fake k8s.io/client-go/kubernetes/typed/authorization/v1 AuthorizationV1Interface,SelfSubjectAccessReviewInterface,SelfSubjectAccessReviewsGetter,SelfSubjectRulesReviewInterface,SelfSubjectRulesReviewsGetter
//go:generate mockgen -source=../../vendor/k8s.io/client-go/dynamic/interface.go -destination=./fake/mock_dynamic_client.go -package=fake -imports=github.com/vmware-tanzu/octant/vendor/k8s.io/client-go/dynamic=k8s.io/client-go/dynamic -mock_names=Interface=MockDynamicInterface k8s.io/client-go/dynamic Interface

// ClientInterface is a client for cluster operations.
type ClientInterface interface {
	DefaultNamespace() string
	ResourceExists(schema.GroupVersionResource) bool
	Resource(schema.GroupKind) (schema.GroupVersionResource, bool, error)
	ResetMapper()
	KubernetesClient() (kubernetes.Interface, error)
	DynamicClient() (dynamic.Interface, error)
	DiscoveryClient() (discovery.DiscoveryInterface, error)
	NamespaceClient() (NamespaceInterface, error)
	InfoClient() (InfoInterface, error)
	Close()
	RESTInterface
}

type RESTInterface interface {
	RESTClient() (rest.Interface, error)
	RESTConfig() *rest.Config
}

// Cluster is a client for cluster operations
type Cluster struct {
	clientConfig clientcmd.ClientConfig
	restConfig   *rest.Config
	logger       log.Logger

	kubernetesClient kubernetes.Interface
	dynamicClient    dynamic.Interface
	discoveryClient  discovery.DiscoveryInterface

	restMapper *restmapper.DeferredDiscoveryRESTMapper

	closeFn context.CancelFunc

	defaultNamespace   string
	providedNamespaces []string
}

var _ ClientInterface = (*Cluster)(nil)

func newCluster(ctx context.Context, clientConfig clientcmd.ClientConfig, restClient *rest.Config, defaultNamespace string, providedNamespaces []string) (*Cluster, error) {
	logger := internalLog.From(ctx).With("component", "cluster client")

	install.Install(scheme.Scheme)
	admissionregistrationv1beta1.AddToScheme(scheme.Scheme)
	apiregistrationv1.AddToScheme(scheme.Scheme)

	kubernetesClient, err := kubernetes.NewForConfig(restClient)
	if err != nil {
		return nil, errors.Wrap(err, "create kubernetes client")
	}

	dynamicClient, err := dynamic.NewForConfig(restClient)
	if err != nil {
		return nil, errors.Wrap(err, "create dynamic client")
	}

	dir, err := ioutil.TempDir("", "octant")
	if err != nil {
		return nil, errors.Wrap(err, "create temp directory")
	}

	logger.With("dir", dir).Debugf("created temp directory")

	cachedDiscoveryClient, err := disk.NewCachedDiscoveryClientForConfig(
		restClient,
		dir,
		dir,
		180*time.Second,
	)
	if err != nil {
		return nil, errors.Wrap(err, "create cached discovery client")
	}

	restMapper := restmapper.NewDeferredDiscoveryRESTMapper(cachedDiscoveryClient)

	c := &Cluster{
		clientConfig:       clientConfig,
		restConfig:         restClient,
		kubernetesClient:   kubernetesClient,
		dynamicClient:      dynamicClient,
		discoveryClient:    cachedDiscoveryClient,
		restMapper:         restMapper,
		logger:             internalLog.From(ctx),
		defaultNamespace:   defaultNamespace,
		providedNamespaces: providedNamespaces,
	}

	ctx, cancel := context.WithCancel(ctx)
	c.closeFn = cancel

	go func() {
		<-ctx.Done()
		logger.Infof("removing cluster client temporary directory")

		if err := os.RemoveAll(dir); err != nil {
			logger.WithErr(err).Errorf("closing temporary directory")
		}
	}()

	return c, nil
}

func (c *Cluster) Close() {
	if c.closeFn != nil {
		c.closeFn()
	}
}

func (c *Cluster) DefaultNamespace() string {
	return c.defaultNamespace
}

func (c *Cluster) ResourceExists(gvr schema.GroupVersionResource) bool {
	restMapper := c.restMapper
	_, err := restMapper.KindFor(gvr)
	return err == nil
}

func (c *Cluster) Resource(gk schema.GroupKind) (schema.GroupVersionResource, bool, error) {
	restMapping, err := c.restMapper.RESTMapping(gk)
	if err != nil {
		return schema.GroupVersionResource{}, false, err
	}
	return restMapping.Resource, restMapping.Scope.Name() == meta.RESTScopeNameNamespace, nil
}

func (c *Cluster) ResetMapper() {
	c.restMapper.Reset()
}

// KubernetesClient returns a Kubernetes client.
func (c *Cluster) KubernetesClient() (kubernetes.Interface, error) {
	return c.kubernetesClient, nil
}

// NamespaceClient returns a namespace client.
func (c *Cluster) NamespaceClient() (NamespaceInterface, error) {
	rc, err := c.RESTClient()
	if err != nil {
		return nil, err
	}

	dc, err := c.DynamicClient()
	if err != nil {
		return nil, err
	}

	ns, _, err := c.clientConfig.Namespace()
	if err != nil {
		return nil, errors.Wrap(err, "resolving initial namespace")
	}
	return newNamespaceClient(dc, rc, ns, c.providedNamespaces), nil
}

// DynamicClient returns a dynamic client.
func (c *Cluster) DynamicClient() (dynamic.Interface, error) {
	return c.dynamicClient, nil
}

// DiscoveryClient returns a DiscoveryClient for the cluster.
func (c *Cluster) DiscoveryClient() (discovery.DiscoveryInterface, error) {
	return c.discoveryClient, nil
}

// InfoClient returns an InfoClient for the cluster.
func (c *Cluster) InfoClient() (InfoInterface, error) {
	return newClusterInfo(c.clientConfig), nil
}

// RESTClient returns a RESTClient for the cluster.
func (c *Cluster) RESTClient() (rest.Interface, error) {
	return rest.RESTClientFor(c.restConfig)
}

// RESTConfig returns configuration for communicating with the cluster.
func (c *Cluster) RESTConfig() *rest.Config {
	return c.restConfig
}

// Version returns a ServerVersion for the cluster.
func (c *Cluster) Version() (string, error) {
	dc, err := c.DiscoveryClient()
	if err != nil {
		return "", err
	}
	serverVersion, err := dc.ServerVersion()
	if err != nil {
		return "", err
	}
	return fmt.Sprint(serverVersion), nil
}

// FromKubeConfig creates a Cluster from a kubeConfig chain.
func FromKubeConfig(ctx context.Context, kubeConfigList, contextName string, initialNamespace string, providedNamespaces []string, options RESTConfigOptions) (*Cluster, error) {
	chain := strings.Deduplicate(filepath.SplitList(kubeConfigList))
	rules := &clientcmd.ClientConfigLoadingRules{
		Precedence: chain,
	}

	overrides := &clientcmd.ConfigOverrides{}
	if contextName != "" {
		overrides.CurrentContext = contextName
	}
	cc := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
	config, err := cc.ClientConfig()
	if err != nil {
		return nil, err
	}

	var defaultNamespace string

	if initialNamespace == "" {
		defaultNamespace, _, err = cc.Namespace()
		if err != nil {
			return nil, err
		}
	} else {
		defaultNamespace = initialNamespace
	}

	logger := internalLog.From(ctx)
	logger.With("client-qps", options.QPS, "client-burst", options.Burst).
		Debugf("initializing REST client configuration")

	config = withConfigDefaults(config, options)

	return newCluster(ctx, cc, config, defaultNamespace, providedNamespaces)
}

// withConfigDefaults returns an extended rest.Config object with additional defaults applied
// See core_client.go#setConfigDefaults
func withConfigDefaults(inConfig *rest.Config, options RESTConfigOptions) *rest.Config {
	config := rest.CopyConfig(inConfig)
	config.QPS = options.QPS
	config.Burst = options.Burst
	config.APIPath = "/api"
	if config.GroupVersion == nil || config.GroupVersion.Group != scheme.Scheme.PrioritizedVersionsForGroup("")[0].Group {
		gv := scheme.Scheme.PrioritizedVersionsForGroup("")[0]
		config.GroupVersion = &gv
	}
	codec := runtime.NoopEncoder{Decoder: scheme.Codecs.UniversalDecoder()}
	config.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})

	if options.UserAgent != "" {
		config.UserAgent = options.UserAgent
	}

	return config
}

type RESTConfigOptions struct {
	QPS       float32
	Burst     int
	UserAgent string
}
<|endoftext|>


<commit_before>package console

import (
	"errors"
	"fmt"
	"sort"
	"sync"
	"time"

	"github.com/cenkalti/rain/internal/jsonutil"
	"github.com/cenkalti/rain/rainrpc"
	"github.com/cenkalti/rain/torrent"
	"github.com/jroimartin/gocui"
)

type Console struct {
	client          *rainrpc.Client
	torrents        []rainrpc.Torrent
	errTorrents     error
	selectedID      uint64
	stats           torrent.Stats
	errStats        error
	m               sync.Mutex
	updateTorrentsC chan struct{}
	updateStatsC    chan struct{}
}

func New(clt *rainrpc.Client) *Console {
	return &Console{
		client:          clt,
		updateTorrentsC: make(chan struct{}),
		updateStatsC:    make(chan struct{}),
	}
}

func (c *Console) Run() error {
	g, err := gocui.NewGui(gocui.OutputNormal)
	if err != nil {
		return err
	}
	defer g.Close()

	g.SetManagerFunc(c.layout)

	g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit)
	g.SetKeybinding("", 'q', gocui.ModNone, quit)
	g.SetKeybinding("torrents", 'j', gocui.ModNone, c.cursorDown)
	g.SetKeybinding("torrents", 'k', gocui.ModNone, c.cursorUp)
	g.SetKeybinding("torrents", 'R', gocui.ModNone, c.removeTorrent)
	g.SetKeybinding("torrents", 's', gocui.ModNone, c.startTorrent)
	g.SetKeybinding("torrents", 'S', gocui.ModNone, c.stopTorrent)

	go c.updateLoop(g)

	err = g.MainLoop()
	if err == gocui.ErrQuit {
		err = nil
	}
	return err
}

func (c *Console) layout(g *gocui.Gui) error {
	err := c.drawTorrents(g)
	if err != nil {
		return err
	}
	err = c.drawStats(g)
	if err != nil {
		return err
	}
	_, err = g.SetCurrentView("torrents")
	return err
}

func (c *Console) drawTorrents(g *gocui.Gui) error {
	c.m.Lock()
	defer c.m.Unlock()

	maxX, maxY := g.Size()
	halfY := maxY / 2
	if v, err := g.SetView("torrents", -1, -1, maxX, halfY); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Highlight = true
		v.SelBgColor = gocui.ColorGreen
		v.SelFgColor = gocui.ColorBlack
		fmt.Fprintln(v, "loading torrents...")
	} else {
		v.Clear()
		if c.errTorrents != nil {
			fmt.Fprintln(v, "error:", c.errTorrents)
			c.selectedID = 0
		} else {
			for _, t := range c.torrents {
				fmt.Fprintf(v, "%5d %s %5d %s\n", t.ID, t.InfoHash, t.Port, t.Name)
			}
			_, cy := v.Cursor()
			_, oy := v.Origin()
			selectedRow := cy + oy
			if selectedRow < len(c.torrents) {
				c.setSelectedID(c.torrents[selectedRow].ID)
			}
		}
	}
	return nil
}

func (c *Console) drawStats(g *gocui.Gui) error {
	c.m.Lock()
	defer c.m.Unlock()

	maxX, maxY := g.Size()
	halfY := maxY / 2
	if v, err := g.SetView("stats", -1, halfY, maxX, maxY); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Wrap = true
		fmt.Fprintln(v, "loading stats...")
	} else {
		v.Clear()
		if c.errStats != nil {
			fmt.Fprintln(v, "error:", c.errStats)
		} else {
			b, err := jsonutil.MarshalCompactPretty(c.stats)
			if err != nil {
				fmt.Fprintln(v, "error:", c.errStats)
			} else {
				fmt.Fprintln(v, string(b))
			}
		}
	}
	return nil
}

func (c *Console) updateLoop(g *gocui.Gui) {
	c.updateTorrents(g)
	c.updateStats(g)

	ticker := time.NewTicker(time.Second)
	for {
		select {
		case <-ticker.C:
			c.updateTorrents(g)
			c.updateStats(g)
		case <-c.updateTorrentsC:
			c.updateTorrents(g)
		case <-c.updateStatsC:
			c.updateStats(g)
		}
	}
}

func (c *Console) updateTorrents(g *gocui.Gui) {
	resp, err := c.client.ListTorrents()

	sort.Slice(resp.Torrents, func(i, j int) bool { return resp.Torrents[i].ID < resp.Torrents[j].ID })

	c.m.Lock()
	c.torrents = resp.Torrents
	c.errTorrents = err
	if len(c.torrents) == 0 {
		c.setSelectedID(0)
	} else if c.selectedID == 0 {
		c.setSelectedID(c.torrents[0].ID)
	}
	c.m.Unlock()

	g.Update(c.drawTorrents)
}

func (c *Console) updateStats(g *gocui.Gui) {
	c.m.Lock()
	selectedID := c.selectedID
	c.m.Unlock()

	if selectedID != 0 {
		resp, err := c.client.GetTorrentStats(selectedID)
		c.m.Lock()
		c.stats = resp.Stats
		c.errStats = err
		c.m.Unlock()
	} else {
		c.m.Lock()
		c.errStats = errors.New("no torrent selected")
		c.m.Unlock()
	}

	g.Update(c.drawStats)
}

func quit(g *gocui.Gui, v *gocui.View) error {
	return gocui.ErrQuit
}

func (c *Console) cursorDown(g *gocui.Gui, v *gocui.View) error {
	c.m.Lock()
	defer c.m.Unlock()

	cx, cy := v.Cursor()
	ox, oy := v.Origin()
	if cy+oy >= len(c.torrents)-1 {
		return nil
	}
	if err := v.SetCursor(cx, cy+1); err != nil {
		if err := v.SetOrigin(ox, oy+1); err != nil {
			return err
		}
	}
	row := cy + oy + 1
	if row >= 0 || row < len(c.torrents) {
		c.setSelectedID(c.torrents[row].ID)
	}
	return nil
}

func (c *Console) cursorUp(g *gocui.Gui, v *gocui.View) error {
	c.m.Lock()
	defer c.m.Unlock()

	cx, cy := v.Cursor()
	ox, oy := v.Origin()
	if cy+oy <= 0 {
		return nil
	}
	if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
		if err := v.SetOrigin(ox, oy-1); err != nil {
			return err
		}
	}
	row := cy + oy - 1
	if row >= 0 || row < len(c.torrents) {
		c.setSelectedID(c.torrents[row].ID)
	}
	return nil
}

func (c *Console) removeTorrent(g *gocui.Gui, v *gocui.View) error {
	c.m.Lock()
	id := c.selectedID
	c.m.Unlock()

	_, err := c.client.RemoveTorrent(id)
	if err != nil {
		return err
	}
	select {
	case c.updateTorrentsC <- struct{}{}:
	default:
	}
	return nil
}

func (c *Console) setSelectedID(id uint64) {
	changed := id != c.selectedID
	c.selectedID = id
	if changed {
		select {
		case c.updateStatsC <- struct{}{}:
		default:
		}
	}
}

func (c *Console) startTorrent(g *gocui.Gui, v *gocui.View) error {
	c.m.Lock()
	id := c.selectedID
	c.m.Unlock()

	_, err := c.client.StartTorrent(id)
	return err
}

func (c *Console) stopTorrent(g *gocui.Gui, v *gocui.View) error {
	c.m.Lock()
	id := c.selectedID
	c.m.Unlock()

	_, err := c.client.StopTorrent(id)
	return err
}
<commit_msg>fix guard for console<commit_after>package console

import (
	"errors"
	"fmt"
	"sort"
	"sync"
	"time"

	"github.com/cenkalti/rain/internal/jsonutil"
	"github.com/cenkalti/rain/rainrpc"
	"github.com/cenkalti/rain/torrent"
	"github.com/jroimartin/gocui"
)

type Console struct {
	client          *rainrpc.Client
	torrents        []rainrpc.Torrent
	errTorrents     error
	selectedID      uint64
	stats           torrent.Stats
	errStats        error
	m               sync.Mutex
	updateTorrentsC chan struct{}
	updateStatsC    chan struct{}
}

func New(clt *rainrpc.Client) *Console {
	return &Console{
		client:          clt,
		updateTorrentsC: make(chan struct{}),
		updateStatsC:    make(chan struct{}),
	}
}

func (c *Console) Run() error {
	g, err := gocui.NewGui(gocui.OutputNormal)
	if err != nil {
		return err
	}
	defer g.Close()

	g.SetManagerFunc(c.layout)

	g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit)
	g.SetKeybinding("", 'q', gocui.ModNone, quit)
	g.SetKeybinding("torrents", 'j', gocui.ModNone, c.cursorDown)
	g.SetKeybinding("torrents", 'k', gocui.ModNone, c.cursorUp)
	g.SetKeybinding("torrents", 'R', gocui.ModNone, c.removeTorrent)
	g.SetKeybinding("torrents", 's', gocui.ModNone, c.startTorrent)
	g.SetKeybinding("torrents", 'S', gocui.ModNone, c.stopTorrent)

	go c.updateLoop(g)

	err = g.MainLoop()
	if err == gocui.ErrQuit {
		err = nil
	}
	return err
}

func (c *Console) layout(g *gocui.Gui) error {
	err := c.drawTorrents(g)
	if err != nil {
		return err
	}
	err = c.drawStats(g)
	if err != nil {
		return err
	}
	_, err = g.SetCurrentView("torrents")
	return err
}

func (c *Console) drawTorrents(g *gocui.Gui) error {
	c.m.Lock()
	defer c.m.Unlock()

	maxX, maxY := g.Size()
	halfY := maxY / 2
	if v, err := g.SetView("torrents", -1, -1, maxX, halfY); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Highlight = true
		v.SelBgColor = gocui.ColorGreen
		v.SelFgColor = gocui.ColorBlack
		fmt.Fprintln(v, "loading torrents...")
	} else {
		v.Clear()
		if c.errTorrents != nil {
			fmt.Fprintln(v, "error:", c.errTorrents)
			c.selectedID = 0
		} else {
			for _, t := range c.torrents {
				fmt.Fprintf(v, "%5d %s %5d %s\n", t.ID, t.InfoHash, t.Port, t.Name)
			}
			_, cy := v.Cursor()
			_, oy := v.Origin()
			selectedRow := cy + oy
			if selectedRow < len(c.torrents) {
				c.setSelectedID(c.torrents[selectedRow].ID)
			}
		}
	}
	return nil
}

func (c *Console) drawStats(g *gocui.Gui) error {
	c.m.Lock()
	defer c.m.Unlock()

	maxX, maxY := g.Size()
	halfY := maxY / 2
	if v, err := g.SetView("stats", -1, halfY, maxX, maxY); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Wrap = true
		fmt.Fprintln(v, "loading stats...")
	} else {
		v.Clear()
		if c.errStats != nil {
			fmt.Fprintln(v, "error:", c.errStats)
		} else {
			b, err := jsonutil.MarshalCompactPretty(c.stats)
			if err != nil {
				fmt.Fprintln(v, "error:", c.errStats)
			} else {
				fmt.Fprintln(v, string(b))
			}
		}
	}
	return nil
}

func (c *Console) updateLoop(g *gocui.Gui) {
	c.updateTorrents(g)
	c.updateStats(g)

	ticker := time.NewTicker(time.Second)
	for {
		select {
		case <-ticker.C:
			c.updateTorrents(g)
			c.updateStats(g)
		case <-c.updateTorrentsC:
			c.updateTorrents(g)
		case <-c.updateStatsC:
			c.updateStats(g)
		}
	}
}

func (c *Console) updateTorrents(g *gocui.Gui) {
	resp, err := c.client.ListTorrents()

	sort.Slice(resp.Torrents, func(i, j int) bool { return resp.Torrents[i].ID < resp.Torrents[j].ID })

	c.m.Lock()
	c.torrents = resp.Torrents
	c.errTorrents = err
	if len(c.torrents) == 0 {
		c.setSelectedID(0)
	} else if c.selectedID == 0 {
		c.setSelectedID(c.torrents[0].ID)
	}
	c.m.Unlock()

	g.Update(c.drawTorrents)
}

func (c *Console) updateStats(g *gocui.Gui) {
	c.m.Lock()
	selectedID := c.selectedID
	c.m.Unlock()

	if selectedID != 0 {
		resp, err := c.client.GetTorrentStats(selectedID)
		c.m.Lock()
		c.stats = resp.Stats
		c.errStats = err
		c.m.Unlock()
	} else {
		c.m.Lock()
		c.errStats = errors.New("no torrent selected")
		c.m.Unlock()
	}

	g.Update(c.drawStats)
}

func quit(g *gocui.Gui, v *gocui.View) error {
	return gocui.ErrQuit
}

func (c *Console) cursorDown(g *gocui.Gui, v *gocui.View) error {
	c.m.Lock()
	defer c.m.Unlock()

	cx, cy := v.Cursor()
	ox, oy := v.Origin()
	if cy+oy >= len(c.torrents)-1 {
		return nil
	}
	if err := v.SetCursor(cx, cy+1); err != nil {
		if err := v.SetOrigin(ox, oy+1); err != nil {
			return err
		}
	}
	row := cy + oy + 1
	if row >= 0 && row < len(c.torrents) {
		c.setSelectedID(c.torrents[row].ID)
	}
	return nil
}

func (c *Console) cursorUp(g *gocui.Gui, v *gocui.View) error {
	c.m.Lock()
	defer c.m.Unlock()

	cx, cy := v.Cursor()
	ox, oy := v.Origin()
	if cy+oy <= 0 {
		return nil
	}
	if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
		if err := v.SetOrigin(ox, oy-1); err != nil {
			return err
		}
	}
	row := cy + oy - 1
	if row >= 0 && row < len(c.torrents) {
		c.setSelectedID(c.torrents[row].ID)
	}
	return nil
}

func (c *Console) removeTorrent(g *gocui.Gui, v *gocui.View) error {
	c.m.Lock()
	id := c.selectedID
	c.m.Unlock()

	_, err := c.client.RemoveTorrent(id)
	if err != nil {
		return err
	}
	select {
	case c.updateTorrentsC <- struct{}{}:
	default:
	}
	return nil
}

func (c *Console) setSelectedID(id uint64) {
	changed := id != c.selectedID
	c.selectedID = id
	if changed {
		select {
		case c.updateStatsC <- struct{}{}:
		default:
		}
	}
}

func (c *Console) startTorrent(g *gocui.Gui, v *gocui.View) error {
	c.m.Lock()
	id := c.selectedID
	c.m.Unlock()

	_, err := c.client.StartTorrent(id)
	return err
}

func (c *Console) stopTorrent(g *gocui.Gui, v *gocui.View) error {
	c.m.Lock()
	id := c.selectedID
	c.m.Unlock()

	_, err := c.client.StopTorrent(id)
	return err
}
<|endoftext|>


<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 save

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/comeback/internal/graph"
)

const fileChunkSize = 1 << 24

// For each incoming node, use the supplied blob store to ensure that the node
// has a non-nil list of scores. Incoming nodes must be in reverse
// topologically sorted order: children must appear before parents.
func fillInScores(
	ctx context.Context,
	basePath string,
	blobStore blob.Store,
	nodeIn <-chan *fsNode,
	nodesOut chan<- *fsNode) (err error) {
	err = errors.New("TODO")
	return
}

// Create a graph.Visitor for *fsNode that saves to the supplied blob store,
// filling in the node's Scores field when it is nil. All visited nodes are
// then written to nodesOut.
func newVisitor(
	chunkSize int,
	basePath string,
	blobStore blob.Store,
	nodesOut chan<- *fsNode) (v graph.Visitor) {
	v = &visitor{
		chunkSize: chunkSize,
		basePath:  basePath,
		blobStore: blobStore,
		nodesOut:  nodesOut,
	}

	return
}

type visitor struct {
	chunkSize int
	basePath  string
	blobStore blob.Store
	nodesOut  chan<- *fsNode
}

func (v *visitor) Visit(ctx context.Context, untyped graph.Node) (err error) {
	// Check the type of the node.
	n, ok := untyped.(*fsNode)
	if !ok {
		err = fmt.Errorf("Unexpected node type: %T", untyped)
		return
	}

	// Ensure that the node has scores set, if it needs to.
	err = v.setScores(ctx, n)
	if err != nil {
		err = fmt.Errorf("setScores: %v", err)
		return
	}

	// Pass on the node.
	select {
	case v.nodesOut <- n:
	case <-ctx.Done():
		err = ctx.Err()
		return
	}

	return
}

func (v *visitor) setScores(
	ctx context.Context,
	n *fsNode) (err error) {
	// If the node already has scores, we're done.
	if n.Scores != nil {
		return
	}

	// Files and directories are the only interesting cases.
	mode := n.Info.Mode()
	switch {
	case mode == 0:
		n.Scores, err = v.saveFile(ctx, path.Join(v.basePath, n.RelPath))
		if err != nil {
			err = fmt.Errorf("saveFile: %v", err)
			return
		}

	case mode&os.ModeDir != 0:
		n.Scores, err = v.saveDir(ctx, path.Join(v.basePath, n.RelPath))
		if err != nil {
			err = fmt.Errorf("saveDir: %v", err)
			return
		}
	}

	return
}

// Guarantees non-nil result when successful, even for empty list of scores.
func (v *visitor) saveFile(
	ctx context.Context,
	path string) (scores []blob.Score, err error) {
	scores = make([]blob.Score, 0, 1)

	// Open the file for reading.
	f, err := os.Open(path)
	if err != nil {
		err = fmt.Errorf("Open: %v", err)
		return
	}

	defer f.Close()

	// Process a chunk at a time.
	buf := make([]byte, v.chunkSize)
	for {
		// Read some data.
		var n int
		n, err = f.Read(buf)

		switch {
		case err == io.EOF:
			// Ignore EOF.
			err = nil

		case err != nil:
			err = fmt.Errorf("Read: %v", err)
			return
		}

		// Are we done?
		if n == 0 {
			break
		}

		// Write out the blob.
		var s blob.Score
		s, err = v.blobStore.Store(ctx, buf[:n])
		if err != nil {
			err = fmt.Errorf("Store: %v", err)
			return
		}

		scores = append(scores, s)
	}

	return
}

func (v *visitor) saveDir(
	ctx context.Context,
	path string) (scores []blob.Score, err error) {
	err = errors.New("TODO")
	return
}
<commit_msg>Fixed a bug.<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 save

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/comeback/internal/graph"
)

const fileChunkSize = 1 << 24

// For each incoming node, use the supplied blob store to ensure that the node
// has a non-nil list of scores. Incoming nodes must be in reverse
// topologically sorted order: children must appear before parents.
func fillInScores(
	ctx context.Context,
	basePath string,
	blobStore blob.Store,
	nodeIn <-chan *fsNode,
	nodesOut chan<- *fsNode) (err error) {
	err = errors.New("TODO")
	return
}

// Create a graph.Visitor for *fsNode that saves to the supplied blob store,
// filling in the node's Scores field when it is nil. All visited nodes are
// then written to nodesOut.
func newVisitor(
	chunkSize int,
	basePath string,
	blobStore blob.Store,
	nodesOut chan<- *fsNode) (v graph.Visitor) {
	v = &visitor{
		chunkSize: chunkSize,
		basePath:  basePath,
		blobStore: blobStore,
		nodesOut:  nodesOut,
	}

	return
}

type visitor struct {
	chunkSize int
	basePath  string
	blobStore blob.Store
	nodesOut  chan<- *fsNode
}

func (v *visitor) Visit(ctx context.Context, untyped graph.Node) (err error) {
	// Check the type of the node.
	n, ok := untyped.(*fsNode)
	if !ok {
		err = fmt.Errorf("Unexpected node type: %T", untyped)
		return
	}

	// Ensure that the node has scores set, if it needs to.
	err = v.setScores(ctx, n)
	if err != nil {
		err = fmt.Errorf("setScores: %v", err)
		return
	}

	// Pass on the node.
	select {
	case v.nodesOut <- n:
	case <-ctx.Done():
		err = ctx.Err()
		return
	}

	return
}

func (v *visitor) setScores(
	ctx context.Context,
	n *fsNode) (err error) {
	// If the node already has scores, we're done.
	if n.Scores != nil {
		return
	}

	// Files and directories are the only interesting cases.
	modeType := n.Info.Mode() & os.ModeType
	switch {
	case modeType == 0:
		n.Scores, err = v.saveFile(ctx, path.Join(v.basePath, n.RelPath))
		if err != nil {
			err = fmt.Errorf("saveFile: %v", err)
			return
		}

	case modeType&os.ModeDir != 0:
		n.Scores, err = v.saveDir(ctx, path.Join(v.basePath, n.RelPath))
		if err != nil {
			err = fmt.Errorf("saveDir: %v", err)
			return
		}
	}

	return
}

// Guarantees non-nil result when successful, even for empty list of scores.
func (v *visitor) saveFile(
	ctx context.Context,
	path string) (scores []blob.Score, err error) {
	scores = make([]blob.Score, 0, 1)

	// Open the file for reading.
	f, err := os.Open(path)
	if err != nil {
		err = fmt.Errorf("Open: %v", err)
		return
	}

	defer f.Close()

	// Process a chunk at a time.
	buf := make([]byte, v.chunkSize)
	for {
		// Read some data.
		var n int
		n, err = f.Read(buf)

		switch {
		case err == io.EOF:
			// Ignore EOF.
			err = nil

		case err != nil:
			err = fmt.Errorf("Read: %v", err)
			return
		}

		// Are we done?
		if n == 0 {
			break
		}

		// Write out the blob.
		var s blob.Score
		s, err = v.blobStore.Store(ctx, buf[:n])
		if err != nil {
			err = fmt.Errorf("Store: %v", err)
			return
		}

		scores = append(scores, s)
	}

	return
}

func (v *visitor) saveDir(
	ctx context.Context,
	path string) (scores []blob.Score, err error) {
	err = errors.New("TODO")
	return
}
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package testutil

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"

	"v.io/x/devtools/internal/tool"
	"v.io/x/devtools/internal/util"
)

var (
	// cleanGo is used to control whether the initTest function removes
	// all stale Go object files and binaries. It is use to prevent the
	// test of this package from interfering with other concurrently
	// running tests that might be sharing the same object files.
	cleanGo = true

	// Regexp to match javascript result files.
	reJSResult = regexp.MustCompile(`.*_(integration|spec)\.out$`)

	// Regexp to match common test result files.
	reTestResult = regexp.MustCompile(`^((tests_.*\.xml)|(status_.*\.json))$`)
)

// internalTestError represents an internal test error.
type internalTestError struct {
	err  error
	name string
}

func (e internalTestError) Error() string {
	return fmt.Sprintf("%s:\n%s\n", e.name, e.err.Error())
}

var testTmpDir = ""

// binDirPath returns the path to the directory for storing temporary
// binaries.
func binDirPath() string {
	if len(testTmpDir) == 0 {
		panic("binDirPath shouldn't be called before initTest")
	}
	return filepath.Join(testTmpDir, "bin")
}

// initTest carries out the initial actions for the given test.
func initTest(ctx *tool.Context, testName string, profiles []string) (func() error, error) {
	// Output the hostname.
	hostname, err := os.Hostname()
	if err != nil {
		return nil, fmt.Errorf("Hostname() failed: %v", err)
	}
	fmt.Fprintf(ctx.Stdout(), "hostname = %q\n", hostname)

	// Create a working test directory under $HOME/tmp and set the
	// TMPDIR environment variable to it.
	rootDir := filepath.Join(os.Getenv("HOME"), "tmp", testName)
	if err := ctx.Run().MkdirAll(rootDir, os.FileMode(0755)); err != nil {
		return nil, err
	}
	workDir, err := ctx.Run().TempDir(rootDir, "")
	if err != nil {
		return nil, fmt.Errorf("TempDir() failed: %v", err)
	}
	if err := os.Setenv("TMPDIR", workDir); err != nil {
		return nil, err
	}
	testTmpDir = workDir
	fmt.Fprintf(ctx.Stdout(), "workdir = %q\n", workDir)
	fmt.Fprintf(ctx.Stdout(), "bin dir = %q\n", binDirPath())

	// Create a temporary directory for storing binaries.
	if err := ctx.Run().MkdirAll(binDirPath(), os.FileMode(0755)); err != nil {
		return nil, err
	}

	// Setup profiles.
	for _, profile := range profiles {
		if err := ctx.Run().Command("v23", "profile", "setup", profile); err != nil {
			return nil, err
		}
	}

	// Descend into the working directory (unless doing a "dry
	// run" in which case the working directory does not exist).
	cwd, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	if !ctx.DryRun() {
		if err := ctx.Run().Chdir(workDir); err != nil {
			return nil, err
		}
	}

	// Remove all stale Go object files and binaries.
	if cleanGo {
		if err := ctx.Run().Command("v23", "goext", "distclean"); err != nil {
			return nil, err
		}
	}

	// Cleanup the test results possibly left behind by the
	// previous test.
	testResultFiles, err := findTestResultFiles(ctx, testName)
	if err != nil {
		return nil, err
	}
	for _, file := range testResultFiles {
		if err := ctx.Run().RemoveAll(file); err != nil {
			return nil, err
		}
	}

	return func() error {
		return ctx.Run().Chdir(cwd)
	}, nil
}

// findTestResultFiles returns a slice of paths to test result related files.
func findTestResultFiles(ctx *tool.Context, testName string) ([]string, error) {
	result := []string{}
	root, err := util.VanadiumRoot()
	if err != nil {
		return nil, err
	}

	// Collect javascript test results.
	jsDir := filepath.Join(root, "release", "javascript", "core", "test_out")
	if _, err := os.Stat(jsDir); err == nil {
		fileInfoList, err := ioutil.ReadDir(jsDir)
		if err != nil {
			return nil, fmt.Errorf("ReadDir(%v) failed: %v", jsDir, err)
		}
		for _, fileInfo := range fileInfoList {
			name := fileInfo.Name()
			if reJSResult.MatchString(name) {
				result = append(result, filepath.Join(jsDir, name))
			}
		}
	} else {
		if !os.IsNotExist(err) {
			return nil, err
		}
	}

	// Collect xUnit xml files and test status json files.
	workspaceDir := os.Getenv("WORKSPACE")
	if workspaceDir == "" {
		workspaceDir = filepath.Join(os.Getenv("HOME"), "tmp", testName)
	}
	fileInfoList, err := ioutil.ReadDir(workspaceDir)
	if err != nil {
		return nil, fmt.Errorf("ReadDir(%v) failed: %v", workspaceDir, err)
	}
	for _, fileInfo := range fileInfoList {
		fileName := fileInfo.Name()
		if reTestResult.MatchString(fileName) {
			result = append(result, filepath.Join(workspaceDir, fileName))
		}
	}
	return result, nil
}

func Pass(ctx *tool.Context, format string, a ...interface{}) {
	strOK := "ok"
	if ctx.Color() {
		strOK = util.ColorString("ok", util.Green)
	}
	fmt.Fprintf(ctx.Stdout(), "%s   ", strOK)
	fmt.Fprintf(ctx.Stdout(), format, a...)
}

func Fail(ctx *tool.Context, format string, a ...interface{}) {
	strFail := "fail"
	if ctx.Color() {
		strFail = util.ColorString("fail", util.Red)
	}
	fmt.Fprintf(ctx.Stderr(), "%s ", strFail)
	fmt.Fprintf(ctx.Stderr(), format, a...)
}
<commit_msg>vmon: show timeout message with yellow color when checks are timing out.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package testutil

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"

	"v.io/x/devtools/internal/tool"
	"v.io/x/devtools/internal/util"
)

var (
	// cleanGo is used to control whether the initTest function removes
	// all stale Go object files and binaries. It is use to prevent the
	// test of this package from interfering with other concurrently
	// running tests that might be sharing the same object files.
	cleanGo = true

	// Regexp to match javascript result files.
	reJSResult = regexp.MustCompile(`.*_(integration|spec)\.out$`)

	// Regexp to match common test result files.
	reTestResult = regexp.MustCompile(`^((tests_.*\.xml)|(status_.*\.json))$`)
)

// internalTestError represents an internal test error.
type internalTestError struct {
	err  error
	name string
}

func (e internalTestError) Error() string {
	return fmt.Sprintf("%s:\n%s\n", e.name, e.err.Error())
}

var testTmpDir = ""

// binDirPath returns the path to the directory for storing temporary
// binaries.
func binDirPath() string {
	if len(testTmpDir) == 0 {
		panic("binDirPath shouldn't be called before initTest")
	}
	return filepath.Join(testTmpDir, "bin")
}

// initTest carries out the initial actions for the given test.
func initTest(ctx *tool.Context, testName string, profiles []string) (func() error, error) {
	// Output the hostname.
	hostname, err := os.Hostname()
	if err != nil {
		return nil, fmt.Errorf("Hostname() failed: %v", err)
	}
	fmt.Fprintf(ctx.Stdout(), "hostname = %q\n", hostname)

	// Create a working test directory under $HOME/tmp and set the
	// TMPDIR environment variable to it.
	rootDir := filepath.Join(os.Getenv("HOME"), "tmp", testName)
	if err := ctx.Run().MkdirAll(rootDir, os.FileMode(0755)); err != nil {
		return nil, err
	}
	workDir, err := ctx.Run().TempDir(rootDir, "")
	if err != nil {
		return nil, fmt.Errorf("TempDir() failed: %v", err)
	}
	if err := os.Setenv("TMPDIR", workDir); err != nil {
		return nil, err
	}
	testTmpDir = workDir
	fmt.Fprintf(ctx.Stdout(), "workdir = %q\n", workDir)
	fmt.Fprintf(ctx.Stdout(), "bin dir = %q\n", binDirPath())

	// Create a temporary directory for storing binaries.
	if err := ctx.Run().MkdirAll(binDirPath(), os.FileMode(0755)); err != nil {
		return nil, err
	}

	// Setup profiles.
	for _, profile := range profiles {
		if err := ctx.Run().Command("v23", "profile", "setup", profile); err != nil {
			return nil, err
		}
	}

	// Descend into the working directory (unless doing a "dry
	// run" in which case the working directory does not exist).
	cwd, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	if !ctx.DryRun() {
		if err := ctx.Run().Chdir(workDir); err != nil {
			return nil, err
		}
	}

	// Remove all stale Go object files and binaries.
	if cleanGo {
		if err := ctx.Run().Command("v23", "goext", "distclean"); err != nil {
			return nil, err
		}
	}

	// Cleanup the test results possibly left behind by the
	// previous test.
	testResultFiles, err := findTestResultFiles(ctx, testName)
	if err != nil {
		return nil, err
	}
	for _, file := range testResultFiles {
		if err := ctx.Run().RemoveAll(file); err != nil {
			return nil, err
		}
	}

	return func() error {
		return ctx.Run().Chdir(cwd)
	}, nil
}

// findTestResultFiles returns a slice of paths to test result related files.
func findTestResultFiles(ctx *tool.Context, testName string) ([]string, error) {
	result := []string{}
	root, err := util.VanadiumRoot()
	if err != nil {
		return nil, err
	}

	// Collect javascript test results.
	jsDir := filepath.Join(root, "release", "javascript", "core", "test_out")
	if _, err := os.Stat(jsDir); err == nil {
		fileInfoList, err := ioutil.ReadDir(jsDir)
		if err != nil {
			return nil, fmt.Errorf("ReadDir(%v) failed: %v", jsDir, err)
		}
		for _, fileInfo := range fileInfoList {
			name := fileInfo.Name()
			if reJSResult.MatchString(name) {
				result = append(result, filepath.Join(jsDir, name))
			}
		}
	} else {
		if !os.IsNotExist(err) {
			return nil, err
		}
	}

	// Collect xUnit xml files and test status json files.
	workspaceDir := os.Getenv("WORKSPACE")
	if workspaceDir == "" {
		workspaceDir = filepath.Join(os.Getenv("HOME"), "tmp", testName)
	}
	fileInfoList, err := ioutil.ReadDir(workspaceDir)
	if err != nil {
		return nil, fmt.Errorf("ReadDir(%v) failed: %v", workspaceDir, err)
	}
	for _, fileInfo := range fileInfoList {
		fileName := fileInfo.Name()
		if reTestResult.MatchString(fileName) {
			result = append(result, filepath.Join(workspaceDir, fileName))
		}
	}
	return result, nil
}

func Pass(ctx *tool.Context, format string, a ...interface{}) {
	strOK := "ok"
	if ctx.Color() {
		strOK = util.ColorString("ok", util.Green)
	}
	fmt.Fprintf(ctx.Stdout(), "%s   ", strOK)
	fmt.Fprintf(ctx.Stdout(), format, a...)
}

func Fail(ctx *tool.Context, format string, a ...interface{}) {
	strFail := "fail"
	if ctx.Color() {
		strFail = util.ColorString("fail", util.Red)
	}
	fmt.Fprintf(ctx.Stderr(), "%s ", strFail)
	fmt.Fprintf(ctx.Stderr(), format, a...)
}

func Warn(ctx *tool.Context, format string, a ...interface{}) {
	strWarn := "warn"
	if ctx.Color() {
		strWarn = util.ColorString("warn", util.Yellow)
	}
	fmt.Fprintf(ctx.Stderr(), "%s ", strWarn)
	fmt.Fprintf(ctx.Stderr(), format, a...)
}
<|endoftext|>


<commit_before>// Package version provides current version information.
package version

// Version describes module version
const Version = "1.8.0"
<commit_msg>bump up versoin to release: 1.8.1<commit_after>// Package version provides current version information.
package version

// Version describes module version
const Version = "1.8.1"
<|endoftext|>


<commit_before>// Package watcher is used for watching files and directories
// for automatic recompilation and restart of app on change
// when in development mode.
package watcher

import (
	"os"
	"path/filepath"
	"sync"

	"github.com/colegion/goal/utils/log"

	"gopkg.in/fsnotify.v1"
)

// Type is a watcher type that allows registering new
// pattern - actions pairs.
type Type struct {
	mu    sync.Mutex
	files map[string]bool
}

// NewType allocates and returns a new instance of watcher Type.
func NewType() *Type {
	return &Type{
		files: map[string]bool{},
	}
}

// Listen gets a pattern and a function. The function will be executed
// when files matching the pattern will be modified.
func (t *Type) Listen(pattern string, fn func()) *fsnotify.Watcher {
	// Create a new watcher.
	w, err := fsnotify.NewWatcher()
	log.AssertNil(err)

	// Find directories matching the pattern.
	ds := glob(pattern)

	// Add the files to the watcher.
	for i := range ds {
		log.Trace.Printf(`Adding "%s" to the list of watched directories...`, ds[i])
		err := w.Add(ds[i])
		if err != nil {
			log.Warn.Println(err)
		}
	}

	// Start watching process.
	go t.NotifyOnUpdate(filepath.ToSlash(pattern), w, fn)
	return w
}

// ListenFile is equivalent of Listen but for files.
// If file is added using ListenFile and the same file
// is withing a pattern of Listen, only the first one
// will trigger restarts.
// I.e. we have the following calls:
//	w.Listen("./", fn1)
//	w.ListenFile("./goal.yml", fn2)
// If "goal.yml" file is modified fn2 will be triggered.
// fn1 may be triggered by changes in any file inside
// "./" directory except "goal.yml".
func (t *Type) ListenFile(path string, fn func()) *fsnotify.Watcher {
	// Create a new watcher.
	w, err := fsnotify.NewWatcher()
	log.AssertNil(err)

	// Watch a directory instead of file.
	// See issue #17 of fsnotify to find out more
	// why we do this.
	dir := filepath.Dir(path)
	w.Add(dir)

	// Clean path and replace back slashes
	// to the normal ones.
	path = filepath.ToSlash(path)

	// Start watching process.
	t.files[path] = true
	go t.NotifyOnUpdate(path, w, fn)
	return w
}

// NotifyOnUpdate starts the function every time a file change
// event is received. Start it as a goroutine.
func (t *Type) NotifyOnUpdate(pattern string, watcher *fsnotify.Watcher, fn func()) {
	for {
		select {
		case ev := <-watcher.Events:
			// Convert path to the Linux format.
			name := filepath.ToSlash(ev.Name)

			// Make sure this is the exact event type that
			// requires a restart.
			if !restartRequired(ev) {
				continue
			}

			// If this is a directory watcher, but a file that was registered
			// with ListenFile has been modified,
			// ignore this event.
			if !t.files[pattern] && t.files[name] {
				continue
			}

			// If this is a single file watcher, make sure this is
			// exactly the file that should be watched, not
			// some other.
			if t.files[pattern] && name != pattern {
				continue
			}

			// Trigger the registered functions.
			t.mu.Lock()
			fn()
			t.mu.Unlock()
		case <-watcher.Errors:
			return
		}
	}
}

// restartRequired checks whether event indicates a file
// has been modified. If so, it returns true.
func restartRequired(event fsnotify.Event) bool {
	// Do not restart if "./bin" directory is modified.
	// TODO: make this configurable.
	d := filepath.FromSlash(event.Name)
	if d == "./bin" || d == "bin" {
		return false
	}

	if event.Op&fsnotify.Chmod == fsnotify.Chmod {
		return false
	}
	return true
}

// glob returns names of all directories matching pattern or nil.
// The only supported special character is an asterisk at the end.
// It means that the directory is expected to be scanned recursively.
// There is no way for fsnotify to watch individual files (see #17),
// so we support only directories.
// File system errors such as I/O reading are ignored.
func glob(pattern string) (ds []string) {
	// Make sure pattern is not empty.
	l := len(pattern)
	if l == 0 {
		return
	}

	// Check whether we should scan the directory recursively.
	recurs := pattern[l-1] == '*'
	if recurs {
		// Trim the asterisk at the end.
		pattern = pattern[:l-1]
	}

	// Make sure such path exists and it is a directory rather than a file.
	info, err := os.Stat(pattern)
	if err != nil {
		return
	}
	if !info.IsDir() {
		log.Warn.Printf(`"%s" is not a directory, skipping it.`, pattern)
		return
	}

	// If not recursive scan was expected, return the path as is.
	if !recurs {
		ds = append(ds, pattern)
		return // Return as is.
	}

	// Start searching directories recursively.
	filepath.Walk(pattern, func(path string, info os.FileInfo, err error) error {
		// Make sure there are no any errors.
		if err != nil {
			return err
		}

		// Make sure the path represents a directory.
		if !info.IsDir() {
			return nil
		}

		// Add the directory path to the list.
		ds = append(ds, path)
		return nil
	})
	return
}
<commit_msg>Added trace message to the restartRequeired func<commit_after>// Package watcher is used for watching files and directories
// for automatic recompilation and restart of app on change
// when in development mode.
package watcher

import (
	"os"
	"path/filepath"
	"sync"

	"github.com/colegion/goal/utils/log"

	"gopkg.in/fsnotify.v1"
)

// Type is a watcher type that allows registering new
// pattern - actions pairs.
type Type struct {
	mu    sync.Mutex
	files map[string]bool
}

// NewType allocates and returns a new instance of watcher Type.
func NewType() *Type {
	return &Type{
		files: map[string]bool{},
	}
}

// Listen gets a pattern and a function. The function will be executed
// when files matching the pattern will be modified.
func (t *Type) Listen(pattern string, fn func()) *fsnotify.Watcher {
	// Create a new watcher.
	w, err := fsnotify.NewWatcher()
	log.AssertNil(err)

	// Find directories matching the pattern.
	ds := glob(pattern)

	// Add the files to the watcher.
	for i := range ds {
		log.Trace.Printf(`Adding "%s" to the list of watched directories...`, ds[i])
		err := w.Add(ds[i])
		if err != nil {
			log.Warn.Println(err)
		}
	}

	// Start watching process.
	go t.NotifyOnUpdate(filepath.ToSlash(pattern), w, fn)
	return w
}

// ListenFile is equivalent of Listen but for files.
// If file is added using ListenFile and the same file
// is withing a pattern of Listen, only the first one
// will trigger restarts.
// I.e. we have the following calls:
//	w.Listen("./", fn1)
//	w.ListenFile("./goal.yml", fn2)
// If "goal.yml" file is modified fn2 will be triggered.
// fn1 may be triggered by changes in any file inside
// "./" directory except "goal.yml".
func (t *Type) ListenFile(path string, fn func()) *fsnotify.Watcher {
	// Create a new watcher.
	w, err := fsnotify.NewWatcher()
	log.AssertNil(err)

	// Watch a directory instead of file.
	// See issue #17 of fsnotify to find out more
	// why we do this.
	dir := filepath.Dir(path)
	w.Add(dir)

	// Clean path and replace back slashes
	// to the normal ones.
	path = filepath.ToSlash(path)

	// Start watching process.
	t.files[path] = true
	go t.NotifyOnUpdate(path, w, fn)
	return w
}

// NotifyOnUpdate starts the function every time a file change
// event is received. Start it as a goroutine.
func (t *Type) NotifyOnUpdate(pattern string, watcher *fsnotify.Watcher, fn func()) {
	for {
		select {
		case ev := <-watcher.Events:
			// Convert path to the Linux format.
			name := filepath.ToSlash(ev.Name)

			// Make sure this is the exact event type that
			// requires a restart.
			if !restartRequired(ev) {
				continue
			}

			// If this is a directory watcher, but a file that was registered
			// with ListenFile has been modified,
			// ignore this event.
			if !t.files[pattern] && t.files[name] {
				continue
			}

			// If this is a single file watcher, make sure this is
			// exactly the file that should be watched, not
			// some other.
			if t.files[pattern] && name != pattern {
				continue
			}

			// Trigger the registered functions.
			t.mu.Lock()
			fn()
			t.mu.Unlock()
		case <-watcher.Errors:
			return
		}
	}
}

// restartRequired checks whether event indicates a file
// has been modified. If so, it returns true.
func restartRequired(event fsnotify.Event) bool {
	// Do not restart if "./bin" directory is modified.
	// TODO: make this configurable.
	d := filepath.ToSlash(event.Name)
	if d == "./bin" || d == "bin" {
		return false
	}

	if event.Op&fsnotify.Chmod == fsnotify.Chmod {
		return false
	}

	log.Trace.Printf(`FS object "%s" has been modified, restarting...`, event.Name)
	return true
}

// glob returns names of all directories matching pattern or nil.
// The only supported special character is an asterisk at the end.
// It means that the directory is expected to be scanned recursively.
// There is no way for fsnotify to watch individual files (see #17),
// so we support only directories.
// File system errors such as I/O reading are ignored.
func glob(pattern string) (ds []string) {
	// Make sure pattern is not empty.
	l := len(pattern)
	if l == 0 {
		return
	}

	// Check whether we should scan the directory recursively.
	recurs := pattern[l-1] == '*'
	if recurs {
		// Trim the asterisk at the end.
		pattern = pattern[:l-1]
	}

	// Make sure such path exists and it is a directory rather than a file.
	info, err := os.Stat(pattern)
	if err != nil {
		return
	}
	if !info.IsDir() {
		log.Warn.Printf(`"%s" is not a directory, skipping it.`, pattern)
		return
	}

	// If not recursive scan was expected, return the path as is.
	if !recurs {
		ds = append(ds, pattern)
		return // Return as is.
	}

	// Start searching directories recursively.
	filepath.Walk(pattern, func(path string, info os.FileInfo, err error) error {
		// Make sure there are no any errors.
		if err != nil {
			return err
		}

		// Make sure the path represents a directory.
		if !info.IsDir() {
			return nil
		}

		// Add the directory path to the list.
		ds = append(ds, path)
		return nil
	})
	return
}
<|endoftext|>


<commit_before>package aero

import (
	"bufio"
	"bytes"
	"encoding/json"
	"io/ioutil"
	"mime"
	"net/http"
	"path/filepath"
	"strconv"
	"time"

	"github.com/OneOfOne/xxhash"
	"github.com/julienschmidt/httprouter"
	cache "github.com/patrickmn/go-cache"
	"github.com/tomasen/realip"
	"github.com/valyala/fasthttp"
)

// This should be close to the MTU size of a TCP packet.
// Regarding performance it makes no sense to compress smaller files.
// Bandwidth can be saved however the savings are minimal for small files
// and the overhead of compressing can lead up to a 75% reduction
// in server speed under high load. Therefore in this case
// we're trying to optimize for performance, not bandwidth.
const gzipThreshold = 1450

const (
	serverHeader                  = "Server"
	server                        = "Aero"
	cacheControlHeader            = "Cache-Control"
	cacheControlAlwaysValidate    = "must-revalidate"
	contentTypeOptionsHeader      = "X-Content-Type-Options"
	contentTypeOptions            = "nosniff"
	xssProtectionHeader           = "X-XSS-Protection"
	xssProtection                 = "1; mode=block"
	etagHeader                    = "ETag"
	contentTypeHeader             = "Content-Type"
	contentTypeHTML               = "text/html; charset=utf-8"
	contentTypeJSON               = "application/json"
	contentTypePlainText          = "text/plain; charset=utf-8"
	contentEncodingHeader         = "Content-Encoding"
	contentEncodingGzip           = "gzip"
	responseTimeHeader            = "X-Response-Time"
	ifNoneMatchHeader             = "If-None-Match"
	xFrameOptionsHeader           = "X-Frame-Options"
	xFrameOptions                 = "SAMEORIGIN"
	referrerPolicyHeader          = "Referrer-Policy"
	referrerPolicySameOrigin      = "no-referrer"
	strictTransportSecurityHeader = "Strict-Transport-Security"
	strictTransportSecurity       = "max-age=31536000; includeSubDomains; preload"
	contentSecurityPolicyHeader   = "Content-Security-Policy"
)

// Context ...
type Context struct {
	// net/http
	request  *http.Request
	response http.ResponseWriter
	params   httprouter.Params

	// A pointer to the application this request occured on.
	App *Application

	// Status code
	StatusCode int

	// Start time
	start time.Time

	// User session
	session *Session
}

// Handle ...
type Handle func(*Context) string

// Session returns the session of the context or creates and caches a new session.
func (ctx *Context) Session() *Session {
	// Return cached session if available.
	if ctx.session != nil {
		return ctx.session
	}

	// Check if the client has a session cookie already.
	// sid := ctx.requestCtx.Request.Header.CookieBytes(sidBytes)
	cookie, err := ctx.request.Cookie("sid")

	if err == nil {
		sid := cookie.Value

		if sid != "" {
			ctx.session = ctx.App.Sessions.Store.Get(sid)

			if ctx.session != nil {
				return ctx.session
			}
		}
	}

	// Create a new session
	ctx.session = ctx.App.Sessions.New()

	sessionCookie := http.Cookie{
		Name:     "sid",
		Value:    ctx.session.id,
		HttpOnly: true,
		Secure:   true,
	}

	http.SetCookie(ctx.response, &sessionCookie)

	return ctx.session
}

// JSON encodes the object to a JSON string and responds.
func (ctx *Context) JSON(value interface{}) string {
	bytes, _ := json.Marshal(value)

	ctx.SetHeader(contentTypeHeader, contentTypeJSON)
	return string(bytes)
}

// HTML sends a HTML string.
func (ctx *Context) HTML(html string) string {
	ctx.SetHeader(contentTypeHeader, contentTypeHTML)
	ctx.SetHeader(contentTypeOptionsHeader, contentTypeOptions)
	ctx.SetHeader(xssProtectionHeader, xssProtection)
	ctx.SetHeader(xFrameOptionsHeader, xFrameOptions)
	ctx.SetHeader(referrerPolicyHeader, referrerPolicySameOrigin)

	if ctx.App.Security.Certificate != "" {
		ctx.SetHeader(strictTransportSecurityHeader, strictTransportSecurity)
		ctx.SetHeader(contentSecurityPolicyHeader, ctx.App.contentSecurityPolicy)
	}

	return html
}

// Text sends a plain text string.
func (ctx *Context) Text(text string) string {
	ctx.SetHeader(contentTypeHeader, contentTypePlainText)
	return text
}

// File sends the contents of a local file and determines its mime type by extension.
func (ctx *Context) File(file string) string {
	mimeType := mime.TypeByExtension(filepath.Ext(file))
	ctx.SetHeader(contentTypeHeader, mimeType)
	data, _ := ioutil.ReadFile(file)
	return string(data)
}

// Error should be used for sending error messages to the user.
func (ctx *Context) Error(statusCode int, explanation string, err error) string {
	ctx.StatusCode = statusCode
	ctx.SetHeader(contentTypeHeader, contentTypeHTML)
	// ctx.App.Logger.Error(
	// 	color.RedString(explanation),
	// 	zap.String("error", err.Error()),
	// 	zap.String("url", ctx.request.RequestURI),
	// )
	return explanation
}

// SetHeader sets header to value.
func (ctx *Context) SetHeader(header string, value string) {
	ctx.response.Header().Set(header, value)
}

// URI returns the relative path, e.g. /blog/post/123.
func (ctx *Context) URI() string {
	return ctx.request.URL.Path
}

// SetURI sets the relative path, e.g. /blog/post/123.
func (ctx *Context) SetURI(b string) {
	ctx.request.URL.Path = b
}

// Get retrieves an URL parameter.
func (ctx *Context) Get(param string) string {
	return ctx.params.ByName(param)
}

// GetInt retrieves an URL parameter as an integer.
func (ctx *Context) GetInt(param string) (int, error) {
	return strconv.Atoi(ctx.Get(param))
}

// RealIP tries to determine the real IP address of the request.
func (ctx *Context) RealIP() string {
	return realip.RealIP(ctx.request)
}

// UserAgent retrieves the user agent for the given request.
func (ctx *Context) UserAgent() string {
	return ctx.request.UserAgent()
}

// Respond responds either with raw code or gzipped if the
// code length is greater than the gzip threshold.
func (ctx *Context) Respond(code string) {
	ctx.RespondBytes(StringToBytesUnsafe(code))
}

// RespondBytes responds either with raw code or gzipped if the
// code length is greater than the gzip threshold. Requires a byte slice.
func (ctx *Context) RespondBytes(b []byte) {
	response := ctx.response
	header := response.Header()

	// Headers
	header.Set(serverHeader, server)
	header.Set(responseTimeHeader, strconv.FormatInt(time.Since(ctx.start).Nanoseconds()/1000, 10)+" us")
	header.Set(cacheControlHeader, cacheControlAlwaysValidate)

	// Body
	if ctx.App.Config.GZip && len(b) >= gzipThreshold {
		header.Set(contentEncodingHeader, contentEncodingGzip)

		// ETag generation
		h := xxhash.NewS64(0)
		h.Write(b)
		etag := strconv.FormatUint(h.Sum64(), 16)

		// If client cache is up to date, send 304 with no response body.
		clientETag := ctx.request.Header.Get(ifNoneMatchHeader)

		if etag == clientETag {
			response.WriteHeader(304)
			return
		}

		// Set ETag
		header.Set(etagHeader, etag)

		if ctx.App.Config.GZipCache {
			cachedResponse, found := ctx.App.gzipCache.Get(etag)

			if found {
				response.WriteHeader(ctx.StatusCode)
				response.Write(cachedResponse.([]byte))
				return
			}
		}

		var buffer bytes.Buffer
		writer := bufio.NewWriter(&buffer)
		fasthttp.WriteGzipLevel(writer, b, 9)
		writer.Flush()
		gzippedBytes := buffer.Bytes()

		response.WriteHeader(ctx.StatusCode)
		response.Write(gzippedBytes)

		if ctx.App.Config.GZipCache {
			ctx.App.gzipCache.Set(etag, gzippedBytes, cache.DefaultExpiration)
		}
	} else {
		response.WriteHeader(ctx.StatusCode)
		response.Write(b)
	}
}
<commit_msg>Changed Header commands<commit_after>package aero

import (
	"bufio"
	"bytes"
	"encoding/json"
	"io/ioutil"
	"mime"
	"net/http"
	"path/filepath"
	"strconv"
	"time"

	"github.com/OneOfOne/xxhash"
	"github.com/julienschmidt/httprouter"
	cache "github.com/patrickmn/go-cache"
	"github.com/tomasen/realip"
	"github.com/valyala/fasthttp"
)

// This should be close to the MTU size of a TCP packet.
// Regarding performance it makes no sense to compress smaller files.
// Bandwidth can be saved however the savings are minimal for small files
// and the overhead of compressing can lead up to a 75% reduction
// in server speed under high load. Therefore in this case
// we're trying to optimize for performance, not bandwidth.
const gzipThreshold = 1450

const (
	serverHeader                  = "Server"
	server                        = "Aero"
	cacheControlHeader            = "Cache-Control"
	cacheControlAlwaysValidate    = "must-revalidate"
	contentTypeOptionsHeader      = "X-Content-Type-Options"
	contentTypeOptions            = "nosniff"
	xssProtectionHeader           = "X-XSS-Protection"
	xssProtection                 = "1; mode=block"
	etagHeader                    = "ETag"
	contentTypeHeader             = "Content-Type"
	contentTypeHTML               = "text/html; charset=utf-8"
	contentTypeJSON               = "application/json"
	contentTypePlainText          = "text/plain; charset=utf-8"
	contentEncodingHeader         = "Content-Encoding"
	contentEncodingGzip           = "gzip"
	responseTimeHeader            = "X-Response-Time"
	ifNoneMatchHeader             = "If-None-Match"
	xFrameOptionsHeader           = "X-Frame-Options"
	xFrameOptions                 = "SAMEORIGIN"
	referrerPolicyHeader          = "Referrer-Policy"
	referrerPolicySameOrigin      = "no-referrer"
	strictTransportSecurityHeader = "Strict-Transport-Security"
	strictTransportSecurity       = "max-age=31536000; includeSubDomains; preload"
	contentSecurityPolicyHeader   = "Content-Security-Policy"
)

// Context ...
type Context struct {
	// net/http
	request  *http.Request
	response http.ResponseWriter
	params   httprouter.Params

	// A pointer to the application this request occured on.
	App *Application

	// Status code
	StatusCode int

	// Start time
	start time.Time

	// User session
	session *Session
}

// Handle ...
type Handle func(*Context) string

// Session returns the session of the context or creates and caches a new session.
func (ctx *Context) Session() *Session {
	// Return cached session if available.
	if ctx.session != nil {
		return ctx.session
	}

	// Check if the client has a session cookie already.
	// sid := ctx.requestCtx.Request.Header.CookieBytes(sidBytes)
	cookie, err := ctx.request.Cookie("sid")

	if err == nil {
		sid := cookie.Value

		if sid != "" {
			ctx.session = ctx.App.Sessions.Store.Get(sid)

			if ctx.session != nil {
				return ctx.session
			}
		}
	}

	// Create a new session
	ctx.session = ctx.App.Sessions.New()

	sessionCookie := http.Cookie{
		Name:     "sid",
		Value:    ctx.session.id,
		HttpOnly: true,
		Secure:   true,
	}

	http.SetCookie(ctx.response, &sessionCookie)

	return ctx.session
}

// JSON encodes the object to a JSON string and responds.
func (ctx *Context) JSON(value interface{}) string {
	bytes, _ := json.Marshal(value)

	ctx.SetResponseHeader(contentTypeHeader, contentTypeJSON)
	return string(bytes)
}

// HTML sends a HTML string.
func (ctx *Context) HTML(html string) string {
	ctx.SetResponseHeader(contentTypeHeader, contentTypeHTML)
	ctx.SetResponseHeader(contentTypeOptionsHeader, contentTypeOptions)
	ctx.SetResponseHeader(xssProtectionHeader, xssProtection)
	ctx.SetResponseHeader(xFrameOptionsHeader, xFrameOptions)
	ctx.SetResponseHeader(referrerPolicyHeader, referrerPolicySameOrigin)

	if ctx.App.Security.Certificate != "" {
		ctx.SetResponseHeader(strictTransportSecurityHeader, strictTransportSecurity)
		ctx.SetResponseHeader(contentSecurityPolicyHeader, ctx.App.contentSecurityPolicy)
	}

	return html
}

// Text sends a plain text string.
func (ctx *Context) Text(text string) string {
	ctx.SetResponseHeader(contentTypeHeader, contentTypePlainText)
	return text
}

// File sends the contents of a local file and determines its mime type by extension.
func (ctx *Context) File(file string) string {
	extension := filepath.Ext(file)
	mimeType := mime.TypeByExtension(extension)
	data, _ := ioutil.ReadFile(file)

	if mimeType == "" {
		mimeType = http.DetectContentType(data)
	}

	ctx.SetResponseHeader(contentTypeHeader, mimeType)
	return string(data)
}

// Error should be used for sending error messages to the user.
func (ctx *Context) Error(statusCode int, explanation string, err error) string {
	ctx.StatusCode = statusCode
	ctx.SetResponseHeader(contentTypeHeader, contentTypeHTML)
	// ctx.App.Logger.Error(
	// 	color.RedString(explanation),
	// 	zap.String("error", err.Error()),
	// 	zap.String("url", ctx.request.RequestURI),
	// )
	return explanation
}

// GetRequestHeader retrieves the value for the request header.
func (ctx *Context) GetRequestHeader(header string) string {
	return ctx.request.Header.Get(header)
}

// SetResponseHeader sets response header to value.
func (ctx *Context) SetResponseHeader(header string, value string) {
	ctx.response.Header().Set(header, value)
}

// URI returns the relative path, e.g. /blog/post/123.
func (ctx *Context) URI() string {
	return ctx.request.URL.Path
}

// SetURI sets the relative path, e.g. /blog/post/123.
func (ctx *Context) SetURI(b string) {
	ctx.request.URL.Path = b
}

// Get retrieves an URL parameter.
func (ctx *Context) Get(param string) string {
	return ctx.params.ByName(param)
}

// GetInt retrieves an URL parameter as an integer.
func (ctx *Context) GetInt(param string) (int, error) {
	return strconv.Atoi(ctx.Get(param))
}

// RealIP tries to determine the real IP address of the request.
func (ctx *Context) RealIP() string {
	return realip.RealIP(ctx.request)
}

// UserAgent retrieves the user agent for the given request.
func (ctx *Context) UserAgent() string {
	return ctx.request.UserAgent()
}

// Respond responds either with raw code or gzipped if the
// code length is greater than the gzip threshold.
func (ctx *Context) Respond(code string) {
	ctx.RespondBytes(StringToBytesUnsafe(code))
}

// RespondBytes responds either with raw code or gzipped if the
// code length is greater than the gzip threshold. Requires a byte slice.
func (ctx *Context) RespondBytes(b []byte) {
	response := ctx.response
	header := response.Header()

	// Headers
	header.Set(serverHeader, server)
	header.Set(responseTimeHeader, strconv.FormatInt(time.Since(ctx.start).Nanoseconds()/1000, 10)+" us")
	header.Set(cacheControlHeader, cacheControlAlwaysValidate)

	// Body
	if ctx.App.Config.GZip && len(b) >= gzipThreshold {
		header.Set(contentEncodingHeader, contentEncodingGzip)

		// ETag generation
		h := xxhash.NewS64(0)
		h.Write(b)
		etag := strconv.FormatUint(h.Sum64(), 16)

		// If client cache is up to date, send 304 with no response body.
		clientETag := ctx.request.Header.Get(ifNoneMatchHeader)

		if etag == clientETag {
			response.WriteHeader(304)
			return
		}

		// Set ETag
		header.Set(etagHeader, etag)

		if ctx.App.Config.GZipCache {
			cachedResponse, found := ctx.App.gzipCache.Get(etag)

			if found {
				response.WriteHeader(ctx.StatusCode)
				response.Write(cachedResponse.([]byte))
				return
			}
		}

		var buffer bytes.Buffer
		writer := bufio.NewWriter(&buffer)
		fasthttp.WriteGzipLevel(writer, b, 9)
		writer.Flush()
		gzippedBytes := buffer.Bytes()

		response.WriteHeader(ctx.StatusCode)
		response.Write(gzippedBytes)

		if ctx.App.Config.GZipCache {
			ctx.App.gzipCache.Set(etag, gzippedBytes, cache.DefaultExpiration)
		}
	} else {
		response.WriteHeader(ctx.StatusCode)
		response.Write(b)
	}
}
<|endoftext|>