<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2017 Red Hat, Inc.
 *
 */

package cache

import (
	"sync"
	"time"

	"k8s.io/client-go/tools/record"

	k8sv1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/tools/cache"

	"kubevirt.io/client-go/log"
	cmdclient "kubevirt.io/kubevirt/pkg/virt-handler/cmd-client"
	notifyserver "kubevirt.io/kubevirt/pkg/virt-handler/notify-server"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
	"kubevirt.io/kubevirt/pkg/watchdog"
)

func newListWatchFromNotify(virtShareDir string, watchdogTimeout int, recorder record.EventRecorder, vmiStore cache.Store) cache.ListerWatcher {
	d := &DomainWatcher{
		backgroundWatcherStarted: false,
		virtShareDir:             virtShareDir,
		watchdogTimeout:          watchdogTimeout,
		recorder:                 recorder,
		vmiStore:                 vmiStore,
	}

	return d
}

type DomainWatcher struct {
	lock                     sync.Mutex
	wg                       sync.WaitGroup
	stopChan                 chan struct{}
	eventChan                chan watch.Event
	backgroundWatcherStarted bool
	virtShareDir             string
	watchdogTimeout          int
	recorder                 record.EventRecorder
	vmiStore                 cache.Store
}

func (d *DomainWatcher) startBackground() error {
	d.lock.Lock()
	defer d.lock.Unlock()

	if d.backgroundWatcherStarted == true {
		return nil
	}

	d.stopChan = make(chan struct{}, 1)
	d.eventChan = make(chan watch.Event, 100)

	d.wg.Add(1)
	go func() {
		defer d.wg.Done()

		expiredWatchdogTicker := time.NewTicker(time.Duration(d.watchdogTimeout) * time.Second).C
		srvErr := make(chan error)
		go func() {
			defer close(srvErr)
			err := notifyserver.RunServer(d.virtShareDir, d.stopChan, d.eventChan, d.recorder, d.vmiStore)
			srvErr <- err
		}()

		for {
			select {
			case <-expiredWatchdogTicker:
				d.handleStaleWatchdogFiles()
			case err := <-srvErr:
				if err != nil {
					log.Log.Reason(err).Errorf("Unexpected err encountered with Domain Notify aggregation server")
				}

				// server exitted so this goroutine is done.
				return
			}
		}
	}()

	d.backgroundWatcherStarted = true
	return nil
}

func (d *DomainWatcher) handleStaleWatchdogFiles() error {
	domains, err := watchdog.GetExpiredDomains(d.watchdogTimeout, d.virtShareDir)
	if err != nil {
		log.Log.Reason(err).Error("failed to detect expired watchdog files in domain informer")
		return err
	}

	for _, domain := range domains {
		log.Log.Object(domain).Warning("detected expire watchdog for domain")
		d.eventChan <- watch.Event{Type: watch.Deleted, Object: domain}
	}
	return nil
}

func (d *DomainWatcher) listAllKnownDomains() ([]*api.Domain, error) {
	var domains []*api.Domain

	socketFiles, err := cmdclient.ListAllSockets(d.virtShareDir)
	if err != nil {
		return nil, err
	}
	for _, socketFile := range socketFiles {
		log.Log.V(3).Infof("List domains from sock %s", socketFile)
		client, err := cmdclient.NewClient(socketFile)
		if err != nil {
			log.Log.Reason(err).Error("failed to connect to cmd client socket")
			// Ignore failure to connect to client.
			// These are all local connections via unix socket.
			// A failure to connect means there's nothing on the other
			// end listening.
			continue
		}
		defer client.Close()

		domain, exists, err := client.GetDomain()
		if err != nil {
			log.Log.Reason(err).Error("failed to list domains on cmd client socket")
			// Failure to get domain list means that client
			// was unable to contact libvirt. As soon as the connection
			// is restored on the client's end, a domain notification will
			// be sent.
			continue
		}
		if exists == true {
			domains = append(domains, domain)
		}
	}
	return domains, nil
}

func (d *DomainWatcher) List(options k8sv1.ListOptions) (runtime.Object, error) {

	log.Log.V(3).Info("Synchronizing domains")
	err := d.startBackground()
	if err != nil {
		return nil, err
	}

	domains, err := d.listAllKnownDomains()
	if err != nil {
		return nil, err
	}

	list := api.DomainList{
		Items: []api.Domain{},
	}

	for _, domain := range domains {
		list.Items = append(list.Items, *domain)
	}
	return &list, nil
}

func (d *DomainWatcher) Watch(options k8sv1.ListOptions) (watch.Interface, error) {
	return d, nil
}

func (d *DomainWatcher) Stop() {
	d.lock.Lock()
	defer d.lock.Unlock()

	if d.backgroundWatcherStarted == false {
		return
	}
	close(d.stopChan)
	d.wg.Wait()
	d.backgroundWatcherStarted = false
	close(d.eventChan)
}

func (d *DomainWatcher) ResultChan() <-chan watch.Event {
	return d.eventChan
}

func NewSharedInformer(virtShareDir string, watchdogTimeout int, recorder record.EventRecorder, vmiStore cache.Store) (cache.SharedInformer, error) {
	lw := newListWatchFromNotify(virtShareDir, watchdogTimeout, recorder, vmiStore)
	informer := cache.NewSharedInformer(lw, &api.Domain{}, 0)
	return informer, nil
}
<commit_msg>Fix typo in expired watchdog log<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2017 Red Hat, Inc.
 *
 */

package cache

import (
	"sync"
	"time"

	"k8s.io/client-go/tools/record"

	k8sv1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/tools/cache"

	"kubevirt.io/client-go/log"
	cmdclient "kubevirt.io/kubevirt/pkg/virt-handler/cmd-client"
	notifyserver "kubevirt.io/kubevirt/pkg/virt-handler/notify-server"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
	"kubevirt.io/kubevirt/pkg/watchdog"
)

func newListWatchFromNotify(virtShareDir string, watchdogTimeout int, recorder record.EventRecorder, vmiStore cache.Store) cache.ListerWatcher {
	d := &DomainWatcher{
		backgroundWatcherStarted: false,
		virtShareDir:             virtShareDir,
		watchdogTimeout:          watchdogTimeout,
		recorder:                 recorder,
		vmiStore:                 vmiStore,
	}

	return d
}

type DomainWatcher struct {
	lock                     sync.Mutex
	wg                       sync.WaitGroup
	stopChan                 chan struct{}
	eventChan                chan watch.Event
	backgroundWatcherStarted bool
	virtShareDir             string
	watchdogTimeout          int
	recorder                 record.EventRecorder
	vmiStore                 cache.Store
}

func (d *DomainWatcher) startBackground() error {
	d.lock.Lock()
	defer d.lock.Unlock()

	if d.backgroundWatcherStarted == true {
		return nil
	}

	d.stopChan = make(chan struct{}, 1)
	d.eventChan = make(chan watch.Event, 100)

	d.wg.Add(1)
	go func() {
		defer d.wg.Done()

		expiredWatchdogTicker := time.NewTicker(time.Duration(d.watchdogTimeout) * time.Second).C
		srvErr := make(chan error)
		go func() {
			defer close(srvErr)
			err := notifyserver.RunServer(d.virtShareDir, d.stopChan, d.eventChan, d.recorder, d.vmiStore)
			srvErr <- err
		}()

		for {
			select {
			case <-expiredWatchdogTicker:
				d.handleStaleWatchdogFiles()
			case err := <-srvErr:
				if err != nil {
					log.Log.Reason(err).Errorf("Unexpected err encountered with Domain Notify aggregation server")
				}

				// server exitted so this goroutine is done.
				return
			}
		}
	}()

	d.backgroundWatcherStarted = true
	return nil
}

func (d *DomainWatcher) handleStaleWatchdogFiles() error {
	domains, err := watchdog.GetExpiredDomains(d.watchdogTimeout, d.virtShareDir)
	if err != nil {
		log.Log.Reason(err).Error("failed to detect expired watchdog files in domain informer")
		return err
	}

	for _, domain := range domains {
		log.Log.Object(domain).Warning("detected expired watchdog for domain")
		d.eventChan <- watch.Event{Type: watch.Deleted, Object: domain}
	}
	return nil
}

func (d *DomainWatcher) listAllKnownDomains() ([]*api.Domain, error) {
	var domains []*api.Domain

	socketFiles, err := cmdclient.ListAllSockets(d.virtShareDir)
	if err != nil {
		return nil, err
	}
	for _, socketFile := range socketFiles {
		log.Log.V(3).Infof("List domains from sock %s", socketFile)
		client, err := cmdclient.NewClient(socketFile)
		if err != nil {
			log.Log.Reason(err).Error("failed to connect to cmd client socket")
			// Ignore failure to connect to client.
			// These are all local connections via unix socket.
			// A failure to connect means there's nothing on the other
			// end listening.
			continue
		}
		defer client.Close()

		domain, exists, err := client.GetDomain()
		if err != nil {
			log.Log.Reason(err).Error("failed to list domains on cmd client socket")
			// Failure to get domain list means that client
			// was unable to contact libvirt. As soon as the connection
			// is restored on the client's end, a domain notification will
			// be sent.
			continue
		}
		if exists == true {
			domains = append(domains, domain)
		}
	}
	return domains, nil
}

func (d *DomainWatcher) List(options k8sv1.ListOptions) (runtime.Object, error) {

	log.Log.V(3).Info("Synchronizing domains")
	err := d.startBackground()
	if err != nil {
		return nil, err
	}

	domains, err := d.listAllKnownDomains()
	if err != nil {
		return nil, err
	}

	list := api.DomainList{
		Items: []api.Domain{},
	}

	for _, domain := range domains {
		list.Items = append(list.Items, *domain)
	}
	return &list, nil
}

func (d *DomainWatcher) Watch(options k8sv1.ListOptions) (watch.Interface, error) {
	return d, nil
}

func (d *DomainWatcher) Stop() {
	d.lock.Lock()
	defer d.lock.Unlock()

	if d.backgroundWatcherStarted == false {
		return
	}
	close(d.stopChan)
	d.wg.Wait()
	d.backgroundWatcherStarted = false
	close(d.eventChan)
}

func (d *DomainWatcher) ResultChan() <-chan watch.Event {
	return d.eventChan
}

func NewSharedInformer(virtShareDir string, watchdogTimeout int, recorder record.EventRecorder, vmiStore cache.Store) (cache.SharedInformer, error) {
	lw := newListWatchFromNotify(virtShareDir, watchdogTimeout, recorder, vmiStore)
	informer := cache.NewSharedInformer(lw, &api.Domain{}, 0)
	return informer, nil
}
<|endoftext|>


<commit_before>package wtwire

// ErrorCode represents a generic error code used when replying to watchtower
// clients. Specific reply messages may extend the ErrorCode primitive and add
// custom codes, so long as they don't collide with the generic error codes..
type ErrorCode uint16

const (
	// CodeOK signals that the request was successfully processed by the
	// watchtower.
	CodeOK ErrorCode = 0

	// CodeTemporaryFailure alerts the client that the watchtower is
	// temporarily unavailable, but that it may try again at a later time.
	CodeTemporaryFailure ErrorCode = 40

	// CodePermanentFailure alerts the client that the watchtower has
	// permanently failed, and further communication should be avoided.
	CodePermanentFailure ErrorCode = 50
)
<commit_msg>watchtower/wtwire/error_code: add human-readable descriptors<commit_after>package wtwire

import "fmt"

// ErrorCode represents a generic error code used when replying to watchtower
// clients. Specific reply messages may extend the ErrorCode primitive and add
// custom codes, so long as they don't collide with the generic error codes..
type ErrorCode uint16

const (
	// CodeOK signals that the request was successfully processed by the
	// watchtower.
	CodeOK ErrorCode = 0

	// CodeTemporaryFailure alerts the client that the watchtower is
	// temporarily unavailable, but that it may try again at a later time.
	CodeTemporaryFailure ErrorCode = 40

	// CodePermanentFailure alerts the client that the watchtower has
	// permanently failed, and further communication should be avoided.
	CodePermanentFailure ErrorCode = 50
)

// String returns a human-readable description of an ErrorCode.
func (c ErrorCode) String() string {
	switch c {
	case CodeOK:
		return "CodeOK"
	case CodeTemporaryFailure:
		return "CodeTemporaryFailure"
	case CodePermanentFailure:
		return "CodePermanentFailure"
	case CreateSessionCodeAlreadyExists:
		return "CreateSessionCodeAlreadyExists"
	case CreateSessionCodeRejectMaxUpdates:
		return "CreateSessionCodeRejectMaxUpdates"
	case CreateSessionCodeRejectRewardRate:
		return "CreateSessionCodeRejectRewardRate"
	case CreateSessionCodeRejectSweepFeeRate:
		return "CreateSessionCodeRejectSweepFeeRate"
	case CreateSessionCodeRejectBlobType:
		return "CreateSessionCodeRejectBlobType"
	case StateUpdateCodeClientBehind:
		return "StateUpdateCodeClientBehind"
	case StateUpdateCodeMaxUpdatesExceeded:
		return "StateUpdateCodeMaxUpdatesExceeded"
	case StateUpdateCodeSeqNumOutOfOrder:
		return "StateUpdateCodeSeqNumOutOfOrder"
	default:
		return fmt.Sprintf("UnknownErrorCode: %d", c)
	}
}
<|endoftext|>


<commit_before>package ditaconv

import (
	"encoding/xml"
	"fmt"
	"io"
	"strings"

	"github.com/egonelbre/fedwiki"
	"github.com/egonelbre/fedwiki/item"

	"github.com/raintreeinc/knowledgebase/dita/ditaindex"
	"github.com/raintreeinc/knowledgebase/dita/xmlconv"
)

// Convert converts a topic to a federated wiki page
func (mapping *Mapping) Convert(topic *ditaindex.Topic) (page *fedwiki.Page, fatal error, errs []error) {
	// make a shallow copy of rules
	rules := xmlconv.NewRules()
	rules.Translate = mapping.Rules.Translate
	rules.Callback = mapping.Rules.Callback
	rules.Remove = mapping.Rules.Remove
	rules.Unwrap = mapping.Rules.Unwrap
	rules.Handle = mapping.Rules.Handle

	slug := mapping.ByTopic[topic]
	if slug[0] != '/' {
		slug = "/" + slug
	}
	convert := &convert{
		Page:    &fedwiki.Page{},
		Slug:    slug,
		Topic:   topic,
		Index:   mapping.Index,
		Mapping: mapping,
		Rules:   rules,
	}

	convert.Rules.Handle.Element = convert.handleAttrs

	convert.run()

	return convert.Page, convert.Fatal, convert.Errors
}

type convert struct {
	Page *fedwiki.Page

	Slug    fedwiki.Slug
	Topic   *ditaindex.Topic
	Index   *ditaindex.Index
	Mapping *Mapping

	Rules *xmlconv.Rules

	Errors []error
	Fatal  error
}

func (conv *convert) check(err error) bool {
	if err != nil {
		conv.Errors = append(conv.Errors, err)
		return true
	}
	return false
}

// entrypoint for starting the conversion
func (conv *convert) run() {
	info, err := conv.Index.Dir.Stat(conv.Topic.Filename)
	if err != nil {
		conv.Fatal = err
		return
	}

	topic := conv.Topic.Original
	if topic == nil {
		conv.Fatal = fmt.Errorf("no original topic")
		return
	}

	// find the body content
	bodytext := ""
	for _, node := range topic.Elements {
		if isBodyTag(node.XMLName.Local) {
			if bodytext != "" {
				conv.Errors = append(conv.Errors, fmt.Errorf("multiple body tags"))
				continue
			}
			bodytext = node.Content
		}
	}

	// create meta information
	tags := convertTags(topic.Keywords)
	meta := make(fedwiki.Meta)
	if len(tags) > 0 {
		meta["tags"] = tags
	}
	meta["kind"] = "help"

	// create the page header
	conv.Page = &fedwiki.Page{
		PageHeader: fedwiki.PageHeader{
			Slug:     conv.Slug,
			Title:    conv.Topic.Title,
			Date:     fedwiki.NewDate(info.ModTime()),
			Synopsis: conv.Topic.Synopsis,
			Meta:     meta,
		},
	}

	defer func() {
		if x := recover(); x != nil {
			conv.Fatal = fmt.Errorf("fatal: %v", x)
		}
	}()

	conv.parse(bodytext)
	conv.addRelatedLinks()
}

// splits node recursively into multiple story items
func (conv *convert) parse(text string) {
	decoder := xml.NewDecoder(strings.NewReader(text))
	conv.unwrap(decoder, nil)
}

// splits node recursively into multiple story items
func (conv *convert) unwrap(decoder *xml.Decoder, start *xml.StartElement) {
	for {
		token, err := decoder.Token()
		if err == io.EOF {
			return
		}
		if conv.check(err) {
			return
		}

		switch token := token.(type) {
		case xml.StartElement:
			if shouldUnwrap(token.Name) {
				conv.unwrap(decoder, &token)
			} else {
				conv.convertItem(decoder, &token)
			}
		case xml.CharData, xml.Comment, xml.ProcInst, xml.Directive:
			// ignore
		case xml.EndElement:
			return
		}
	}
}

func (conv *convert) addRelatedLinks() {
	for _, set := range conv.Topic.Links {
		text := "
Related
"
		text += "
"

		links := []*ditaindex.Topic{set.Parent, set.Prev, set.Next}
		links = append(links, set.Children...)
		links = append(links, set.Siblings...)

		for _, topic := range links {
			if topic == nil {
				continue
			}
			text += "
" + conv.asLink(topic) + "
"
		}
		text += "
"

		conv.Page.Story.Append(item.HTML(text))
	}
}

func (conv *convert) asLink(topic *ditaindex.Topic) string {
	slug := string(conv.Mapping.ByTopic[topic])
	title := topic.Title
	return "" + title + ""
}
<commit_msg>Added assigning content IDs<commit_after>package ditaconv

import (
	"encoding/xml"
	"fmt"
	"io"
	"strings"

	"github.com/egonelbre/fedwiki"
	"github.com/egonelbre/fedwiki/item"

	"github.com/raintreeinc/knowledgebase/dita/ditaindex"
	"github.com/raintreeinc/knowledgebase/dita/xmlconv"
)

// Convert converts a topic to a federated wiki page
func (mapping *Mapping) Convert(topic *ditaindex.Topic) (page *fedwiki.Page, fatal error, errs []error) {
	// make a shallow copy of rules
	rules := xmlconv.NewRules()
	rules.Translate = mapping.Rules.Translate
	rules.Callback = mapping.Rules.Callback
	rules.Remove = mapping.Rules.Remove
	rules.Unwrap = mapping.Rules.Unwrap
	rules.Handle = mapping.Rules.Handle

	slug := mapping.ByTopic[topic]
	if slug[0] != '/' {
		slug = "/" + slug
	}
	convert := &convert{
		Page:    &fedwiki.Page{},
		Slug:    slug,
		Topic:   topic,
		Index:   mapping.Index,
		Mapping: mapping,
		Rules:   rules,
	}

	convert.Rules.Handle.Element = convert.handleAttrs

	convert.run()

	return convert.Page, convert.Fatal, convert.Errors
}

type convert struct {
	Page *fedwiki.Page

	Slug    fedwiki.Slug
	Topic   *ditaindex.Topic
	Index   *ditaindex.Index
	Mapping *Mapping

	Rules *xmlconv.Rules

	Errors []error
	Fatal  error
}

func (conv *convert) check(err error) bool {
	if err != nil {
		conv.Errors = append(conv.Errors, err)
		return true
	}
	return false
}

// entrypoint for starting the conversion
func (conv *convert) run() {
	info, err := conv.Index.Dir.Stat(conv.Topic.Filename)
	if err != nil {
		conv.Fatal = err
		return
	}

	topic := conv.Topic.Original
	if topic == nil {
		conv.Fatal = fmt.Errorf("no original topic")
		return
	}

	// find the body content
	bodytext := ""
	for _, node := range topic.Elements {
		if isBodyTag(node.XMLName.Local) {
			if bodytext != "" {
				conv.Errors = append(conv.Errors, fmt.Errorf("multiple body tags"))
				continue
			}
			bodytext = node.Content
		}
	}

	// create meta information
	tags := convertTags(topic.Keywords)
	meta := make(fedwiki.Meta)
	if len(tags) > 0 {
		meta["tags"] = tags
	}
	meta["kind"] = "help"

	// create the page header
	conv.Page = &fedwiki.Page{
		PageHeader: fedwiki.PageHeader{
			Slug:     conv.Slug,
			Title:    conv.Topic.Title,
			Date:     fedwiki.NewDate(info.ModTime()),
			Synopsis: conv.Topic.Synopsis,
			Meta:     meta,
		},
	}

	defer func() {
		if x := recover(); x != nil {
			conv.Fatal = fmt.Errorf("fatal: %v", x)
		}
	}()

	conv.parse(bodytext)
	conv.addRelatedLinks()
	conv.assignIDs()
}

// splits node recursively into multiple story items
func (conv *convert) parse(text string) {
	decoder := xml.NewDecoder(strings.NewReader(text))
	conv.unwrap(decoder, nil)
}

// splits node recursively into multiple story items
func (conv *convert) unwrap(decoder *xml.Decoder, start *xml.StartElement) {
	for {
		token, err := decoder.Token()
		if err == io.EOF {
			return
		}
		if conv.check(err) {
			return
		}

		switch token := token.(type) {
		case xml.StartElement:
			if shouldUnwrap(token.Name) {
				conv.unwrap(decoder, &token)
			} else {
				conv.convertItem(decoder, &token)
			}
		case xml.CharData, xml.Comment, xml.ProcInst, xml.Directive:
			// ignore
		case xml.EndElement:
			return
		}
	}
}

func (conv *convert) addRelatedLinks() {
	for _, set := range conv.Topic.Links {
		text := "
Related
"
		text += "
"

		links := []*ditaindex.Topic{set.Parent, set.Prev, set.Next}
		links = append(links, set.Children...)
		links = append(links, set.Siblings...)

		for _, topic := range links {
			if topic == nil {
				continue
			}
			text += "
" + conv.asLink(topic) + "
"
		}
		text += "
"

		conv.Page.Story.Append(item.HTML(text))
	}
}

func (conv *convert) asLink(topic *ditaindex.Topic) string {
	slug := string(conv.Mapping.ByTopic[topic])
	title := topic.Title
	return "" + title + ""
}

func (conv *convert) assignIDs() {
	s := conv.Page.Story
	for _, item := range s {
		item["id"] = fedwiki.NewID()
	}
}
<|endoftext|>


<commit_before>package host

import (
	"errors"
	"io"
	"os"
	"path/filepath"
	"strconv"

	"github.com/NebulousLabs/Sia/consensus"
	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/modules"
)

var (
	HostCapacityErr = errors.New("host is at capacity and can not take more files")
)

// allocate allocates space for a file and creates it on disk.
func (h *Host) allocate(filesize uint64) (file *os.File, path string, err error) {
	h.spaceRemaining -= int64(filesize)
	h.fileCounter++
	path = strconv.Itoa(h.fileCounter)
	fullpath := filepath.Join(h.hostDir, path)
	file, err = os.Create(fullpath)
	if err != nil {
		return
	}
	return
}

// deallocate deletes a file and restores its allocated space.
func (h *Host) deallocate(filesize uint64, path string) {
	fullpath := filepath.Join(h.hostDir, path)
	os.Remove(fullpath)
	h.spaceRemaining += int64(filesize)
}

// considerTerms checks that the terms of a potential file contract fall
// within acceptable bounds, as defined by the host.
func (h *Host) considerTerms(terms modules.ContractTerms) error {
	switch {
	case terms.FileSize < h.MinFilesize || terms.FileSize > h.MaxFilesize:
		return errors.New("file is of incorrect size")

	case terms.FileSize > uint64(h.spaceRemaining):
		return HostCapacityErr

	case terms.Duration < h.MinDuration || terms.Duration > h.MaxDuration:
		return errors.New("duration is out of bounds")

	case terms.DurationStart >= h.state.Height():
		return errors.New("duration cannot start in the future")

	case terms.WindowSize < h.MinWindow:
		return errors.New("challenge window is not large enough")

	case terms.Price.Cmp(h.Price) < 0:
		return errors.New("price does not match host settings")

	case terms.Collateral.Cmp(h.Collateral) > 0:
		return errors.New("collateral does not match host settings")

	case len(terms.ValidProofOutputs) != 1:
		return errors.New("payment len does not match host settings")

	case terms.ValidProofOutputs[0].UnlockHash != h.UnlockHash:
		return errors.New("payment output does not match host settings")

	case len(terms.MissedProofOutputs) != 1:
		return errors.New("refund len does not match host settings")

	case terms.MissedProofOutputs[0].UnlockHash != consensus.ZeroUnlockHash:
		return errors.New("coins are not paying out to correct address")
	}

	return nil
}

// verifyTransaction checks that the provided transaction matches the provided
// contract terms, and that the Merkle root provided is equal to the merkle
// root of the transaction file contract.
func verifyTransaction(txn consensus.Transaction, terms modules.ContractTerms, merkleRoot crypto.Hash) error {
	// Check that there is only one file contract.
	if len(txn.FileContracts) != 1 {
		return errors.New("transaction should have only one file contract.")
	}
	fc := txn.FileContracts[0]

	// Get the expected payout.
	sizeCurrency := consensus.NewCurrency64(terms.FileSize)
	durationCurrency := consensus.NewCurrency64(uint64(terms.Duration))
	clientCost := terms.Price.Mul(sizeCurrency).Mul(durationCurrency)
	hostCollateral := terms.Collateral.Mul(sizeCurrency).Mul(durationCurrency)
	expectedPayout := clientCost.Add(hostCollateral)

	switch {
	case fc.FileSize != terms.FileSize:
		return errors.New("bad file contract file size")

	case fc.FileMerkleRoot != merkleRoot:
		return errors.New("bad file contract Merkle root")

	case fc.Start != terms.DurationStart+terms.Duration:
		return errors.New("bad file contract start height")

	case fc.Expiration != terms.DurationStart+terms.Duration+terms.WindowSize:
		return errors.New("bad file contract expiration")

	case fc.Payout.Cmp(expectedPayout) != 0:
		return errors.New("bad file contract payout")

	case len(fc.ValidProofOutputs) != 1:
		return errors.New("bad file contract valid proof outputs")

	case fc.ValidProofOutputs[0].UnlockHash != terms.ValidProofOutputs[0].UnlockHash:
		return errors.New("bad file contract valid proof outputs")

	case len(fc.MissedProofOutputs) != 1:
		return errors.New("bad file contract missed proof outputs")

	case fc.MissedProofOutputs[0].UnlockHash != terms.MissedProofOutputs[0].UnlockHash:
		return errors.New("bad file contract missed proof outputs")

	case fc.TerminationHash != consensus.ZeroUnlockHash:
		return errors.New("bad file contract termination hash")
	}
	return nil
}

// addCollateral takes a transaction and its contract terms and adds the host
// collateral to the transaction.
func (h *Host) addCollateral(txn consensus.Transaction, terms modules.ContractTerms) (fundedTxn consensus.Transaction, txnID string, err error) {
	// Determine the amount of colletaral the host needs to provide.
	sizeCurrency := consensus.NewCurrency64(terms.FileSize)
	durationCurrency := consensus.NewCurrency64(uint64(terms.Duration))
	collateral := terms.Collateral.Mul(sizeCurrency).Mul(durationCurrency)

	txnID, err = h.wallet.RegisterTransaction(txn)
	if err != nil {
		return
	}
	if collateral.Cmp(consensus.NewCurrency64(0)) == 0 {
		return txn, txnID, nil
	}
	fundedTxn, err = h.wallet.FundTransaction(txnID, collateral)
	if err != nil {
		return
	}
	return
}

// NegotiateContract is an RPC that negotiates a file contract. If the
// negotiation is successful, the file is downloaded and the host begins
// submitting proofs of storage.
func (h *Host) NegotiateContract(conn modules.NetConn) (err error) {
	// Read the contract terms.
	var terms modules.ContractTerms
	err = conn.ReadObject(&terms, maxContractLen)
	if err != nil {
		return
	}

	// Consider the contract terms. If they are unnacceptable, return an error
	// describing why.
	h.mu.RLock()
	err = h.considerTerms(terms)
	h.mu.RUnlock()
	if err != nil {
		err = conn.WriteObject(err.Error())
		return
	}

	// terms are acceptable; allocate space for file
	h.mu.Lock()
	file, path, err := h.allocate(terms.FileSize)
	h.mu.Unlock()
	if err != nil {
		return
	}
	defer file.Close()

	// rollback everything if something goes wrong
	defer func() {
		if err != nil {
			h.deallocate(terms.FileSize, path)
		}
	}()

	// signal that we are ready to download file
	err = conn.WriteObject(modules.AcceptTermsResponse)
	if err != nil {
		return
	}

	// simultaneously download file and calculate its Merkle root.
	tee := io.TeeReader(
		// use a LimitedReader to ensure we don't read indefinitely
		io.LimitReader(conn, int64(terms.FileSize)),
		// each byte we read from tee will also be written to file
		file,
	)
	merkleRoot, err := crypto.ReaderMerkleRoot(tee)
	if err != nil {
		return
	}

	// Data has been sent, read in the unsigned transaction with the file
	// contract.
	var unsignedTxn consensus.Transaction
	err = conn.ReadObject(&unsignedTxn, maxContractLen)
	if err != nil {
		return
	}

	// Verify that the transaction matches the agreed upon terms, and that the
	// Merkle root in the file contract matches our independently calculated
	// Merkle root.
	err = verifyTransaction(unsignedTxn, terms, merkleRoot)
	if err != nil {
		err = errors.New("transaction does not satisfy terms: " + err.Error())
		conn.WriteObject(err.Error())
		return
	}

	// Add the collateral to the transaction, but do not sign the transaction.
	collateralTxn, txnID, err := h.addCollateral(unsignedTxn, terms)
	if err != nil {
		return
	}
	err = conn.WriteObject(collateralTxn)
	if err != nil {
		return
	}

	// Read in the renter-signed transaction and check that it matches the
	// previously accepted transaction.
	var signedTxn consensus.Transaction
	err = conn.ReadObject(&signedTxn, maxContractLen)
	if err != nil {
		return
	}
	if collateralTxn.ID() != signedTxn.ID() {
		err = errors.New("signed transaction does not match the transaction with collateral")
		return
	}

	// Add the signatures from the renter signed transaction, and then sign the
	// transaction, then submit the transaction.
	for _, sig := range signedTxn.Signatures {
		_, _, err = h.wallet.AddSignature(txnID, sig)
		if err != nil {
			return
		}
	}
	fullTxn, err := h.wallet.SignTransaction(txnID, true)
	if err != nil {
		return
	}
	err = h.tpool.AcceptTransaction(fullTxn)
	if err != nil {
		return
	}

	// Add this contract to the host's list of obligations.
	fcid := signedTxn.FileContractID(0)
	fc := signedTxn.FileContracts[0]
	proofHeight := fc.Expiration + StorageProofReorgDepth
	co := contractObligation{
		id:           fcid,
		fileContract: fc,
		path:         path,
	}
	h.mu.Lock()
	h.obligationsByHeight[proofHeight] = append(h.obligationsByHeight[proofHeight], co)
	h.obligationsByID[fcid] = co
	h.mu.Unlock()

	// TODO: we don't currently watch the blockchain to make sure that the
	// transaction actually gets into the blockchain.

	return
}
<commit_msg>fix negotiation mismatch<commit_after>package host

import (
	"errors"
	"io"
	"os"
	"path/filepath"
	"strconv"

	"github.com/NebulousLabs/Sia/consensus"
	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/modules"
)

var (
	HostCapacityErr = errors.New("host is at capacity and can not take more files")
)

// allocate allocates space for a file and creates it on disk.
func (h *Host) allocate(filesize uint64) (file *os.File, path string, err error) {
	h.spaceRemaining -= int64(filesize)
	h.fileCounter++
	path = strconv.Itoa(h.fileCounter)
	fullpath := filepath.Join(h.hostDir, path)
	file, err = os.Create(fullpath)
	if err != nil {
		return
	}
	return
}

// deallocate deletes a file and restores its allocated space.
func (h *Host) deallocate(filesize uint64, path string) {
	fullpath := filepath.Join(h.hostDir, path)
	os.Remove(fullpath)
	h.spaceRemaining += int64(filesize)
}

// considerTerms checks that the terms of a potential file contract fall
// within acceptable bounds, as defined by the host.
func (h *Host) considerTerms(terms modules.ContractTerms) error {
	switch {
	case terms.FileSize < h.MinFilesize || terms.FileSize > h.MaxFilesize:
		return errors.New("file is of incorrect size")

	case terms.FileSize > uint64(h.spaceRemaining):
		return HostCapacityErr

	case terms.Duration < h.MinDuration || terms.Duration > h.MaxDuration:
		return errors.New("duration is out of bounds")

	case terms.DurationStart >= h.state.Height():
		return errors.New("duration cannot start in the future")

	case terms.WindowSize < h.MinWindow:
		return errors.New("challenge window is not large enough")

	case terms.Price.Cmp(h.Price) < 0:
		return errors.New("price does not match host settings")

	case terms.Collateral.Cmp(h.Collateral) > 0:
		return errors.New("collateral does not match host settings")

	case len(terms.ValidProofOutputs) != 1:
		return errors.New("payment len does not match host settings")

	case terms.ValidProofOutputs[0].UnlockHash != h.UnlockHash:
		return errors.New("payment output does not match host settings")

	case len(terms.MissedProofOutputs) != 1:
		return errors.New("refund len does not match host settings")

	case terms.MissedProofOutputs[0].UnlockHash != consensus.ZeroUnlockHash:
		return errors.New("coins are not paying out to correct address")
	}

	return nil
}

// verifyTransaction checks that the provided transaction matches the provided
// contract terms, and that the Merkle root provided is equal to the merkle
// root of the transaction file contract.
func verifyTransaction(txn consensus.Transaction, terms modules.ContractTerms, merkleRoot crypto.Hash) error {
	// Check that there is only one file contract.
	if len(txn.FileContracts) != 1 {
		return errors.New("transaction should have only one file contract.")
	}
	fc := txn.FileContracts[0]

	// Get the expected payout.
	sizeCurrency := consensus.NewCurrency64(terms.FileSize)
	durationCurrency := consensus.NewCurrency64(uint64(terms.Duration))
	clientCost := terms.Price.Mul(sizeCurrency).Mul(durationCurrency)
	hostCollateral := terms.Collateral.Mul(sizeCurrency).Mul(durationCurrency)
	expectedPayout := clientCost.Add(hostCollateral)

	switch {
	case fc.FileSize != terms.FileSize:
		return errors.New("bad file contract file size")

	case fc.FileMerkleRoot != merkleRoot:
		return errors.New("bad file contract Merkle root")

	case fc.Start != terms.DurationStart+terms.Duration:
		return errors.New("bad file contract start height")

	case fc.Expiration != terms.DurationStart+terms.Duration+terms.WindowSize:
		return errors.New("bad file contract expiration")

	case fc.Payout.Cmp(expectedPayout) != 0:
		return errors.New("bad file contract payout")

	case len(fc.ValidProofOutputs) != 1:
		return errors.New("bad file contract valid proof outputs")

	case fc.ValidProofOutputs[0].UnlockHash != terms.ValidProofOutputs[0].UnlockHash:
		return errors.New("bad file contract valid proof outputs")

	case len(fc.MissedProofOutputs) != 1:
		return errors.New("bad file contract missed proof outputs")

	case fc.MissedProofOutputs[0].UnlockHash != terms.MissedProofOutputs[0].UnlockHash:
		return errors.New("bad file contract missed proof outputs")

	case fc.TerminationHash != consensus.ZeroUnlockHash:
		return errors.New("bad file contract termination hash")
	}
	return nil
}

// addCollateral takes a transaction and its contract terms and adds the host
// collateral to the transaction.
func (h *Host) addCollateral(txn consensus.Transaction, terms modules.ContractTerms) (fundedTxn consensus.Transaction, txnID string, err error) {
	// Determine the amount of colletaral the host needs to provide.
	sizeCurrency := consensus.NewCurrency64(terms.FileSize)
	durationCurrency := consensus.NewCurrency64(uint64(terms.Duration))
	collateral := terms.Collateral.Mul(sizeCurrency).Mul(durationCurrency)

	txnID, err = h.wallet.RegisterTransaction(txn)
	if err != nil {
		return
	}
	if collateral.Cmp(consensus.NewCurrency64(0)) == 0 {
		return txn, txnID, nil
	}
	fundedTxn, err = h.wallet.FundTransaction(txnID, collateral)
	if err != nil {
		return
	}
	return
}

// NegotiateContract is an RPC that negotiates a file contract. If the
// negotiation is successful, the file is downloaded and the host begins
// submitting proofs of storage.
func (h *Host) NegotiateContract(conn modules.NetConn) (err error) {
	// Read the contract terms.
	var terms modules.ContractTerms
	err = conn.ReadObject(&terms, maxContractLen)
	if err != nil {
		return
	}

	// Consider the contract terms. If they are unnacceptable, return an error
	// describing why.
	h.mu.RLock()
	err = h.considerTerms(terms)
	h.mu.RUnlock()
	if err != nil {
		err = conn.WriteObject(err.Error())
		return
	}

	// terms are acceptable; allocate space for file
	h.mu.Lock()
	file, path, err := h.allocate(terms.FileSize)
	h.mu.Unlock()
	if err != nil {
		return
	}
	defer file.Close()

	// rollback everything if something goes wrong
	defer func() {
		if err != nil {
			h.deallocate(terms.FileSize, path)
		}
	}()

	// signal that we are ready to download file
	err = conn.WriteObject(modules.AcceptTermsResponse)
	if err != nil {
		return
	}

	// simultaneously download file and calculate its Merkle root.
	tee := io.TeeReader(
		// use a LimitedReader to ensure we don't read indefinitely
		io.LimitReader(conn, int64(terms.FileSize)),
		// each byte we read from tee will also be written to file
		file,
	)
	merkleRoot, err := crypto.ReaderMerkleRoot(tee)
	if err != nil {
		return
	}

	// Data has been sent, read in the unsigned transaction with the file
	// contract.
	var unsignedTxn consensus.Transaction
	err = conn.ReadObject(&unsignedTxn, maxContractLen)
	if err != nil {
		return
	}

	// Verify that the transaction matches the agreed upon terms, and that the
	// Merkle root in the file contract matches our independently calculated
	// Merkle root.
	err = verifyTransaction(unsignedTxn, terms, merkleRoot)
	if err != nil {
		err = errors.New("transaction does not satisfy terms: " + err.Error())
		return
	}

	// Add the collateral to the transaction, but do not sign the transaction.
	collateralTxn, txnID, err := h.addCollateral(unsignedTxn, terms)
	if err != nil {
		return
	}
	err = conn.WriteObject(collateralTxn)
	if err != nil {
		return
	}

	// Read in the renter-signed transaction and check that it matches the
	// previously accepted transaction.
	var signedTxn consensus.Transaction
	err = conn.ReadObject(&signedTxn, maxContractLen)
	if err != nil {
		return
	}
	if collateralTxn.ID() != signedTxn.ID() {
		err = errors.New("signed transaction does not match the transaction with collateral")
		return
	}

	// Add the signatures from the renter signed transaction, and then sign the
	// transaction, then submit the transaction.
	for _, sig := range signedTxn.Signatures {
		_, _, err = h.wallet.AddSignature(txnID, sig)
		if err != nil {
			return
		}
	}
	fullTxn, err := h.wallet.SignTransaction(txnID, true)
	if err != nil {
		return
	}
	err = h.tpool.AcceptTransaction(fullTxn)
	if err != nil {
		return
	}

	// Add this contract to the host's list of obligations.
	fcid := signedTxn.FileContractID(0)
	fc := signedTxn.FileContracts[0]
	proofHeight := fc.Expiration + StorageProofReorgDepth
	co := contractObligation{
		id:           fcid,
		fileContract: fc,
		path:         path,
	}
	h.mu.Lock()
	h.obligationsByHeight[proofHeight] = append(h.obligationsByHeight[proofHeight], co)
	h.obligationsByID[fcid] = co
	h.mu.Unlock()

	// TODO: we don't currently watch the blockchain to make sure that the
	// transaction actually gets into the blockchain.

	return
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package plugin

import (
	"bytes"
	"context"
	"fmt"
	"strings"
	"time"

	githubql "github.com/shurcooL/githubv4"
	"github.com/sirupsen/logrus"

	"k8s.io/test-infra/prow/config"
	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/labels"
	"k8s.io/test-infra/prow/pluginhelp"
	"k8s.io/test-infra/prow/plugins"
)

const (
	// PluginName is the name of this plugin
	PluginName         = labels.NeedsRebase
	needsRebaseMessage = "PR needs rebase."
)

var sleep = time.Sleep

type githubClient interface {
	GetIssueLabels(org, repo string, number int) ([]github.Label, error)
	CreateComment(org, repo string, number int, comment string) error
	BotUserChecker() (func(candidate string) bool, error)
	AddLabel(org, repo string, number int, label string) error
	RemoveLabel(org, repo string, number int, label string) error
	IsMergeable(org, repo string, number int, sha string) (bool, error)
	DeleteStaleComments(org, repo string, number int, comments []github.IssueComment, isStale func(github.IssueComment) bool) error
	Query(context.Context, interface{}, map[string]interface{}) error
	GetPullRequest(org, repo string, number int) (*github.PullRequest, error)
}

// HelpProvider constructs the PluginHelp for this plugin that takes into account enabled repositories.
// HelpProvider defines the type for function that construct the PluginHelp for plugins.
func HelpProvider(_ []config.OrgRepo) (*pluginhelp.PluginHelp, error) {
	return &pluginhelp.PluginHelp{
			Description: `The needs-rebase plugin manages the '` + labels.NeedsRebase + `' label by removing it from Pull Requests that are mergeable and adding it to those which are not.
The plugin reacts to commit changes on PRs in addition to periodically scanning all open PRs for any changes to mergeability that could have resulted from changes in other PRs.`,
		},
		nil
}

// HandlePullRequestEvent handles a GitHub pull request event and adds or removes a
// "needs-rebase" label based on whether the GitHub api considers the PR mergeable
func HandlePullRequestEvent(log *logrus.Entry, ghc githubClient, pre *github.PullRequestEvent) error {
	if pre.Action != github.PullRequestActionOpened && pre.Action != github.PullRequestActionSynchronize && pre.Action != github.PullRequestActionReopened {
		return nil
	}

	return handle(log, ghc, &pre.PullRequest)
}

// HandleIssueCommentEvent handles a GitHub issue comment event and adds or removes a
// "needs-rebase" label if the issue is a PR based on whether the GitHub api considers
// the PR mergeable
func HandleIssueCommentEvent(log *logrus.Entry, ghc githubClient, ice *github.IssueCommentEvent) error {
	if !ice.Issue.IsPullRequest() {
		return nil
	}
	pr, err := ghc.GetPullRequest(ice.Repo.Owner.Login, ice.Repo.Name, ice.Issue.Number)
	if err != nil {
		return err
	}

	return handle(log, ghc, pr)
}

// handle handles a GitHub PR to determine if the "needs-rebase"
// label needs to be added or removed. It depends on GitHub mergeability check
// to decide the need for a rebase.
func handle(log *logrus.Entry, ghc githubClient, pr *github.PullRequest) error {
	if pr.Merged {
		return nil
	}
	// Before checking mergeability wait a few seconds to give github a chance to calculate it.
	// This initial delay prevents us from always wasting the first API token.
	sleep(time.Second * 5)

	org := pr.Base.Repo.Owner.Login
	repo := pr.Base.Repo.Name
	number := pr.Number
	sha := pr.Head.SHA
	*log = *log.WithFields(logrus.Fields{
		github.OrgLogField:  org,
		github.RepoLogField: repo,
		github.PrLogField:   number,
		"head-sha":          sha,
	})

	mergeable, err := ghc.IsMergeable(org, repo, number, sha)
	if err != nil {
		return err
	}
	issueLabels, err := ghc.GetIssueLabels(org, repo, number)
	if err != nil {
		return err
	}
	hasLabel := github.HasLabel(labels.NeedsRebase, issueLabels)

	return takeAction(log, ghc, org, repo, number, pr.User.Login, hasLabel, mergeable)
}

// HandleAll checks all orgs and repos that enabled this plugin for open PRs to
// determine if the "needs-rebase" label needs to be added or removed. It
// depends on GitHub's mergeability check to decide the need for a rebase.
func HandleAll(log *logrus.Entry, ghc githubClient, config *plugins.Configuration) error {
	log.Info("Checking all PRs.")
	orgs, repos := config.EnabledReposForExternalPlugin(PluginName)
	if len(orgs) == 0 && len(repos) == 0 {
		log.Warnf("No repos have been configured for the %s plugin", PluginName)
		return nil
	}
	var buf bytes.Buffer
	fmt.Fprint(&buf, "archived:false is:pr is:open")
	for _, org := range orgs {
		fmt.Fprintf(&buf, " org:\"%s\"", org)
	}
	for _, repo := range repos {
		fmt.Fprintf(&buf, " repo:\"%s\"", repo)
	}
	prs, err := search(context.Background(), log, ghc, buf.String())
	if err != nil {
		return err
	}
	log.Infof("Considering %d PRs.", len(prs))

	for _, pr := range prs {
		// Skip PRs that are calculating mergeability. They will be updated by event or next loop.
		if pr.Mergeable == githubql.MergeableStateUnknown {
			continue
		}
		org := string(pr.Repository.Owner.Login)
		repo := string(pr.Repository.Name)
		num := int(pr.Number)
		l := log.WithFields(logrus.Fields{
			"org":  org,
			"repo": repo,
			"pr":   num,
		})
		hasLabel := false
		for _, label := range pr.Labels.Nodes {
			if label.Name == labels.NeedsRebase {
				hasLabel = true
				break
			}
		}
		err := takeAction(
			l,
			ghc,
			org,
			repo,
			num,
			string(pr.Author.Login),
			hasLabel,
			pr.Mergeable == githubql.MergeableStateMergeable,
		)
		if err != nil {
			l.WithError(err).Error("Error handling PR.")
		}
	}
	return nil
}

// takeAction adds or removes the "needs-rebase" label based on the current
// state of the PR (hasLabel and mergeable). It also handles adding and
// removing GitHub comments notifying the PR author that a rebase is needed.
func takeAction(log *logrus.Entry, ghc githubClient, org, repo string, num int, author string, hasLabel, mergeable bool) error {
	if !mergeable && !hasLabel {
		if err := ghc.AddLabel(org, repo, num, labels.NeedsRebase); err != nil {
			log.WithError(err).Errorf("Failed to add %q label.", labels.NeedsRebase)
		}
		msg := plugins.FormatSimpleResponse(author, needsRebaseMessage)
		return ghc.CreateComment(org, repo, num, msg)
	} else if mergeable && hasLabel {
		// remove label and prune comment
		if err := ghc.RemoveLabel(org, repo, num, labels.NeedsRebase); err != nil {
			log.WithError(err).Errorf("Failed to remove %q label.", labels.NeedsRebase)
		}
		botUserChecker, err := ghc.BotUserChecker()
		if err != nil {
			return err
		}
		return ghc.DeleteStaleComments(org, repo, num, nil, shouldPrune(botUserChecker))
	}
	return nil
}

func shouldPrune(isBot func(string) bool) func(github.IssueComment) bool {
	return func(ic github.IssueComment) bool {
		return isBot(ic.User.Login) &&
			strings.Contains(ic.Body, needsRebaseMessage)
	}
}

func search(ctx context.Context, log *logrus.Entry, ghc githubClient, q string) ([]pullRequest, error) {
	var ret []pullRequest
	vars := map[string]interface{}{
		"query":        githubql.String(q),
		"searchCursor": (*githubql.String)(nil),
	}
	var totalCost int
	var remaining int
	for {
		sq := searchQuery{}
		if err := ghc.Query(ctx, &sq, vars); err != nil {
			return nil, err
		}
		totalCost += int(sq.RateLimit.Cost)
		remaining = int(sq.RateLimit.Remaining)
		for _, n := range sq.Search.Nodes {
			ret = append(ret, n.PullRequest)
		}
		if !sq.Search.PageInfo.HasNextPage {
			break
		}
		vars["searchCursor"] = githubql.NewString(sq.Search.PageInfo.EndCursor)
	}
	log.Infof("Search for query \"%s\" cost %d point(s). %d remaining.", q, totalCost, remaining)
	return ret, nil
}

// See: https://developer.github.com/v4/object/pullrequest/.
type pullRequest struct {
	Number githubql.Int
	Author struct {
		Login githubql.String
	}
	Repository struct {
		Name  githubql.String
		Owner struct {
			Login githubql.String
		}
	}
	Labels struct {
		Nodes []struct {
			Name githubql.String
		}
	} `graphql:"labels(first:100)"`
	Mergeable githubql.MergeableState
}

// See: https://developer.github.com/v4/query/.
type searchQuery struct {
	RateLimit struct {
		Cost      githubql.Int
		Remaining githubql.Int
	}
	Search struct {
		PageInfo struct {
			HasNextPage githubql.Boolean
			EndCursor   githubql.String
		}
		Nodes []struct {
			PullRequest pullRequest `graphql:"... on PullRequest"`
		}
	} `graphql:"search(type: ISSUE, first: 100, after: $searchCursor, query: $query)"`
}
<commit_msg>Needs rebase: Improve logs to be able to figure out if and what its doing<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package plugin

import (
	"bytes"
	"context"
	"fmt"
	"strings"
	"time"

	githubql "github.com/shurcooL/githubv4"
	"github.com/sirupsen/logrus"

	"k8s.io/test-infra/prow/config"
	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/labels"
	"k8s.io/test-infra/prow/pluginhelp"
	"k8s.io/test-infra/prow/plugins"
)

const (
	// PluginName is the name of this plugin
	PluginName         = labels.NeedsRebase
	needsRebaseMessage = "PR needs rebase."
)

var sleep = time.Sleep

type githubClient interface {
	GetIssueLabels(org, repo string, number int) ([]github.Label, error)
	CreateComment(org, repo string, number int, comment string) error
	BotUserChecker() (func(candidate string) bool, error)
	AddLabel(org, repo string, number int, label string) error
	RemoveLabel(org, repo string, number int, label string) error
	IsMergeable(org, repo string, number int, sha string) (bool, error)
	DeleteStaleComments(org, repo string, number int, comments []github.IssueComment, isStale func(github.IssueComment) bool) error
	Query(context.Context, interface{}, map[string]interface{}) error
	GetPullRequest(org, repo string, number int) (*github.PullRequest, error)
}

// HelpProvider constructs the PluginHelp for this plugin that takes into account enabled repositories.
// HelpProvider defines the type for function that construct the PluginHelp for plugins.
func HelpProvider(_ []config.OrgRepo) (*pluginhelp.PluginHelp, error) {
	return &pluginhelp.PluginHelp{
			Description: `The needs-rebase plugin manages the '` + labels.NeedsRebase + `' label by removing it from Pull Requests that are mergeable and adding it to those which are not.
The plugin reacts to commit changes on PRs in addition to periodically scanning all open PRs for any changes to mergeability that could have resulted from changes in other PRs.`,
		},
		nil
}

// HandlePullRequestEvent handles a GitHub pull request event and adds or removes a
// "needs-rebase" label based on whether the GitHub api considers the PR mergeable
func HandlePullRequestEvent(log *logrus.Entry, ghc githubClient, pre *github.PullRequestEvent) error {
	if pre.Action != github.PullRequestActionOpened && pre.Action != github.PullRequestActionSynchronize && pre.Action != github.PullRequestActionReopened {
		return nil
	}

	return handle(log, ghc, &pre.PullRequest)
}

// HandleIssueCommentEvent handles a GitHub issue comment event and adds or removes a
// "needs-rebase" label if the issue is a PR based on whether the GitHub api considers
// the PR mergeable
func HandleIssueCommentEvent(log *logrus.Entry, ghc githubClient, ice *github.IssueCommentEvent) error {
	if !ice.Issue.IsPullRequest() {
		return nil
	}
	pr, err := ghc.GetPullRequest(ice.Repo.Owner.Login, ice.Repo.Name, ice.Issue.Number)
	if err != nil {
		return err
	}

	return handle(log, ghc, pr)
}

// handle handles a GitHub PR to determine if the "needs-rebase"
// label needs to be added or removed. It depends on GitHub mergeability check
// to decide the need for a rebase.
func handle(log *logrus.Entry, ghc githubClient, pr *github.PullRequest) error {
	if pr.Merged {
		return nil
	}
	// Before checking mergeability wait a few seconds to give github a chance to calculate it.
	// This initial delay prevents us from always wasting the first API token.
	sleep(time.Second * 5)

	org := pr.Base.Repo.Owner.Login
	repo := pr.Base.Repo.Name
	number := pr.Number
	sha := pr.Head.SHA
	*log = *log.WithFields(logrus.Fields{
		github.OrgLogField:  org,
		github.RepoLogField: repo,
		github.PrLogField:   number,
		"head-sha":          sha,
	})

	mergeable, err := ghc.IsMergeable(org, repo, number, sha)
	if err != nil {
		return err
	}
	issueLabels, err := ghc.GetIssueLabels(org, repo, number)
	if err != nil {
		return err
	}
	hasLabel := github.HasLabel(labels.NeedsRebase, issueLabels)

	return takeAction(log, ghc, org, repo, number, pr.User.Login, hasLabel, mergeable)
}

// HandleAll checks all orgs and repos that enabled this plugin for open PRs to
// determine if the "needs-rebase" label needs to be added or removed. It
// depends on GitHub's mergeability check to decide the need for a rebase.
func HandleAll(log *logrus.Entry, ghc githubClient, config *plugins.Configuration) error {
	log.Info("Checking all PRs.")
	orgs, repos := config.EnabledReposForExternalPlugin(PluginName)
	if len(orgs) == 0 && len(repos) == 0 {
		log.Warnf("No repos have been configured for the %s plugin", PluginName)
		return nil
	}
	var buf bytes.Buffer
	fmt.Fprint(&buf, "archived:false is:pr is:open")
	for _, org := range orgs {
		fmt.Fprintf(&buf, " org:\"%s\"", org)
	}
	for _, repo := range repos {
		fmt.Fprintf(&buf, " repo:\"%s\"", repo)
	}
	prs, err := search(context.Background(), log, ghc, buf.String())
	if err != nil {
		return err
	}
	log.WithField("prs_found_count", len(prs)).Debug("Processing all found PRs")

	for _, pr := range prs {
		// Skip PRs that are calculating mergeability. They will be updated by event or next loop.
		if pr.Mergeable == githubql.MergeableStateUnknown {
			continue
		}
		org := string(pr.Repository.Owner.Login)
		repo := string(pr.Repository.Name)
		num := int(pr.Number)
		var hasLabel bool
		for _, label := range pr.Labels.Nodes {
			if label.Name == labels.NeedsRebase {
				hasLabel = true
				break
			}
		}
		l := log.WithFields(logrus.Fields{
			"org":       org,
			"repo":      repo,
			"pr":        num,
			"mergeable": pr.Mergeable,
			"has_label": hasLabel,
		})
		l.Debug("Processing PR")
		err := takeAction(
			l,
			ghc,
			org,
			repo,
			num,
			string(pr.Author.Login),
			hasLabel,
			pr.Mergeable == githubql.MergeableStateMergeable,
		)
		if err != nil {
			l.WithError(err).Error("Error handling PR.")
		}
	}
	return nil
}

// takeAction adds or removes the "needs-rebase" label based on the current
// state of the PR (hasLabel and mergeable). It also handles adding and
// removing GitHub comments notifying the PR author that a rebase is needed.
func takeAction(log *logrus.Entry, ghc githubClient, org, repo string, num int, author string, hasLabel, mergeable bool) error {
	if !mergeable && !hasLabel {
		if err := ghc.AddLabel(org, repo, num, labels.NeedsRebase); err != nil {
			log.WithError(err).Errorf("Failed to add %q label.", labels.NeedsRebase)
		}
		msg := plugins.FormatSimpleResponse(author, needsRebaseMessage)
		return ghc.CreateComment(org, repo, num, msg)
	} else if mergeable && hasLabel {
		// remove label and prune comment
		if err := ghc.RemoveLabel(org, repo, num, labels.NeedsRebase); err != nil {
			log.WithError(err).Errorf("Failed to remove %q label.", labels.NeedsRebase)
		}
		botUserChecker, err := ghc.BotUserChecker()
		if err != nil {
			return err
		}
		return ghc.DeleteStaleComments(org, repo, num, nil, shouldPrune(botUserChecker))
	}
	return nil
}

func shouldPrune(isBot func(string) bool) func(github.IssueComment) bool {
	return func(ic github.IssueComment) bool {
		return isBot(ic.User.Login) &&
			strings.Contains(ic.Body, needsRebaseMessage)
	}
}

func search(ctx context.Context, log *logrus.Entry, ghc githubClient, q string) ([]pullRequest, error) {
	var ret []pullRequest
	vars := map[string]interface{}{
		"query":        githubql.String(q),
		"searchCursor": (*githubql.String)(nil),
	}
	var totalCost int
	var remaining int
	for {
		sq := searchQuery{}
		if err := ghc.Query(ctx, &sq, vars); err != nil {
			return nil, err
		}
		totalCost += int(sq.RateLimit.Cost)
		remaining = int(sq.RateLimit.Remaining)
		for _, n := range sq.Search.Nodes {
			ret = append(ret, n.PullRequest)
		}
		if !sq.Search.PageInfo.HasNextPage {
			break
		}
		vars["searchCursor"] = githubql.NewString(sq.Search.PageInfo.EndCursor)
	}
	log.Infof("Search for query \"%s\" cost %d point(s). %d remaining.", q, totalCost, remaining)
	return ret, nil
}

// See: https://developer.github.com/v4/object/pullrequest/.
type pullRequest struct {
	Number githubql.Int
	Author struct {
		Login githubql.String
	}
	Repository struct {
		Name  githubql.String
		Owner struct {
			Login githubql.String
		}
	}
	Labels struct {
		Nodes []struct {
			Name githubql.String
		}
	} `graphql:"labels(first:100)"`
	Mergeable githubql.MergeableState
}

// See: https://developer.github.com/v4/query/.
type searchQuery struct {
	RateLimit struct {
		Cost      githubql.Int
		Remaining githubql.Int
	}
	Search struct {
		PageInfo struct {
			HasNextPage githubql.Boolean
			EndCursor   githubql.String
		}
		Nodes []struct {
			PullRequest pullRequest `graphql:"... on PullRequest"`
		}
	} `graphql:"search(type: ISSUE, first: 100, after: $searchCursor, query: $query)"`
}
<|endoftext|>


<commit_before>// Copyright (c) 2016, 2017 Evgeny Badin

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ui

import (
	"encoding/json"
	"fmt"
	"sort"
	"strconv"
	"time"

	runewidth "github.com/mattn/go-runewidth"

	"github.com/budkin/jam/music"
)

var (
	defDur    = time.Duration(0) // this and below are used for when nothing is playing
	defTrack  = &music.BTrack{}
	defArtist = ""
)

func (app *App) updateUI() {
	app.Screen.Clear()
	fill(app.Screen, app.Width/3, 1, 1, app.Height-3, '│', dfStyle)
	app.printHeader()
	if len(app.Artists) > 0 {
		app.printArtists(app.Status.ScrOffset[false], app.Height+app.Status.ScrOffset[false]-3)
		app.printSongs(app.Status.ScrOffset[true], app.Height+app.Status.ScrOffset[true]-3)
		for app.Status.CurPos[false] > app.Height-3 {
			app.Status.CurPos[false]--
		}
		for app.Status.CurPos[true] > app.Height-3 {
			app.Status.CurPos[true] = 2
			app.Status.NumTrack = 0
			app.Status.NumAlbum[true] = 0
		}
		app.hlEntry()
	}
	app.printStatus()
	app.printBar(defDur, defTrack, defArtist)
}

func (app *App) hlEntry() {
	i := app.Status.CurPos[false] - 1 + app.Status.ScrOffset[false]
	if app.Artists[i] != "" {
		printSingleItem(app.Screen, 0, app.Status.CurPos[false], hlStyle, app.Artists[i], 1, true, app.Width)
	} else {
		printSingleItem(app.Screen, 0, app.Status.CurPos[false], hlStyle, app.Albums[app.Artists[i-app.numAlb(i)]][app.numAlb(i)-1], 3, true, app.Width)

	}

	if app.Status.InTracks {
		song := app.Songs[app.Albums[app.Artists[i-app.numAlb(i)]][app.Status.NumAlbum[true]]][app.Status.NumTrack]
		js := new(music.BTrack)
		json.Unmarshal([]byte(song), js)
		printSingleItem(app.Screen, app.Width/3+2, app.Status.CurPos[app.Status.InTracks], hlStyle, makeSongLine(js, app.Width), 0, false, app.Width)
	}
}
func (app *App) printHeader() {
	fill(app.Screen, 0, 0, app.Width, 1, ' ', hlStyle)
	print(app.Screen, 1, 0, hlStyle, "Artist / Album")
	print(app.Screen, app.Width/3+2, 0, hlStyle, "Track")
	print(app.Screen, app.Width-8, 0, hlStyle, "Library")
	fill(app.Screen, 0, app.Height-2, app.Width, 1, ' ', hlStyle)
}

func (app *App) printStatus() {
	if app.Status.InSearch {
		app.Screen.SetContent(0, app.Height-1, '/', nil, dfStyle)
		for i, r := range app.Status.Query {
			app.Screen.SetContent(i+1, app.Height-1, r, nil, dfStyle)
		}
	}
}

func (app *App) printBar(dur time.Duration, track *music.BTrack, artist string) {
	strdur := ""
	str := fmt.Sprintf(" %02v:%02v %v - %v ", int(dur.Minutes()), int(dur.Seconds())%60, artist, track.Title)
	lenstr := 0
	for _, r := range str {
		lenstr += runewidth.RuneWidth(r)
	}
	print(app.Screen, 0, app.Height-2, barStyle, str)
	leng := app.Width - lenstr
	durat, _ := strconv.Atoi(track.DurationMillis)
	dura := time.Duration(durat) * time.Millisecond
	for i := 0.0; i < float64(leng)/dura.Seconds()*dur.Seconds(); i += 1.0 {
		strdur += "—"
	}
	print(app.Screen, lenstr, app.Height-2, barStyle, strdur)
	app.Screen.Show()
}

func (app *App) printArtists(beg, end int) {
	for len(app.Artists) < end {
		end--
	}
	j := 1
	for beg < end {
		if app.Artists[beg] != "" {
			printSingleItem(app.Screen, 0, j, dfStyle, app.Artists[beg], 1, true, app.Width)

			j++
			beg++
		} else {
			printSingleItem(app.Screen, 0, j, dfStyle, app.Albums[app.Artists[beg-app.numAlb(beg)]][app.numAlb(beg)-1], 3, true, app.Width)
			j++
			beg++
		}

	}
}

func (app *App) printAlbum(y int, alb string) {
	makeAlbumLine(&alb, app.Width)
	print(app.Screen, app.Width/3+2, y, alStyle, alb)

}

func (app *App) printSongs(beg, end int) {
	// queue = [][]*music.BTrack{}
	app.populateSongs()
	i, k := 0, 1
	if app.Status.NumAlbum[false] == -1 {
		j := app.numSongs()
		if j < end {
			end = j
		}
		keys := []string{}
		for a := range app.Songs {
			keys = append(keys, a)
		}
		sort.Strings(keys)
		app.LastAlbum = keys[len(keys)-1]
		for _, key := range keys {
			que := []*music.BTrack{}
			if i >= beg && i < end {
				app.printAlbum(k, key)
				k++
			}
			i++
			for _, song := range app.Songs[key] {
				js := new(music.BTrack)
				json.Unmarshal([]byte(song), js)
				if i >= beg && i < end {
					printSingleItem(app.Screen, app.Width/3+2, k, dfStyle, makeSongLine(js, app.Width), 0, false, app.Width)
					que = append(que, js)
					k++
				}
				i++
			}
			app.Status.Queue = append(app.Status.Queue, que)

		}
	} else {
		que := []*music.BTrack{}
		j := app.Status.CurPos[false] - 1 + app.Status.ScrOffset[false]
		if len(app.Songs[app.Albums[app.Artists[j-app.numAlb(j)]][app.Status.NumAlbum[false]]]) < end {
			end = len(app.Songs[app.Albums[app.Artists[j-app.numAlb(j)]][app.Status.NumAlbum[false]]])
		}

		for _, song := range app.Songs[app.Albums[app.Artists[j-app.numAlb(j)]][app.Status.NumAlbum[false]]] {
			js := new(music.BTrack)
			json.Unmarshal([]byte(song), js)
			if i >= beg {
				printSingleItem(app.Screen, app.Width/3+2, k, dfStyle, makeSongLine(js, app.Width), 0, false, app.Width)
				que = append(que, js)
				k++
			}
			i++
		}
		for l := app.numAlb(j); l > 1; l-- {
			app.Status.Queue = append(app.Status.Queue, []*music.BTrack{})
		}
		app.Status.Queue = append(app.Status.Queue, que)
	}
}
<commit_msg>Reinitialize Queue on every movement<commit_after>// Copyright (c) 2016, 2017 Evgeny Badin

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ui

import (
	"encoding/json"
	"fmt"
	"sort"
	"strconv"
	"time"

	runewidth "github.com/mattn/go-runewidth"

	"github.com/budkin/jam/music"
)

var (
	defDur    = time.Duration(0) // this and below are used for when nothing is playing
	defTrack  = &music.BTrack{}
	defArtist = ""
)

func (app *App) updateUI() {
	app.Screen.Clear()
	fill(app.Screen, app.Width/3, 1, 1, app.Height-3, '│', dfStyle)
	app.printHeader()
	if len(app.Artists) > 0 {
		app.printArtists(app.Status.ScrOffset[false], app.Height+app.Status.ScrOffset[false]-3)
		app.printSongs(app.Status.ScrOffset[true], app.Height+app.Status.ScrOffset[true]-3)
		for app.Status.CurPos[false] > app.Height-3 {
			app.Status.CurPos[false]--
		}
		for app.Status.CurPos[true] > app.Height-3 {
			app.Status.CurPos[true] = 2
			app.Status.NumTrack = 0
			app.Status.NumAlbum[true] = 0
		}
		app.hlEntry()
	}
	app.printStatus()
	app.printBar(defDur, defTrack, defArtist)
}

func (app *App) hlEntry() {
	i := app.Status.CurPos[false] - 1 + app.Status.ScrOffset[false]
	if app.Artists[i] != "" {
		printSingleItem(app.Screen, 0, app.Status.CurPos[false], hlStyle, app.Artists[i], 1, true, app.Width)
	} else {
		printSingleItem(app.Screen, 0, app.Status.CurPos[false], hlStyle, app.Albums[app.Artists[i-app.numAlb(i)]][app.numAlb(i)-1], 3, true, app.Width)

	}

	if app.Status.InTracks {
		song := app.Songs[app.Albums[app.Artists[i-app.numAlb(i)]][app.Status.NumAlbum[true]]][app.Status.NumTrack]
		js := new(music.BTrack)
		json.Unmarshal([]byte(song), js)
		printSingleItem(app.Screen, app.Width/3+2, app.Status.CurPos[app.Status.InTracks], hlStyle, makeSongLine(js, app.Width), 0, false, app.Width)
	}
}
func (app *App) printHeader() {
	fill(app.Screen, 0, 0, app.Width, 1, ' ', hlStyle)
	print(app.Screen, 1, 0, hlStyle, "Artist / Album")
	print(app.Screen, app.Width/3+2, 0, hlStyle, "Track")
	print(app.Screen, app.Width-8, 0, hlStyle, "Library")
	fill(app.Screen, 0, app.Height-2, app.Width, 1, ' ', hlStyle)
}

func (app *App) printStatus() {
	if app.Status.InSearch {
		app.Screen.SetContent(0, app.Height-1, '/', nil, dfStyle)
		for i, r := range app.Status.Query {
			app.Screen.SetContent(i+1, app.Height-1, r, nil, dfStyle)
		}
	}
}

func (app *App) printBar(dur time.Duration, track *music.BTrack, artist string) {
	strdur := ""
	str := fmt.Sprintf(" %02v:%02v %v - %v ", int(dur.Minutes()), int(dur.Seconds())%60, artist, track.Title)
	lenstr := 0
	for _, r := range str {
		lenstr += runewidth.RuneWidth(r)
	}
	print(app.Screen, 0, app.Height-2, barStyle, str)
	leng := app.Width - lenstr
	durat, _ := strconv.Atoi(track.DurationMillis)
	dura := time.Duration(durat) * time.Millisecond
	for i := 0.0; i < float64(leng)/dura.Seconds()*dur.Seconds(); i += 1.0 {
		strdur += "—"
	}
	print(app.Screen, lenstr, app.Height-2, barStyle, strdur)
	app.Screen.Show()
}

func (app *App) printArtists(beg, end int) {
	for len(app.Artists) < end {
		end--
	}
	j := 1
	for beg < end {
		if app.Artists[beg] != "" {
			printSingleItem(app.Screen, 0, j, dfStyle, app.Artists[beg], 1, true, app.Width)

			j++
			beg++
		} else {
			printSingleItem(app.Screen, 0, j, dfStyle, app.Albums[app.Artists[beg-app.numAlb(beg)]][app.numAlb(beg)-1], 3, true, app.Width)
			j++
			beg++
		}

	}
}

func (app *App) printAlbum(y int, alb string) {
	makeAlbumLine(&alb, app.Width)
	print(app.Screen, app.Width/3+2, y, alStyle, alb)

}

func (app *App) printSongs(beg, end int) {
	// queue = [][]*music.BTrack{}
	app.Status.Queue = [][]*music.BTrack{}
	app.populateSongs()
	i, k := 0, 1
	if app.Status.NumAlbum[false] == -1 {
		j := app.numSongs()
		if j < end {
			end = j
		}
		keys := []string{}
		for a := range app.Songs {
			keys = append(keys, a)
		}
		sort.Strings(keys)
		app.LastAlbum = keys[len(keys)-1]
		for _, key := range keys {
			que := []*music.BTrack{}
			if i >= beg && i < end {
				app.printAlbum(k, key)
				k++
			}
			i++
			for _, song := range app.Songs[key] {
				js := new(music.BTrack)
				json.Unmarshal([]byte(song), js)
				if i >= beg && i < end {
					printSingleItem(app.Screen, app.Width/3+2, k, dfStyle, makeSongLine(js, app.Width), 0, false, app.Width)
					que = append(que, js)
					k++
				}
				i++
			}
			app.Status.Queue = append(app.Status.Queue, que)

		}
	} else {
		que := []*music.BTrack{}
		j := app.Status.CurPos[false] - 1 + app.Status.ScrOffset[false]
		if len(app.Songs[app.Albums[app.Artists[j-app.numAlb(j)]][app.Status.NumAlbum[false]]]) < end {
			end = len(app.Songs[app.Albums[app.Artists[j-app.numAlb(j)]][app.Status.NumAlbum[false]]])
		}

		for _, song := range app.Songs[app.Albums[app.Artists[j-app.numAlb(j)]][app.Status.NumAlbum[false]]] {
			js := new(music.BTrack)
			json.Unmarshal([]byte(song), js)
			if i >= beg {
				printSingleItem(app.Screen, app.Width/3+2, k, dfStyle, makeSongLine(js, app.Width), 0, false, app.Width)
				que = append(que, js)
				k++
			}
			i++
		}
		for l := app.numAlb(j); l > 1; l-- {
			app.Status.Queue = append(app.Status.Queue, []*music.BTrack{})
		}
		app.Status.Queue = append(app.Status.Queue, que)
	}
}
<|endoftext|>


<commit_before>package iter

import (
	"fmt"
	"sync"
)

type Uint32 struct {
	Slice []uint32
	err error
	index int
	mutex sync.RWMutex
	closed bool
	datum uint32
}

func (receiver *Uint32) Close() error {
	if nil == receiver {
		return errNilReceiver
	}

	receiver.mutex.Lock()
	defer receiver.mutex.Unlock()

	receiver.closed = true

	return nil
}

// Decode stores the next datum in the data represented by the empty interface value `x`.
// If `x` is nil, the value will be discarded.
// Otherwise, the value underlying `x` must be a pointer to the correct type for the next datum.
func (receiver *Uint32) Decode(x interface{}) error {
	if nil == receiver {
		return errNilReceiver
	}

	receiver.mutex.RLock()
	defer receiver.mutex.RUnlock()

	switch p := x.(type) {
	case *uint32:
		if nil == p {
			return nil
		}

		*p = receiver.datum
	case *interface{}:
		if nil == p {
			return nil
		}

		*p = receiver.datum
	default:
		return &internalBadTypeComplainer{fmt.Sprintf("%T", p)}
	}

	return nil
}

// Err returns the error, if an error was encountered during an iteration.
// If no error was encountered during an iteration, then Err returns nil.
func (receiver *Uint32) Err() error {
	if nil == receiver {
		return errNilReceiver
	}

	receiver.mutex.RLock()
	defer receiver.mutex.RUnlock()

	return receiver.err
}

// Next prepares the next datum for reading via the Decode method.
//
// If there is indeed a next datum, Next returns true.
//
// And a call to the Decode method will obtain that next datum.
//
// If there is no next datum, Next returns false.
//
// If an error was encountered while calling Next, Next will also return false.
//
// And a call to the Err method will obtain that error.
//
// Err should be called to distinguish between the two cases where the Next
// method can return false: 'no next datum' and 'an error was encountered'.
func (receiver *Uint32) Next() bool {
	if nil == receiver {
		return false
	}

	receiver.mutex.Lock()
	defer receiver.mutex.Unlock()

	if nil != receiver.err {
		return false
	}

	if receiver.closed {
		return false
	}

	slice := receiver.Slice
	if nil == slice  {
		return false
	}

	index := receiver.index
	if len(slice) <= index {
		return false
	}

	receiver.datum = slice[index]
	receiver.index++

	return true
}
<commit_msg>made it so iter.Uint32 can also Decode into *uint64 too.<commit_after>package iter

import (
	"fmt"
	"sync"
)

type Uint32 struct {
	Slice []uint32
	err error
	index int
	mutex sync.RWMutex
	closed bool
	datum uint32
}

func (receiver *Uint32) Close() error {
	if nil == receiver {
		return errNilReceiver
	}

	receiver.mutex.Lock()
	defer receiver.mutex.Unlock()

	receiver.closed = true

	return nil
}

// Decode stores the next datum in the data represented by the empty interface value `x`.
// If `x` is nil, the value will be discarded.
// Otherwise, the value underlying `x` must be a pointer to the correct type for the next datum.
func (receiver *Uint32) Decode(x interface{}) error {
	if nil == receiver {
		return errNilReceiver
	}

	receiver.mutex.RLock()
	defer receiver.mutex.RUnlock()

	switch p := x.(type) {
	case *uint32:
		if nil == p {
			return nil
		}

		*p = receiver.datum
	case *uint64:
		if nil == p {
			return nil
		}

		*p = uint64(receiver.datum)
	case *interface{}:
		if nil == p {
			return nil
		}

		*p = receiver.datum
	default:
		return &internalBadTypeComplainer{fmt.Sprintf("%T", p)}
	}

	return nil
}

// Err returns the error, if an error was encountered during an iteration.
// If no error was encountered during an iteration, then Err returns nil.
func (receiver *Uint32) Err() error {
	if nil == receiver {
		return errNilReceiver
	}

	receiver.mutex.RLock()
	defer receiver.mutex.RUnlock()

	return receiver.err
}

// Next prepares the next datum for reading via the Decode method.
//
// If there is indeed a next datum, Next returns true.
//
// And a call to the Decode method will obtain that next datum.
//
// If there is no next datum, Next returns false.
//
// If an error was encountered while calling Next, Next will also return false.
//
// And a call to the Err method will obtain that error.
//
// Err should be called to distinguish between the two cases where the Next
// method can return false: 'no next datum' and 'an error was encountered'.
func (receiver *Uint32) Next() bool {
	if nil == receiver {
		return false
	}

	receiver.mutex.Lock()
	defer receiver.mutex.Unlock()

	if nil != receiver.err {
		return false
	}

	if receiver.closed {
		return false
	}

	slice := receiver.Slice
	if nil == slice  {
		return false
	}

	index := receiver.index
	if len(slice) <= index {
		return false
	}

	receiver.datum = slice[index]
	receiver.index++

	return true
}
<|endoftext|>


<commit_before>/*
Tests for the login/ endpoint.

Author:		Alastair Hughes
Contact:	<hobbitalastair at yandex dot com>
*/

package main

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

var loginUrl = url + "login"

var loginTests = []Test{
	Test{
		Name:   "login:Unauthorized",
		Method: "GET", URL: loginUrl, Status: http.StatusUnauthorized,
		SetAuth: setNilAuth,
	},
	Test{
		Name:   "login:Forbidden",
		Method: "GET", URL: loginUrl, Status: http.StatusForbidden,
	},
	Test{
		Name:   "login:Create",
		Method: "POST", URL: loginUrl, Status: http.StatusCreated,
	},
	Test{
		Name:   "login:Get",
		Method: "GET", URL: loginUrl, Status: http.StatusOK,
		CheckBody: checkNotManager,
	},
	Test{
		Name:   "login:CreateAgain",
		Method: "POST", URL: loginUrl, Status: http.StatusForbidden,
		SetAuth: setWrongPassword,
	},
}

type login struct {
	Manager bool
}

// setNilAuth does not set any auth.
func setNilAuth(r *http.Request) {
	return
}

// setWrongPassword provides an invalid password.
func setWrongPassword(r *http.Request) {
	r.SetBasicAuth(defaultUser, "some other password")
}

// checkNotManager checks that the manager flag is not set.
func checkNotManager(dec *json.Decoder) error {
	login := login{true}
	dec.Decode(&login)
	if login.Manager != false {
		return fmt.Errorf("Default login is a manager!")
	}
	return nil
}

// vim: sw=4 ts=4 noexpandtab
<commit_msg>Test for both is a manager and is not<commit_after>/*
Tests for the login/ endpoint.

Author:		Alastair Hughes
Contact:	<hobbitalastair at yandex dot com>
*/

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/mel-app/backend/src"
)

var loginUrl = url + "login"

var loginTests = []Test{
	Test{
		Name:   "login:Unauthorized",
		Method: "GET", URL: loginUrl, Status: http.StatusUnauthorized,
		SetAuth: setNilAuth,
	},
	Test{
		Name:   "login:Forbidden",
		Method: "GET", URL: loginUrl, Status: http.StatusForbidden,
	},
	Test{
		Name:   "login:Create",
		Method: "POST", URL: loginUrl, Status: http.StatusCreated,
	},
	Test{
		Name:   "login:Get",
		Method: "GET", URL: loginUrl, Status: http.StatusOK,
		CheckBody: checkNotManager,
	},
	Test{
		Name:   "login:CreateAgain",
		Method: "POST", URL: loginUrl, Status: http.StatusForbidden,
		SetAuth: setWrongPassword,
	},
	Test{
		Name:	"login:MakeManager",
		Method: "GET", URL: loginUrl, Status: http.StatusOK,
		Pre:	makeManager,
		CheckBody: checkManager,
	},
}

type login struct {
	Manager bool
}

// setNilAuth does not set any auth.
func setNilAuth(r *http.Request) {
	return
}

// setWrongPassword provides an invalid password.
func setWrongPassword(r *http.Request) {
	r.SetBasicAuth(defaultUser, "some other password")
}

// makeManager makes the default user a manager.
func makeManager(db *sql.DB) error {
	return backend.NewDB(db).SetIsManager(defaultUser, true)
}

// checkManager checks that the manager flag is set.
func checkManager(dec *json.Decoder) error {
	login := login{false}
	dec.Decode(&login)
	if login.Manager != true {
		return fmt.Errorf("Setting a manager did not work!")
	}
	return nil
}

// checkNotManager checks that the manager flag is not set.
func checkNotManager(dec *json.Decoder) error {
	login := login{true}
	dec.Decode(&login)
	if login.Manager != false {
		return fmt.Errorf("Default login is a manager!")
	}
	return nil
}

// vim: sw=4 ts=4 noexpandtab
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io/ioutil"
	"reflect"
	"sync"

	"github.com/golang/protobuf/proto"
	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
)

type serverReflectionServer struct {
	mu                sync.Mutex
	typeToNameMap     map[reflect.Type]string
	nameToTypeMap     map[string]reflect.Type
	typeToFileDescMap map[reflect.Type]*dpb.FileDescriptorProto
	filenameToDescMap map[string]*dpb.FileDescriptorProto
}

func newServerReflectionServer() *serverReflectionServer {
	return &serverReflectionServer{
		typeToNameMap:     make(map[reflect.Type]string),
		nameToTypeMap:     make(map[string]reflect.Type),
		typeToFileDescMap: make(map[reflect.Type]*dpb.FileDescriptorProto),
		filenameToDescMap: make(map[string]*dpb.FileDescriptorProto),
	}
}

type protoMessage interface {
	Descriptor() ([]byte, []int)
}

// TODO return an error rather than a bool
func (s *serverReflectionServer) fileDescForType(st reflect.Type) (*dpb.FileDescriptorProto, []int, error) {
	m, ok := reflect.Zero(reflect.PtrTo(st)).Interface().(protoMessage)
	if !ok {
		// TODO better print content.
		return nil, nil, fmt.Errorf("failed to create message from type: %v", st)
	}
	enc, idxs := m.Descriptor()

	// TODO Check cache first.
	// if fn, ok := s.typeToFilenameMap[st]; ok {
	// 	if fd, ok := s.filenameToDescMap[fn]; ok {
	// 		return fd, idxs, ok
	// 	}
	// }

	fd, err := s.decodeFileDesc(enc)
	if err != nil {
		return nil, nil, err
	}
	// TODO Cache missed, add to cache.
	// s.typeToFilenameMap[st] = fd.GetName()
	return fd, idxs, nil
}

func (s *serverReflectionServer) decodeFileDesc(enc []byte) (*dpb.FileDescriptorProto, error) {
	raw := decompress(enc)
	if raw == nil {
		return nil, fmt.Errorf("failed to decompress enc")
	}

	fd := new(dpb.FileDescriptorProto)
	if err := proto.Unmarshal(raw, fd); err != nil {
		return nil, fmt.Errorf("bad descriptor: %v", err)
	}
	// TODO If decodeFileDesc is called, it's the first time this file is seen.
	// Add it to cache.
	// s.filenameToDescMap[fd.GetName()] = fd
	return fd, nil
}

func decompress(b []byte) []byte {
	r, err := gzip.NewReader(bytes.NewReader(b))
	if err != nil {
		fmt.Printf("bad gzipped descriptor: %v\n", err)
		return nil
	}
	out, err := ioutil.ReadAll(r)
	if err != nil {
		fmt.Printf("bad gzipped descriptor: %v\n", err)
		return nil
	}
	return out
}

func (s *serverReflectionServer) typeForName(name string) (reflect.Type, error) {
	// TODO cache

	pt := proto.MessageType(name)
	if pt == nil {
		return nil, fmt.Errorf("unknown type: %q", name)
	}
	st := pt.Elem()
	// TODO cache
	// s.typeToNameMap[st] = name
	// s.nameToTypeMap[name] = st
	// fd, _, ok := s.fileDescForType(st)
	// if ok {
	// 	s.typeToFilenameMap[st] = fd.GetName()
	// }
	return st, nil
}

func (s *serverReflectionServer) nameForType(st reflect.Type) (string, error) {
	// TODO cache

	var name string
	fd, idxs, err := s.fileDescForType(st)
	if err != nil {
		return "", err
	}
	mt := fd.MessageType[idxs[0]]
	name = mt.GetName()
	for i := 1; i < len(idxs); i++ {
		mt = mt.NestedType[idxs[i]]
		name += "_" + mt.GetName()
	}
	if fd.Package != nil {
		name = *fd.Package + "." + name
	}
	return name, nil
}

func (s *serverReflectionServer) nameForPointer(i interface{}) (string, error) {
	return s.nameForType(reflect.TypeOf(i).Elem())
}

func (s *serverReflectionServer) filenameForType(st reflect.Type) (string, error) {
	fd, _, err := s.fileDescForType(st)
	if err != nil {
		return "", err
	}
	return fd.GetName(), nil
}

// TODO filenameForMethod and Service

func (s *serverReflectionServer) fileDescContainingExtension(st reflect.Type, ext int32) (*dpb.FileDescriptorProto, error) {
	m, ok := reflect.Zero(reflect.PtrTo(st)).Interface().(proto.Message)
	if !ok {
		return nil, fmt.Errorf("failed to create message from type: %v", st)
	}

	var extDesc *proto.ExtensionDesc
	for id, desc := range proto.RegisteredExtensions(m) {
		if id == ext {
			extDesc = desc
			break
		}
	}

	if extDesc == nil {
		return nil, fmt.Errorf("failed to find registered extension for extension number %v", ext)
	}

	extT := reflect.TypeOf(extDesc.ExtensionType).Elem()
	// TODO doesn't work if extT is simple types, like int32
	// TODO check cache
	fd, _, err := s.fileDescForType(extT)
	if err != nil {
		return nil, err
	}
	return fd, nil
}

// TODO filenameContainingExtension
// fd := fileDescContainingExtension()
// return fd.GetName()

func (s *serverReflectionServer) allExtensionNumbersForType(st reflect.Type) ([]int32, error) {
	m, ok := reflect.Zero(reflect.PtrTo(st)).Interface().(proto.Message)
	if !ok {
		return nil, fmt.Errorf("failed to create message from type: %v", st)
	}

	var out []int32
	for id := range proto.RegisteredExtensions(m) {
		out = append(out, id)
	}
	return out, nil
}
<commit_msg>Make use of cache maps<commit_after>package main

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io/ioutil"
	"reflect"
	"sync"

	"github.com/golang/protobuf/proto"
	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
)

type serverReflectionServer struct {
	// TODO mu is not used. Add lock() and unlock().
	mu                sync.Mutex
	typeToNameMap     map[reflect.Type]string
	nameToTypeMap     map[string]reflect.Type
	typeToFileDescMap map[reflect.Type]*dpb.FileDescriptorProto
	filenameToDescMap map[string]*dpb.FileDescriptorProto
}

func newServerReflectionServer() *serverReflectionServer {
	return &serverReflectionServer{
		typeToNameMap:     make(map[reflect.Type]string),
		nameToTypeMap:     make(map[string]reflect.Type),
		typeToFileDescMap: make(map[reflect.Type]*dpb.FileDescriptorProto),
		filenameToDescMap: make(map[string]*dpb.FileDescriptorProto),
	}
}

type protoMessage interface {
	Descriptor() ([]byte, []int)
}

func (s *serverReflectionServer) fileDescForType(st reflect.Type) (*dpb.FileDescriptorProto, []int, error) {
	// Indexes list is not stored in cache.
	// So this step is needed to get idxs.
	m, ok := reflect.Zero(reflect.PtrTo(st)).Interface().(protoMessage)
	if !ok {
		return nil, nil, fmt.Errorf("failed to create message from type: %v", st)
	}
	enc, idxs := m.Descriptor()

	// Check type to fileDesc cache.
	if fd, ok := s.typeToFileDescMap[st]; ok {
		return fd, idxs, nil
	}

	// Cache missed, try to decode.
	fd, err := s.decodeFileDesc(enc)
	if err != nil {
		return nil, nil, err
	}
	// Add to cache.
	s.typeToFileDescMap[st] = fd
	return fd, idxs, nil
}

func (s *serverReflectionServer) decodeFileDesc(enc []byte) (*dpb.FileDescriptorProto, error) {
	raw := decompress(enc)
	if raw == nil {
		return nil, fmt.Errorf("failed to decompress enc")
	}

	fd := new(dpb.FileDescriptorProto)
	if err := proto.Unmarshal(raw, fd); err != nil {
		return nil, fmt.Errorf("bad descriptor: %v", err)
	}
	// If decodeFileDesc is called, it's the first time this file is seen.
	// Add it to cache.
	s.filenameToDescMap[fd.GetName()] = fd
	return fd, nil
}

func decompress(b []byte) []byte {
	r, err := gzip.NewReader(bytes.NewReader(b))
	if err != nil {
		fmt.Printf("bad gzipped descriptor: %v\n", err)
		return nil
	}
	out, err := ioutil.ReadAll(r)
	if err != nil {
		fmt.Printf("bad gzipped descriptor: %v\n", err)
		return nil
	}
	return out
}

func (s *serverReflectionServer) typeForName(name string) (reflect.Type, error) {
	// Check cache first.
	if st, ok := s.nameToTypeMap[name]; ok {
		return st, nil
	}

	pt := proto.MessageType(name)
	if pt == nil {
		return nil, fmt.Errorf("unknown type: %q", name)
	}
	st := pt.Elem()

	// Add to cache.
	s.typeToNameMap[st] = name
	s.nameToTypeMap[name] = st

	// TODO is this necessary?
	// In most cases, the returned type will be used to search
	// for file descriptor.
	// Add it to cache now.
	fd, _, err := s.fileDescForType(st)
	if err == nil {
		s.typeToFileDescMap[st] = fd
	}

	return st, nil
}

func (s *serverReflectionServer) nameForType(st reflect.Type) (string, error) {
	// Check cache first.
	if name, ok := s.typeToNameMap[st]; ok {
		return name, nil
	}

	var name string
	fd, idxs, err := s.fileDescForType(st)
	if err != nil {
		return "", err
	}
	mt := fd.MessageType[idxs[0]]
	name = mt.GetName()
	for i := 1; i < len(idxs); i++ {
		mt = mt.NestedType[idxs[i]]
		name += "_" + mt.GetName()
	}
	if fd.Package != nil {
		name = *fd.Package + "." + name
	}

	// Add to cache.
	s.typeToNameMap[st] = name
	s.nameToTypeMap[name] = st

	return name, nil
}

func (s *serverReflectionServer) nameForPointer(i interface{}) (string, error) {
	return s.nameForType(reflect.TypeOf(i).Elem())
}

func (s *serverReflectionServer) filenameForType(st reflect.Type) (string, error) {
	// Check cache first. The value of cache is descriptor, not filename.
	if fd, ok := s.typeToFileDescMap[st]; ok {
		return fd.GetName(), nil
	}

	fd, _, err := s.fileDescForType(st)
	if err != nil {
		return "", err
	}
	return fd.GetName(), nil
}

// TODO filenameForMethod and Service

func (s *serverReflectionServer) fileDescContainingExtension(st reflect.Type, ext int32) (*dpb.FileDescriptorProto, error) {
	m, ok := reflect.Zero(reflect.PtrTo(st)).Interface().(proto.Message)
	if !ok {
		return nil, fmt.Errorf("failed to create message from type: %v", st)
	}

	var extDesc *proto.ExtensionDesc
	for id, desc := range proto.RegisteredExtensions(m) {
		if id == ext {
			extDesc = desc
			break
		}
	}

	if extDesc == nil {
		return nil, fmt.Errorf("failed to find registered extension for extension number %v", ext)
	}

	extT := reflect.TypeOf(extDesc.ExtensionType).Elem()
	// TODO this doesn't work if extT is simple types, like int32
	// Check cache.
	if fd, ok := s.typeToFileDescMap[extT]; ok {
		return fd, nil
	}

	fd, _, err := s.fileDescForType(extT)
	if err != nil {
		return nil, err
	}
	return fd, nil
}

// TODO filenameContainingExtension
// fd := fileDescContainingExtension()
// return fd.GetName()

func (s *serverReflectionServer) allExtensionNumbersForType(st reflect.Type) ([]int32, error) {
	m, ok := reflect.Zero(reflect.PtrTo(st)).Interface().(proto.Message)
	if !ok {
		return nil, fmt.Errorf("failed to create message from type: %v", st)
	}

	var out []int32
	for id := range proto.RegisteredExtensions(m) {
		out = append(out, id)
	}
	return out, nil
}
<|endoftext|>