<commit_before>package platform

import (
	"image"
	"image/color"
	"sync"

	"golang.org/x/exp/shiny/driver"
	"golang.org/x/exp/shiny/screen"
	"golang.org/x/image/math/f64"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
)

// WindowState contains what the window loop and program proper both need to touch
type WindowState struct {
	// the Width of the framebuffer
	Width int
	// the Height of the framebuffer
	Height int

	// a Mutex that must be held when reading or writing in WindowState
	Mutex sync.Mutex

	// Pix is the raw RGBA bytes of the framebuffer
	Pix []byte

	keyCodeArray [256]bool
	keyCodeMap map[key.Code]bool
	keyCharArray [256]bool
	keyCharMap map[rune]bool

	eventQueue screen.EventDeque
	drawRequested bool
}

// CharIsDown returns the key state for that char
func (s *WindowState) CharIsDown(c rune) bool {
	if c < 256 {
		return s.keyCharArray[byte(c)]
	}
	return s.keyCharMap[c]
}
// CodeIsDown returns the key state for that keyCode
func (s *WindowState) CodeIsDown(c key.Code) bool {
	if c < 256 {
		return s.keyCodeArray[byte(c)]
	}
	return s.keyCodeMap[c]
}

func (s *WindowState) updateKeyboardState(e key.Event) {
	setVal := e.Direction == key.DirPress
	if setVal || e.Direction == key.DirRelease {
		if e.Code < 256 {
			s.keyCodeArray[byte(e.Code)] = setVal
		}
		s.keyCodeMap[e.Code] = setVal
		if e.Rune < 256 {
			s.keyCharArray[byte(e.Rune)] = setVal
		}
		s.keyCharMap[e.Rune] = setVal
	}
}

// RequestDraw puts a draw request on the window loop queue
// It is assumed the mutex is already held when this function is called.
func (s *WindowState) RequestDraw() {
	if !s.drawRequested {
		s.eventQueue.Send(drawRequest{})
		s.drawRequested = true
	}
}

type drawRequest struct {}

// InitDisplayLoop creates a window and starts event loop
func InitDisplayLoop(title string, windowWidth, windowHeight, frameWidth, frameHeight int, updateLoop func(*WindowState)) {
	driver.Main(func (s screen.Screen) {

		w, err := s.NewWindow(&screen.NewWindowOptions{windowWidth, windowHeight, title})
		if err != nil {
			panic(err)
		}
		defer w.Release()

		buf, err := s.NewBuffer(image.Point{frameWidth, frameHeight})
		if err != nil {
			panic(err)
		}
		tex, err := s.NewTexture(image.Point{frameWidth, frameHeight})
		if err != nil {
			panic(err)
		}

		windowState := WindowState{
			Width: frameWidth,
			Height: frameHeight,
			Pix: make([]byte, 4*frameWidth*frameHeight),
			eventQueue: w,
			keyCodeMap: map[key.Code]bool{},
			keyCharMap: map[rune]bool{},
		}

		go updateLoop(&windowState)

		szRect := buf.Bounds()
		needFullRepaint := true

		for {
			publish := false

			switch e := w.NextEvent().(type) {
			case lifecycle.Event:
				if e.To == lifecycle.StageDead {
					return
				}

			case key.Event:
				windowState.Mutex.Lock()
				windowState.updateKeyboardState(e)
				windowState.Mutex.Unlock()

			case drawRequest:
				windowState.Mutex.Lock()
				copy(buf.RGBA().Pix, windowState.Pix)
				tex.Upload(image.Point{0, 0}, buf, buf.Bounds())
				windowState.drawRequested = false
				windowState.Mutex.Unlock()
				publish = true

			case size.Event:
				szRect = e.Bounds()

			case paint.Event:
				needFullRepaint = true
				publish = true
			}

			if publish {
				scaleFactX := float64(szRect.Max.X) / float64(tex.Bounds().Max.X)
				scaleFactY := float64(szRect.Max.Y) / float64(tex.Bounds().Max.Y)
				scaleFact := scaleFactX
				if scaleFactY < scaleFact {
					scaleFact = scaleFactY
				}
				// NOTE: flicker happens when scale is not an integer
				scaleFact = float64(int(scaleFact))
				newWidth := int(scaleFact * float64(tex.Bounds().Max.X))
				centerX := float64(szRect.Max.X/2 - newWidth/2)
				src2dst := f64.Aff3 {
					float64(int(scaleFact)), 0, centerX,
					0, float64(int(scaleFact)), 0,
				}
				identTrans := f64.Aff3 {
					1, 0, 0,
					0, 1, 0,
				}
				// get flicker when we do two draws all the time, so
				// only do it when we resize or get moved on/offscreen
				if needFullRepaint {
					w.DrawUniform(identTrans, color.Black, szRect, screen.Src, nil)
					needFullRepaint = false
				}
				w.Draw(src2dst, tex, tex.Bounds(), screen.Src, nil)
				w.Publish()
			}
		}
	})
}
<commit_msg>Handle the fact that rune is signed<commit_after>package platform

import (
	"image"
	"image/color"
	"sync"

	"golang.org/x/exp/shiny/driver"
	"golang.org/x/exp/shiny/screen"
	"golang.org/x/image/math/f64"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
)

// WindowState contains what the window loop and program proper both need to touch
type WindowState struct {
	// the Width of the framebuffer
	Width int
	// the Height of the framebuffer
	Height int

	// a Mutex that must be held when reading or writing in WindowState
	Mutex sync.Mutex

	// Pix is the raw RGBA bytes of the framebuffer
	Pix []byte

	keyCodeArray [256]bool
	keyCodeMap map[key.Code]bool
	keyCharArray [256]bool
	keyCharMap map[rune]bool

	eventQueue screen.EventDeque
	drawRequested bool
}

// CopyKeyCharArray writes the current ascii keystate to dest
func (s *WindowState) CopyKeyCharArray(dest []bool) {
	copy(dest, s.keyCharArray[:])
}

// CharIsDown returns the key state for that char
func (s *WindowState) CharIsDown(c rune) bool {
	if c >= 0 && c < 256 {
		return s.keyCharArray[byte(c)]
	}
	return s.keyCharMap[c]
}
// CodeIsDown returns the key state for that keyCode
func (s *WindowState) CodeIsDown(c key.Code) bool {
	if c < 256 {
		return s.keyCodeArray[byte(c)]
	}
	return s.keyCodeMap[c]
}

func (s *WindowState) updateKeyboardState(e key.Event) {
	setVal := e.Direction == key.DirPress
	if setVal || e.Direction == key.DirRelease {
		if e.Code < 256 {
			s.keyCodeArray[byte(e.Code)] = setVal
		} else {
			s.keyCodeMap[e.Code] = setVal
		}
		if e.Rune >= 0 && e.Rune < 256 {
			s.keyCharArray[byte(e.Rune)] = setVal
		} else {
			s.keyCharMap[e.Rune] = setVal
		}
	}
}

// RequestDraw puts a draw request on the window loop queue
// It is assumed the mutex is already held when this function is called.
func (s *WindowState) RequestDraw() {
	if !s.drawRequested {
		s.eventQueue.Send(drawRequest{})
		s.drawRequested = true
	}
}

type drawRequest struct {}

// InitDisplayLoop creates a window and starts event loop
func InitDisplayLoop(title string, windowWidth, windowHeight, frameWidth, frameHeight int, updateLoop func(*WindowState)) {
	driver.Main(func (s screen.Screen) {

		w, err := s.NewWindow(&screen.NewWindowOptions{windowWidth, windowHeight, title})
		if err != nil {
			panic(err)
		}
		defer w.Release()

		buf, err := s.NewBuffer(image.Point{frameWidth, frameHeight})
		if err != nil {
			panic(err)
		}
		tex, err := s.NewTexture(image.Point{frameWidth, frameHeight})
		if err != nil {
			panic(err)
		}

		windowState := WindowState{
			Width: frameWidth,
			Height: frameHeight,
			Pix: make([]byte, 4*frameWidth*frameHeight),
			eventQueue: w,
			keyCodeMap: map[key.Code]bool{},
			keyCharMap: map[rune]bool{},
		}

		go updateLoop(&windowState)

		szRect := buf.Bounds()
		needFullRepaint := true

		for {
			publish := false

			switch e := w.NextEvent().(type) {
			case lifecycle.Event:
				if e.To == lifecycle.StageDead {
					return
				}

			case key.Event:
				windowState.Mutex.Lock()
				windowState.updateKeyboardState(e)
				windowState.Mutex.Unlock()

			case drawRequest:
				windowState.Mutex.Lock()
				copy(buf.RGBA().Pix, windowState.Pix)
				tex.Upload(image.Point{0, 0}, buf, buf.Bounds())
				windowState.drawRequested = false
				windowState.Mutex.Unlock()
				publish = true

			case size.Event:
				szRect = e.Bounds()

			case paint.Event:
				needFullRepaint = true
				publish = true
			}

			if publish {
				scaleFactX := float64(szRect.Max.X) / float64(tex.Bounds().Max.X)
				scaleFactY := float64(szRect.Max.Y) / float64(tex.Bounds().Max.Y)
				scaleFact := scaleFactX
				if scaleFactY < scaleFact {
					scaleFact = scaleFactY
				}
				// NOTE: flicker happens when scale is not an integer
				scaleFact = float64(int(scaleFact))
				newWidth := int(scaleFact * float64(tex.Bounds().Max.X))
				centerX := float64(szRect.Max.X/2 - newWidth/2)
				src2dst := f64.Aff3 {
					float64(int(scaleFact)), 0, centerX,
					0, float64(int(scaleFact)), 0,
				}
				identTrans := f64.Aff3 {
					1, 0, 0,
					0, 1, 0,
				}
				// get flicker when we do two draws all the time, so
				// only do it when we resize or get moved on/offscreen
				if needFullRepaint {
					w.DrawUniform(identTrans, color.Black, szRect, screen.Src, nil)
					needFullRepaint = false
				}
				w.Draw(src2dst, tex, tex.Bounds(), screen.Src, nil)
				w.Publish()
			}
		}
	})
}
<|endoftext|>


<commit_before>// Copyright 2013 Prometheus Team
// 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 manager

import (
	"errors"
	"log"
	"time"
)

const (
	minimumRefreshPeriod    = 5 * time.Minute
	notificationRetryPeriod = 1 * time.Minute
)

// AggregationRule creates and manages the scope for received events.
type AggregationRule struct {
	Filters Filters

	RepeatRate time.Duration
}

type AggregationInstances []*AggregationInstance

type AggregationInstance struct {
	Rule  *AggregationRule
	Event *Event

	// When was this AggregationInstance created?
	Created time.Time
	// When was the last refresh received into this AggregationInstance?
	LastRefreshed time.Time

	// When was the last successful notification sent out for this
	// AggregationInstance?
	lastNotificationSent time.Time
	// Timer used to trigger a notification retry/resend.
	notificationResendTimer *time.Timer
	// Timer used to trigger the deletion of the AggregationInstance after it
	// hasn't been refreshed for too long.
	expiryTimer *time.Timer
}

func (r *AggregationRule) Handles(e *Event) bool {
	return r.Filters.Handles(e)
}

func (r *AggregationInstance) Ingest(e *Event) {
	r.Event = e
	r.LastRefreshed = time.Now()

	r.expiryTimer.Reset(minimumRefreshPeriod)
}

func (r *AggregationInstance) SendNotification(s SummaryReceiver) {
	if time.Since(r.lastNotificationSent) < r.Rule.RepeatRate {
		return
	}

	err := s.Receive(&EventSummary{
		Rule:  r.Rule,
		Event: r.Event,
	})
	if err != nil {
		log.Printf("Error while sending notification: %s, retrying in %v", err, notificationRetryPeriod)
		r.resendNotificationAfter(notificationRetryPeriod, s)
		return
	}

	r.resendNotificationAfter(r.Rule.RepeatRate, s)
	r.lastNotificationSent = time.Now()
}

func (r *AggregationInstance) resendNotificationAfter(d time.Duration, s SummaryReceiver) {
	// BUG: we can't just call SendNotification whenever the timer ends without
	// any synchronisation. The timer should instead feed into a channel which is
	// served by the main Dispatch() loop.
	r.notificationResendTimer = time.AfterFunc(d, func() {
		r.SendNotification(s)
	})
}

func (r *AggregationInstance) Close() {
	if r.notificationResendTimer != nil {
		r.notificationResendTimer.Stop()
	}
	if r.expiryTimer != nil {
		r.expiryTimer.Stop()
	}
}

type AggregationRules []*AggregationRule

type Aggregator struct {
	Rules      AggregationRules
	Aggregates map[EventFingerprint]*AggregationInstance

	aggRequests             chan *aggregateEventsRequest
	getAggregatesRequests   chan *getAggregatesRequest
	removeAggregateRequests chan EventFingerprint
	rulesRequests           chan *aggregatorResetRulesRequest
	closeRequests           chan *closeRequest
}

func NewAggregator() *Aggregator {
	return &Aggregator{
		Aggregates: make(map[EventFingerprint]*AggregationInstance),

		aggRequests:             make(chan *aggregateEventsRequest),
		getAggregatesRequests:   make(chan *getAggregatesRequest),
		removeAggregateRequests: make(chan EventFingerprint),
		rulesRequests:           make(chan *aggregatorResetRulesRequest),
		closeRequests:           make(chan *closeRequest),
	}
}

func (a *Aggregator) Close() {
	req := &closeRequest{
		done: make(chan bool),
	}
	a.closeRequests <- req
	<-req.done
}

func (a *Aggregator) closeInternal() {
	close(a.rulesRequests)
	close(a.aggRequests)
	close(a.getAggregatesRequests)
	close(a.removeAggregateRequests)
	close(a.closeRequests)
}

type aggregateEventsResponse struct {
	Err error
}

type aggregateEventsRequest struct {
	Events Events

	Response chan *aggregateEventsResponse
}

type getAggregatesResponse struct {
	Aggregates AggregationInstances
}

type getAggregatesRequest struct {
	Response chan getAggregatesResponse
}

type closeRequest struct {
	done chan bool
}

func (a *Aggregator) aggregate(req *aggregateEventsRequest, s SummaryReceiver) {
	if len(a.Rules) == 0 {
		req.Response <- &aggregateEventsResponse{
			Err: errors.New("No aggregation rules"),
		}
		close(req.Response)
		return
	}
	log.Println("aggregating", *req)
	for _, element := range req.Events {
		for _, r := range a.Rules {
			log.Println("Checking rule", r, r.Handles(element))
			if r.Handles(element) {
				fp := element.Fingerprint()
				aggregation, ok := a.Aggregates[fp]
				if !ok {
					expTimer := time.AfterFunc(minimumRefreshPeriod, func() {
						a.removeAggregateRequests <- fp
					})

					aggregation = &AggregationInstance{
						Rule:        r,
						Created:     time.Now(),
						expiryTimer: expTimer,
					}

					a.Aggregates[fp] = aggregation
				}

				aggregation.Ingest(element)
				aggregation.SendNotification(s)
				break
			}
		}
	}

	req.Response <- new(aggregateEventsResponse)
	close(req.Response)
}

type aggregatorResetRulesResponse struct{}

type aggregatorResetRulesRequest struct {
	Rules AggregationRules

	Response chan *aggregatorResetRulesResponse
}

func (a *Aggregator) replaceRules(r *aggregatorResetRulesRequest) {
	log.Println("Replacing", len(r.Rules), "aggregator rules...")
	a.Rules = r.Rules

	r.Response <- new(aggregatorResetRulesResponse)
	close(r.Response)
}

func (a *Aggregator) AlertAggregates() AggregationInstances {
	req := &getAggregatesRequest{
		Response: make(chan getAggregatesResponse),
	}

	a.getAggregatesRequests <- req

	result := <-req.Response

	return result.Aggregates
}

func (a *Aggregator) aggregates() AggregationInstances {
	aggs := make(AggregationInstances, 0, len(a.Aggregates))
	for _, agg := range a.Aggregates {
		aggs = append(aggs, agg)
	}
	return aggs
}

func (a *Aggregator) Receive(e Events) error {
	req := &aggregateEventsRequest{
		Events:   e,
		Response: make(chan *aggregateEventsResponse),
	}

	a.aggRequests <- req

	result := <-req.Response

	return result.Err
}

func (a *Aggregator) SetRules(r AggregationRules) error {
	req := &aggregatorResetRulesRequest{
		Rules:    r,
		Response: make(chan *aggregatorResetRulesResponse),
	}

	a.rulesRequests <- req

	_ = <-req.Response

	return nil
}

func (a *Aggregator) Dispatch(s SummaryReceiver) {
	for {
		select {
		case req := <-a.aggRequests:
			a.aggregate(req, s)

		case rules := <-a.rulesRequests:
			a.replaceRules(rules)

		case req := <-a.getAggregatesRequests:
			aggs := a.aggregates()
			req.Response <- getAggregatesResponse{
				Aggregates: aggs,
			}
			close(req.Response)

		case fp := <-a.removeAggregateRequests:
			log.Println("Deleting expired aggregation instance", a)
			a.Aggregates[fp].Close()
			delete(a.Aggregates, fp)

		case req := <-a.closeRequests:
			a.closeInternal()
			req.done <- true
			// BUG: Simply returning here will prevent proper draining. Fix this.
			return
		}
	}
}
<commit_msg>Cleanup: rename "element" to "event".<commit_after>// Copyright 2013 Prometheus Team
// 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 manager

import (
	"errors"
	"log"
	"time"
)

const (
	minimumRefreshPeriod    = 5 * time.Minute
	notificationRetryPeriod = 1 * time.Minute
)

// AggregationRule creates and manages the scope for received events.
type AggregationRule struct {
	Filters Filters

	RepeatRate time.Duration
}

type AggregationInstances []*AggregationInstance

type AggregationInstance struct {
	Rule  *AggregationRule
	Event *Event

	// When was this AggregationInstance created?
	Created time.Time
	// When was the last refresh received into this AggregationInstance?
	LastRefreshed time.Time

	// When was the last successful notification sent out for this
	// AggregationInstance?
	lastNotificationSent time.Time
	// Timer used to trigger a notification retry/resend.
	notificationResendTimer *time.Timer
	// Timer used to trigger the deletion of the AggregationInstance after it
	// hasn't been refreshed for too long.
	expiryTimer *time.Timer
}

func (r *AggregationRule) Handles(e *Event) bool {
	return r.Filters.Handles(e)
}

func (r *AggregationInstance) Ingest(e *Event) {
	r.Event = e
	r.LastRefreshed = time.Now()

	r.expiryTimer.Reset(minimumRefreshPeriod)
}

func (r *AggregationInstance) SendNotification(s SummaryReceiver) {
	if time.Since(r.lastNotificationSent) < r.Rule.RepeatRate {
		return
	}

	err := s.Receive(&EventSummary{
		Rule:  r.Rule,
		Event: r.Event,
	})
	if err != nil {
		log.Printf("Error while sending notification: %s, retrying in %v", err, notificationRetryPeriod)
		r.resendNotificationAfter(notificationRetryPeriod, s)
		return
	}

	r.resendNotificationAfter(r.Rule.RepeatRate, s)
	r.lastNotificationSent = time.Now()
}

func (r *AggregationInstance) resendNotificationAfter(d time.Duration, s SummaryReceiver) {
	// BUG: we can't just call SendNotification whenever the timer ends without
	// any synchronisation. The timer should instead feed into a channel which is
	// served by the main Dispatch() loop.
	r.notificationResendTimer = time.AfterFunc(d, func() {
		r.SendNotification(s)
	})
}

func (r *AggregationInstance) Close() {
	if r.notificationResendTimer != nil {
		r.notificationResendTimer.Stop()
	}
	if r.expiryTimer != nil {
		r.expiryTimer.Stop()
	}
}

type AggregationRules []*AggregationRule

type Aggregator struct {
	Rules      AggregationRules
	Aggregates map[EventFingerprint]*AggregationInstance

	aggRequests             chan *aggregateEventsRequest
	getAggregatesRequests   chan *getAggregatesRequest
	removeAggregateRequests chan EventFingerprint
	rulesRequests           chan *aggregatorResetRulesRequest
	closeRequests           chan *closeRequest
}

func NewAggregator() *Aggregator {
	return &Aggregator{
		Aggregates: make(map[EventFingerprint]*AggregationInstance),

		aggRequests:             make(chan *aggregateEventsRequest),
		getAggregatesRequests:   make(chan *getAggregatesRequest),
		removeAggregateRequests: make(chan EventFingerprint),
		rulesRequests:           make(chan *aggregatorResetRulesRequest),
		closeRequests:           make(chan *closeRequest),
	}
}

func (a *Aggregator) Close() {
	req := &closeRequest{
		done: make(chan bool),
	}
	a.closeRequests <- req
	<-req.done
}

func (a *Aggregator) closeInternal() {
	close(a.rulesRequests)
	close(a.aggRequests)
	close(a.getAggregatesRequests)
	close(a.removeAggregateRequests)
	close(a.closeRequests)
}

type aggregateEventsResponse struct {
	Err error
}

type aggregateEventsRequest struct {
	Events Events

	Response chan *aggregateEventsResponse
}

type getAggregatesResponse struct {
	Aggregates AggregationInstances
}

type getAggregatesRequest struct {
	Response chan getAggregatesResponse
}

type closeRequest struct {
	done chan bool
}

func (a *Aggregator) aggregate(req *aggregateEventsRequest, s SummaryReceiver) {
	if len(a.Rules) == 0 {
		req.Response <- &aggregateEventsResponse{
			Err: errors.New("No aggregation rules"),
		}
		close(req.Response)
		return
	}
	log.Println("aggregating", *req)
	for _, event := range req.Events {
		for _, r := range a.Rules {
			log.Println("Checking rule", r, r.Handles(event))
			if r.Handles(event) {
				fp := event.Fingerprint()
				aggregation, ok := a.Aggregates[fp]
				if !ok {
					expTimer := time.AfterFunc(minimumRefreshPeriod, func() {
						a.removeAggregateRequests <- fp
					})

					aggregation = &AggregationInstance{
						Rule:        r,
						Created:     time.Now(),
						expiryTimer: expTimer,
					}

					a.Aggregates[fp] = aggregation
				}

				aggregation.Ingest(event)
				aggregation.SendNotification(s)
				break
			}
		}
	}

	req.Response <- new(aggregateEventsResponse)
	close(req.Response)
}

type aggregatorResetRulesResponse struct{}

type aggregatorResetRulesRequest struct {
	Rules AggregationRules

	Response chan *aggregatorResetRulesResponse
}

func (a *Aggregator) replaceRules(r *aggregatorResetRulesRequest) {
	log.Println("Replacing", len(r.Rules), "aggregator rules...")
	a.Rules = r.Rules

	r.Response <- new(aggregatorResetRulesResponse)
	close(r.Response)
}

func (a *Aggregator) AlertAggregates() AggregationInstances {
	req := &getAggregatesRequest{
		Response: make(chan getAggregatesResponse),
	}

	a.getAggregatesRequests <- req

	result := <-req.Response

	return result.Aggregates
}

func (a *Aggregator) aggregates() AggregationInstances {
	aggs := make(AggregationInstances, 0, len(a.Aggregates))
	for _, agg := range a.Aggregates {
		aggs = append(aggs, agg)
	}
	return aggs
}

func (a *Aggregator) Receive(e Events) error {
	req := &aggregateEventsRequest{
		Events:   e,
		Response: make(chan *aggregateEventsResponse),
	}

	a.aggRequests <- req

	result := <-req.Response

	return result.Err
}

func (a *Aggregator) SetRules(r AggregationRules) error {
	req := &aggregatorResetRulesRequest{
		Rules:    r,
		Response: make(chan *aggregatorResetRulesResponse),
	}

	a.rulesRequests <- req

	_ = <-req.Response

	return nil
}

func (a *Aggregator) Dispatch(s SummaryReceiver) {
	for {
		select {
		case req := <-a.aggRequests:
			a.aggregate(req, s)

		case rules := <-a.rulesRequests:
			a.replaceRules(rules)

		case req := <-a.getAggregatesRequests:
			aggs := a.aggregates()
			req.Response <- getAggregatesResponse{
				Aggregates: aggs,
			}
			close(req.Response)

		case fp := <-a.removeAggregateRequests:
			log.Println("Deleting expired aggregation instance", a)
			a.Aggregates[fp].Close()
			delete(a.Aggregates, fp)

		case req := <-a.closeRequests:
			a.closeInternal()
			req.done <- true
			// BUG: Simply returning here will prevent proper draining. Fix this.
			return
		}
	}
}
<|endoftext|>


<commit_before>package controllers

import (
	m "devices-server/app/models"
	"github.com/revel/revel"
)

type Devices struct {
	GormController
}

/*
	Deviceを作成
 	@param name:機種名
 	@param manufacturer:メーカー
 	@param carrier:キャリア
 	@param os:OS
 	@param size:サイズ
 	@param resolution:解像度
 	@param memory:メモリ
 	@param dateOfRelease:発売日
 	@param other:その他
 	return data{sucess, device}
*/
func (c Devices) Create(name string,
	manufacturer string,
	carrier string,
	os string,
	size string,
	resolution string,
	memory string,
	dateOfRelease int64,
	other string) revel.Result {
	data := struct {
		Success bool     `json:"success"`
		Device  m.Device `json:"device"`
	}{
		Success: false,
		Device:  m.Device{},
	}

	var devices []m.Device
	c.Txn.Find(&devices, "name = ?", name)
	if len(devices) == 0 {
		device := m.Device{
			Name:          name,
			Manufacturer:  manufacturer,
			Carrier:       carrier,
			Os:            os,
			Size:          size,
			Resolution:    resolution,
			Memory:        memory,
			DateOfRelease: dateOfRelease,
			Other:         other,
		}
		c.Txn.NewRecord(device)
		c.Txn.Create(&device)
		data.Device = device
		data.Success = true
	}
	return c.RenderJson(data)
}

/*
	Deviceを更新
 	@param device_id:ID
 	@param name:機種名
 	@param manufacturer:メーカー
 	@param carrier:キャリア
 	@param os:OS
 	@param size:サイズ
 	@param resolution:解像度
 	@param memory:メモリ
 	@param dateOfRelease:発売日
 	@param other:その他
 	return data{sucess, device}
*/
func (c Devices) Update(device_id int64,
	name string,
	manufacturer string,
	carrier string,
	os string,
	size string,
	resolution string,
	memory string,
	dateOfRelease int64,
	other string) revel.Result {
	data := struct {
		Success bool     `json:"success"`
		Device  m.Device `json:"device"`
	}{
		Success: false,
		Device:  m.Device{},
	}

	var devices []m.Device
	c.Txn.Find(&devices, "id = ?", device_id)
	if len(devices) != 0 {
		device := devices[0]
		device.Name = name
		device.Manufacturer = manufacturer
		device.Carrier = carrier
		device.Os = os
		device.Size = size
		device.Resolution = resolution
		device.Memory = memory
		device.DateOfRelease = dateOfRelease
		device.Other = other
		c.Txn.Save(&device)
		data.Device = device
		data.Success = true
	}
	return c.RenderJson(data)
}

/*
	Deviceのリストを取得
 	return data{sucess, devices}
*/
func (c Devices) List() revel.Result {
	data := struct {
		Success bool       `json:"success"`
		Devices []m.Device `json:"devices"`
	}{
		Success: false,
		Devices: []m.Device{},
	}

	var devices []m.Device
	c.Txn.Find(&devices)
	if len(devices) != 0 {
		data.Devices = devices
		data.Success = true
	}
	return c.RenderJson(data)
}

/*
	Deviceを特定のユーザーに貸し出す
	@param userId:ユーザ-ID
 	@param deviceId:端末ID
 	return data{sucess, device}
*/
func (c Devices) Borrow(user_id int64, device_id int64) revel.Result {
	data := struct {
		Success bool     `json:"success"`
		Device  m.Device `json:"device"`
	}{
		Success: false,
		Device:  m.Device{},
	}

	var users []m.User
	c.Txn.Find(&users, "id = ?", user_id)
	if len(users) != 0 {
		var devices []m.Device
		c.Txn.Find(&devices, "id = ?", device_id)
		if len(devices) != 0 {
			device := devices[0]
			device.User = users[0]
			device.DeviceStates = appendDeviceState(device.DeviceStates, users[0], device.Id)
			c.Txn.Save(&device)

			data.Device = device
			data.Success = true
		}
	}
	return c.RenderJson(data)
}

/*
	Deviceを特定のユーザーに貸し出す
	@param userId:ユーザ-ID
 	@param deviceId:端末ID
 	return data{sucess, device}
*/
func (c Devices) Return(user_id int64, device_id int64) revel.Result {
	data := struct {
		Success bool     `json:"success"`
		Device  m.Device `json:"device"`
	}{
		Success: false,
		Device:  m.Device{},
	}

	var users []m.User
	c.Txn.Find(&users, "id = ?", user_id)
	if len(users) != 0 {
		var devices []m.Device
		c.Txn.Find(&devices, "id = ?", device_id)
		if len(devices) != 0 {
			device := devices[0]
			device.User = users[0]
			device.DeviceStates = appendDeviceState(device.DeviceStates, users[0], device.Id)
			c.Txn.Save(&device)

			data.Device = device
			data.Success = true
		}
	}
	return c.RenderJson(data)
}

func (c Devices) appendDeviceState(deviceStates []m.DeviceState, user m.User, device_id int64) []m.DeviceState {
	deviceState := m.DeviceState{
		Action:   true,
		DeviceId: device_id,
		User:     user,
	}
	c.Txn.NewRecord(deviceState)
	c.Txn.Create(&deviceState)
	deviceStates = append(deviceStates, deviceState)
	return deviceStates
}
<commit_msg>Modify func<commit_after>package controllers

import (
	m "devices-server/app/models"
	"github.com/revel/revel"
)

type Devices struct {
	GormController
}

/*
	Deviceを作成
 	@param name:機種名
 	@param manufacturer:メーカー
 	@param carrier:キャリア
 	@param os:OS
 	@param size:サイズ
 	@param resolution:解像度
 	@param memory:メモリ
 	@param dateOfRelease:発売日
 	@param other:その他
 	return data{sucess, device}
*/
func (c Devices) Create(name string,
	manufacturer string,
	carrier string,
	os string,
	size string,
	resolution string,
	memory string,
	dateOfRelease int64,
	other string) revel.Result {
	data := struct {
		Success bool     `json:"success"`
		Device  m.Device `json:"device"`
	}{
		Success: false,
		Device:  m.Device{},
	}

	var devices []m.Device
	c.Txn.Find(&devices, "name = ?", name)
	if len(devices) == 0 {
		device := m.Device{
			Name:          name,
			Manufacturer:  manufacturer,
			Carrier:       carrier,
			Os:            os,
			Size:          size,
			Resolution:    resolution,
			Memory:        memory,
			DateOfRelease: dateOfRelease,
			Other:         other,
		}
		c.Txn.NewRecord(device)
		c.Txn.Create(&device)
		data.Device = device
		data.Success = true
	}
	return c.RenderJson(data)
}

/*
	Deviceを更新
 	@param device_id:ID
 	@param name:機種名
 	@param manufacturer:メーカー
 	@param carrier:キャリア
 	@param os:OS
 	@param size:サイズ
 	@param resolution:解像度
 	@param memory:メモリ
 	@param dateOfRelease:発売日
 	@param other:その他
 	return data{sucess, device}
*/
func (c Devices) Update(device_id int64,
	name string,
	manufacturer string,
	carrier string,
	os string,
	size string,
	resolution string,
	memory string,
	dateOfRelease int64,
	other string) revel.Result {
	data := struct {
		Success bool     `json:"success"`
		Device  m.Device `json:"device"`
	}{
		Success: false,
		Device:  m.Device{},
	}

	var devices []m.Device
	c.Txn.Find(&devices, "id = ?", device_id)
	if len(devices) != 0 {
		device := devices[0]
		device.Name = name
		device.Manufacturer = manufacturer
		device.Carrier = carrier
		device.Os = os
		device.Size = size
		device.Resolution = resolution
		device.Memory = memory
		device.DateOfRelease = dateOfRelease
		device.Other = other
		c.Txn.Save(&device)
		data.Device = device
		data.Success = true
	}
	return c.RenderJson(data)
}

/*
	Deviceのリストを取得
 	return data{sucess, devices}
*/
func (c Devices) List() revel.Result {
	data := struct {
		Success bool       `json:"success"`
		Devices []m.Device `json:"devices"`
	}{
		Success: false,
		Devices: []m.Device{},
	}

	var devices []m.Device
	c.Txn.Find(&devices)
	if len(devices) != 0 {
		data.Devices = devices
		data.Success = true
	}
	return c.RenderJson(data)
}

func (c Devices) appendDeviceState(deviceStates []m.DeviceState, user m.User, device_id int64) []m.DeviceState {
	deviceState := m.DeviceState{
		Action:   true,
		DeviceId: device_id,
		User:     user,
	}
	c.Txn.NewRecord(deviceState)
	c.Txn.Create(&deviceState)
	deviceStates = append(deviceStates, deviceState)
	return deviceStates
}

/*
	Deviceを特定のユーザーに貸し出す
	@param userId:ユーザ-ID
 	@param deviceId:端末ID
 	return data{sucess, device}
*/
func (c Devices) Borrow(user_id int64, device_id int64) revel.Result {
	data := struct {
		Success bool     `json:"success"`
		Device  m.Device `json:"device"`
	}{
		Success: false,
		Device:  m.Device{},
	}

	var users []m.User
	c.Txn.Find(&users, "id = ?", user_id)
	if len(users) != 0 {
		var devices []m.Device
		c.Txn.Find(&devices, "id = ?", device_id)
		if len(devices) != 0 {
			device := devices[0]
			device.User = users[0]
			device.DeviceStates = appendDeviceState(device.DeviceStates, users[0], device.Id)
			c.Txn.Save(&device)

			data.Device = device
			data.Success = true
		}
	}
	return c.RenderJson(data)
}

/*
	Deviceを特定のユーザーに貸し出す
	@param userId:ユーザ-ID
 	@param deviceId:端末ID
 	return data{sucess, device}
*/
func (c Devices) Return(user_id int64, device_id int64) revel.Result {
	data := struct {
		Success bool     `json:"success"`
		Device  m.Device `json:"device"`
	}{
		Success: false,
		Device:  m.Device{},
	}

	var users []m.User
	c.Txn.Find(&users, "id = ?", user_id)
	if len(users) != 0 {
		var devices []m.Device
		c.Txn.Find(&devices, "id = ?", device_id)
		if len(devices) != 0 {
			device := devices[0]
			device.User = users[0]
			device.DeviceStates = appendDeviceState(device.DeviceStates, users[0], device.Id)
			c.Txn.Save(&device)

			data.Device = device
			data.Success = true
		}
	}
	return c.RenderJson(data)
}
<|endoftext|>


<commit_before>package google

import (
	"fmt"
	"log"
	"sort"
	"strings"

	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
	computeBeta "google.golang.org/api/compute/v0.beta"
	"google.golang.org/api/compute/v1"
	"google.golang.org/api/googleapi"
)

const peerNetworkLinkRegex = "projects/(" + ProjectRegex + ")/global/networks/((?:[a-z](?:[-a-z0-9]*[a-z0-9])?))$"

func resourceComputeNetworkPeering() *schema.Resource {
	return &schema.Resource{
		Create: resourceComputeNetworkPeeringCreate,
		Read:   resourceComputeNetworkPeeringRead,
		Delete: resourceComputeNetworkPeeringDelete,
		Importer: &schema.ResourceImporter{
			State: resourceComputeNetworkPeeringImport,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateGCPName,
			},

			"network": {
				Type:             schema.TypeString,
				Required:         true,
				ForceNew:         true,
				ValidateFunc:     validateRegexp(peerNetworkLinkRegex),
				DiffSuppressFunc: compareSelfLinkRelativePaths,
			},

			"peer_network": {
				Type:             schema.TypeString,
				Required:         true,
				ForceNew:         true,
				ValidateFunc:     validateRegexp(peerNetworkLinkRegex),
				DiffSuppressFunc: compareSelfLinkRelativePaths,
			},

			"state": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"state_details": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"auto_create_routes": {
				Type:     schema.TypeBool,
				Optional: true,
				Removed:  "auto_create_routes has been removed because it's redundant and not user-configurable. It can safely be removed from your config",
				Computed: true,
			},
		},
	}
}

func resourceComputeNetworkPeeringCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkFieldValue, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
	if err != nil {
		return err
	}

	request := &computeBeta.NetworksAddPeeringRequest{}
	request.NetworkPeering = expandNetworkPeering(d)

	addOp, err := config.clientComputeBeta.Networks.AddPeering(networkFieldValue.Project, networkFieldValue.Name, request).Do()
	if err != nil {
		return fmt.Errorf("Error adding network peering: %s", err)
	}

	err = computeOperationWait(config, addOp, networkFieldValue.Project, "Adding Network Peering")
	if err != nil {
		return err
	}

	d.SetId(fmt.Sprintf("%s/%s", networkFieldValue.Name, d.Get("name").(string)))

	return resourceComputeNetworkPeeringRead(d, meta)
}

func resourceComputeNetworkPeeringRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	peeringName := d.Get("name").(string)
	networkFieldValue, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
	if err != nil {
		return err
	}

	network, err := config.clientComputeBeta.Networks.Get(networkFieldValue.Project, networkFieldValue.Name).Do()
	if err != nil {
		return handleNotFoundError(err, d, fmt.Sprintf("Network %q", networkFieldValue.Name))
	}

	peering := findPeeringFromNetwork(network, peeringName)
	if peering == nil {
		log.Printf("[WARN] Removing network peering %s from network %s because it's gone", peeringName, network.Name)
		d.SetId("")
		return nil
	}

	d.Set("peer_network", peering.Network)
	d.Set("name", peering.Name)
	d.Set("state", peering.State)
	d.Set("state_details", peering.StateDetails)

	return nil
}

func resourceComputeNetworkPeeringDelete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	// Remove the `network` to `peer_network` peering
	name := d.Get("name").(string)
	networkFieldValue, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
	if err != nil {
		return err
	}
	peerNetworkFieldValue, err := ParseNetworkFieldValue(d.Get("peer_network").(string), d, config)
	if err != nil {
		return err
	}

	request := &compute.NetworksRemovePeeringRequest{
		Name: name,
	}

	// Only one delete peering operation at a time can be performed inside any peered VPCs.
	peeringLockName := getNetworkPeeringLockName(networkFieldValue.Name, peerNetworkFieldValue.Name)
	mutexKV.Lock(peeringLockName)
	defer mutexKV.Unlock(peeringLockName)

	removeOp, err := config.clientCompute.Networks.RemovePeering(networkFieldValue.Project, networkFieldValue.Name, request).Do()
	if err != nil {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			log.Printf("[WARN] Peering `%s` already removed from network `%s`", name, networkFieldValue.Name)
		} else {
			return fmt.Errorf("Error removing peering `%s` from network `%s`: %s", name, networkFieldValue.Name, err)
		}
	} else {
		err = computeOperationWait(config, removeOp, networkFieldValue.Project, "Removing Network Peering")
		if err != nil {
			return err
		}
	}

	return nil
}

func findPeeringFromNetwork(network *computeBeta.Network, peeringName string) *computeBeta.NetworkPeering {
	for _, p := range network.Peerings {
		if p.Name == peeringName {
			return p
		}
	}
	return nil
}
func expandNetworkPeering(d *schema.ResourceData) *computeBeta.NetworkPeering {
	return &computeBeta.NetworkPeering{
		ExchangeSubnetRoutes: true,
		Name:                 d.Get("name").(string),
		Network:              d.Get("peer_network").(string),
	}
}

func getNetworkPeeringLockName(networkName, peerNetworkName string) string {
	// Whether you delete the peering from network A to B or the one from B to A, they
	// cannot happen at the same time.
	networks := []string{networkName, peerNetworkName}
	sort.Strings(networks)

	return fmt.Sprintf("network_peering/%s/%s", networks[0], networks[1])
}

func resourceComputeNetworkPeeringImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
	config := meta.(*Config)
	splits := strings.Split(d.Id(), "/")
	if len(splits) != 3 {
		return nil, fmt.Errorf("Error parsing network peering import format, expected: {project}/{network}/{name}")
	}

	// Build the template for the network self_link
	urlTemplate, err := replaceVars(d, config, "{{ComputeBasePath}}projects/%s/global/networks/%s")
	if err != nil {
		return nil, err
	}
	d.Set("network", ConvertSelfLinkToV1(fmt.Sprintf(urlTemplate, splits[0], splits[1])))
	d.Set("name", splits[2])

	// Replace import id for the resource id
	id := fmt.Sprintf("%s/%s", splits[1], splits[2])
	d.SetId(id)

	return []*schema.ResourceData{d}, nil
}
<commit_msg>Add mutex to peering create (#5338)<commit_after>package google

import (
	"fmt"
	"log"
	"sort"
	"strings"

	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
	computeBeta "google.golang.org/api/compute/v0.beta"
	"google.golang.org/api/compute/v1"
	"google.golang.org/api/googleapi"
)

const peerNetworkLinkRegex = "projects/(" + ProjectRegex + ")/global/networks/((?:[a-z](?:[-a-z0-9]*[a-z0-9])?))$"

func resourceComputeNetworkPeering() *schema.Resource {
	return &schema.Resource{
		Create: resourceComputeNetworkPeeringCreate,
		Read:   resourceComputeNetworkPeeringRead,
		Delete: resourceComputeNetworkPeeringDelete,
		Importer: &schema.ResourceImporter{
			State: resourceComputeNetworkPeeringImport,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateGCPName,
			},

			"network": {
				Type:             schema.TypeString,
				Required:         true,
				ForceNew:         true,
				ValidateFunc:     validateRegexp(peerNetworkLinkRegex),
				DiffSuppressFunc: compareSelfLinkRelativePaths,
			},

			"peer_network": {
				Type:             schema.TypeString,
				Required:         true,
				ForceNew:         true,
				ValidateFunc:     validateRegexp(peerNetworkLinkRegex),
				DiffSuppressFunc: compareSelfLinkRelativePaths,
			},

			"state": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"state_details": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"auto_create_routes": {
				Type:     schema.TypeBool,
				Optional: true,
				Removed:  "auto_create_routes has been removed because it's redundant and not user-configurable. It can safely be removed from your config",
				Computed: true,
			},
		},
	}
}

func resourceComputeNetworkPeeringCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkFieldValue, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
	if err != nil {
		return err
	}
	peerNetworkFieldValue, err := ParseNetworkFieldValue(d.Get("peer_network").(string), d, config)
	if err != nil {
		return err
	}

	request := &computeBeta.NetworksAddPeeringRequest{}
	request.NetworkPeering = expandNetworkPeering(d)

	// Only one peering operation at a time can be performed for a given network.
	// Lock on both networks, sorted so we don't deadlock for A <--> B peering pairs.
	peeringLockNames := sortedNetworkPeeringMutexKeys(networkFieldValue, peerNetworkFieldValue)
	for _, kn := range peeringLockNames {
		mutexKV.Lock(kn)
		defer mutexKV.Unlock(kn)
	}

	addOp, err := config.clientComputeBeta.Networks.AddPeering(networkFieldValue.Project, networkFieldValue.Name, request).Do()
	if err != nil {
		return fmt.Errorf("Error adding network peering: %s", err)
	}

	err = computeOperationWait(config, addOp, networkFieldValue.Project, "Adding Network Peering")
	if err != nil {
		return err
	}

	d.SetId(fmt.Sprintf("%s/%s", networkFieldValue.Name, d.Get("name").(string)))

	return resourceComputeNetworkPeeringRead(d, meta)
}

func resourceComputeNetworkPeeringRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	peeringName := d.Get("name").(string)
	networkFieldValue, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
	if err != nil {
		return err
	}

	network, err := config.clientComputeBeta.Networks.Get(networkFieldValue.Project, networkFieldValue.Name).Do()
	if err != nil {
		return handleNotFoundError(err, d, fmt.Sprintf("Network %q", networkFieldValue.Name))
	}

	peering := findPeeringFromNetwork(network, peeringName)
	if peering == nil {
		log.Printf("[WARN] Removing network peering %s from network %s because it's gone", peeringName, network.Name)
		d.SetId("")
		return nil
	}

	d.Set("peer_network", peering.Network)
	d.Set("name", peering.Name)
	d.Set("state", peering.State)
	d.Set("state_details", peering.StateDetails)

	return nil
}

func resourceComputeNetworkPeeringDelete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	// Remove the `network` to `peer_network` peering
	name := d.Get("name").(string)
	networkFieldValue, err := ParseNetworkFieldValue(d.Get("network").(string), d, config)
	if err != nil {
		return err
	}
	peerNetworkFieldValue, err := ParseNetworkFieldValue(d.Get("peer_network").(string), d, config)
	if err != nil {
		return err
	}

	request := &compute.NetworksRemovePeeringRequest{
		Name: name,
	}

	// Only one peering operation at a time can be performed for a given network.
	// Lock on both networks, sorted so we don't deadlock for A <--> B peering pairs.
	peeringLockNames := sortedNetworkPeeringMutexKeys(networkFieldValue, peerNetworkFieldValue)
	for _, kn := range peeringLockNames {
		mutexKV.Lock(kn)
		defer mutexKV.Unlock(kn)
	}

	removeOp, err := config.clientCompute.Networks.RemovePeering(networkFieldValue.Project, networkFieldValue.Name, request).Do()
	if err != nil {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			log.Printf("[WARN] Peering `%s` already removed from network `%s`", name, networkFieldValue.Name)
		} else {
			return fmt.Errorf("Error removing peering `%s` from network `%s`: %s", name, networkFieldValue.Name, err)
		}
	} else {
		err = computeOperationWait(config, removeOp, networkFieldValue.Project, "Removing Network Peering")
		if err != nil {
			return err
		}
	}

	return nil
}

func findPeeringFromNetwork(network *computeBeta.Network, peeringName string) *computeBeta.NetworkPeering {
	for _, p := range network.Peerings {
		if p.Name == peeringName {
			return p
		}
	}
	return nil
}
func expandNetworkPeering(d *schema.ResourceData) *computeBeta.NetworkPeering {
	return &computeBeta.NetworkPeering{
		ExchangeSubnetRoutes: true,
		Name:                 d.Get("name").(string),
		Network:              d.Get("peer_network").(string),
	}
}

func sortedNetworkPeeringMutexKeys(networkName, peerNetworkName *GlobalFieldValue) []string {
	// Whether you delete the peering from network A to B or the one from B to A, they
	// cannot happen at the same time.
	networks := []string{
		fmt.Sprintf("%s/peerings", networkName.RelativeLink()),
		fmt.Sprintf("%s/peerings", peerNetworkName.RelativeLink()),
	}
	sort.Strings(networks)
	return networks
}

func resourceComputeNetworkPeeringImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
	config := meta.(*Config)
	splits := strings.Split(d.Id(), "/")
	if len(splits) != 3 {
		return nil, fmt.Errorf("Error parsing network peering import format, expected: {project}/{network}/{name}")
	}

	// Build the template for the network self_link
	urlTemplate, err := replaceVars(d, config, "{{ComputeBasePath}}projects/%s/global/networks/%s")
	if err != nil {
		return nil, err
	}
	d.Set("network", ConvertSelfLinkToV1(fmt.Sprintf(urlTemplate, splits[0], splits[1])))
	d.Set("name", splits[2])

	// Replace import id for the resource id
	id := fmt.Sprintf("%s/%s", splits[1], splits[2])
	d.SetId(id)

	return []*schema.ResourceData{d}, nil
}
<|endoftext|>


<commit_before>package proxy

import (
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"sync"

	"github.com/h2oai/steamY/master/az"
	"github.com/h2oai/steamY/master/data"
)

type reverseProxy struct {
	clusterId int64
	host      string
	proxy     *httputil.ReverseProxy
}

func newReverseProxy(clusterId int64, host string) *reverseProxy {
	return &reverseProxy{
		clusterId,
		host,
		httputil.NewSingleHostReverseProxy(&url.URL{
			Scheme: "http",
			Host:   host,
		}),
	}
}

type ProxyHandler struct {
	mu      *sync.RWMutex
	proxies map[int64]*reverseProxy
	az      az.Az
	ds      *data.Datastore
}

func NewProxyHandler(az az.Az, ds *data.Datastore) *ProxyHandler {
	return &ProxyHandler{
		&sync.RWMutex{},
		make(map[int64]*reverseProxy),
		az,
		ds,
	}
}

func (pm *ProxyHandler) getOrCreateReverseProxy(clusterId int64, host string) *reverseProxy {
	pm.mu.RLock()
	rp, ok := pm.proxies[clusterId]
	pm.mu.RUnlock()

	if ok {
		return rp
	}

	rp = newReverseProxy(clusterId, host)
	pm.mu.Lock()
	pm.proxies[clusterId] = rp
	pm.mu.Unlock()
	return rp
}

func (pm *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	// All proxy requests require a header with key=X-Cluster; value=cluster-id (in)

	clusterHeader := r.Header.Get("X-Cluster")
	if clusterHeader == "" {
		http.Error(w, "Cluster requests via Steam requires a valid X-Cluster HTTP header", http.StatusBadRequest)
		return
	}

	clusterId, err := strconv.ParseInt(clusterHeader, 10, 64)
	if err != nil {
		http.Error(w, "Invalid X-Cluster HTTP header, expected integer", http.StatusBadRequest)
		return
	}

	// Identify the principal

	pz, azerr := pm.az.Identify(r)
	if azerr != nil {
		http.Error(w, azerr.Error(), http.StatusForbidden)
		return
	}

	// Check if principal is allowed to use clusters

	if err := pz.CheckPermission(pm.ds.Permissions.ViewCluster); err != nil {
		http.Error(w, err.Error(), http.StatusForbidden)
		return
	}

	// Read cluster from database.
	// This also checks if the principal has privileges to view this specific cluster.

	cluster, err := pm.ds.ReadCluster(pz, clusterId)
	if err != nil {
		http.Error(w, err.Error(), http.StatusForbidden)
		return
	}

	// Get existing proxy, or create one if missing.

	rp := pm.getOrCreateReverseProxy(clusterId, cluster.Address)

	// Forward

	rp.proxy.ServeHTTP(w, r)
}
<commit_msg>STEAM-160: proxies can forward to flow with a query string<commit_after>package proxy

import (
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"sync"

	"github.com/h2oai/steamY/master/az"
	"github.com/h2oai/steamY/master/data"
)

type reverseProxy struct {
	clusterId int64
	host      string
	proxy     *httputil.ReverseProxy
}

func newReverseProxy(clusterId int64, host string) *reverseProxy {
	return &reverseProxy{
		clusterId,
		host,
		httputil.NewSingleHostReverseProxy(&url.URL{
			Scheme: "http",
			Host:   host,
		}),
	}
}

type ProxyHandler struct {
	mu      *sync.RWMutex
	proxies map[int64]*reverseProxy
	az      az.Az
	ds      *data.Datastore
}

func NewProxyHandler(az az.Az, ds *data.Datastore) *ProxyHandler {
	return &ProxyHandler{
		&sync.RWMutex{},
		make(map[int64]*reverseProxy),
		az,
		ds,
	}
}

func (pm *ProxyHandler) getOrCreateReverseProxy(clusterId int64, host string) *reverseProxy {
	pm.mu.RLock()
	rp, ok := pm.proxies[clusterId]
	pm.mu.RUnlock()

	if ok {
		return rp
	}

	rp = newReverseProxy(clusterId, host)
	pm.mu.Lock()
	pm.proxies[clusterId] = rp
	pm.mu.Unlock()
	return rp
}

func (pm *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	// All proxy requests require a header with key=X-Cluster; value=cluster-id (in)

	clusterHeader := r.Header.Get("X-Cluster")
	if clusterHeader == "" {
		clusterId := r.URL.Query().Get("cluster_id")
		if r.URL.Path == "/flow/" && clusterId != "" {
			r.Header.Set("X-Cluster", clusterId)
			clusterHeader = clusterId
		} else {
			http.Error(w, "Cluster requests via Steam requires a valid X-Cluster HTTP header", http.StatusBadRequest)
			return
		}
	}

	clusterId, err := strconv.ParseInt(clusterHeader, 10, 64)
	if err != nil {
		http.Error(w, "Invalid X-Cluster HTTP header, expected integer", http.StatusBadRequest)
		return
	}

	// Identify the principal

	pz, azerr := pm.az.Identify(r)
	if azerr != nil {
		http.Error(w, azerr.Error(), http.StatusForbidden)
		return
	}

	// Check if principal is allowed to use clusters

	if err := pz.CheckPermission(pm.ds.Permissions.ViewCluster); err != nil {
		http.Error(w, err.Error(), http.StatusForbidden)
		return
	}

	// Read cluster from database.
	// This also checks if the principal has privileges to view this specific cluster.

	cluster, err := pm.ds.ReadCluster(pz, clusterId)
	if err != nil {
		http.Error(w, err.Error(), http.StatusForbidden)
		return
	}

	// Get existing proxy, or create one if missing.

	rp := pm.getOrCreateReverseProxy(clusterId, cluster.Address)

	// Forward

	rp.proxy.ServeHTTP(w, r)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
)

//TODO: rename this to tempArff or something.
const outputFile = "solves.arff"

type appOptions struct {
	inFile  string
	outFile string
	help    bool
	flagSet *flag.FlagSet
}

/*

#Instructions:
# * https://weka.wikispaces.com/Primer
# * https://weka.wikispaces.com/How+to+run+WEKA+schemes+from+commandline

#This program assumes that Weka is installed in /Applications

# Convert the provided CSV to arff, capture output, delete the arff.

# java -cp "/Applications/weka-3-6-11-oracle-jvm.app/Contents/Java/weka.jar" weka.core.converters.CSVLoader solves.csv > solves.arff
# java <CLASSPATH> weka.classifiers.functions.SMOreg -C 1.0 -N 2 -I "weka.classifiers.functions.supportVector.RegSMOImproved -L 0.001 -W 1 -P 1.0E-12 -T 0.001 -V" -K "weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0" -c first -i <ARFF FILE>

#java -cp "/Applications/weka-3-6-11-oracle-jvm.app/Contents/Java/weka.jar" weka.classifiers.functions.SMOreg -C 1.0 -N 2 -I "weka.classifiers.functions.supportVector.RegSMOImproved -L 0.001 -W 1 -P 1.0E-12 -T 0.001 -V" -K "weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0" -c first -i -t solves.arff

*/

func (a *appOptions) defineFlags() {
	if a.flagSet == nil {
		return
	}
	a.flagSet.StringVar(&a.inFile, "i", "solves.csv", "Which file to read from")
	a.flagSet.StringVar(&a.outFile, "o", "analysis.txt", "Which file to output analysis to")
	a.flagSet.BoolVar(&a.help, "h", false, "If provided, will print help and exit.")
}

func (a *appOptions) parse(args []string) {
	a.flagSet.Parse(args)
}

func newAppOptions(flagSet *flag.FlagSet) *appOptions {
	a := &appOptions{
		flagSet: flagSet,
	}
	a.defineFlags()
	return a
}

func main() {

	options := newAppOptions(flag.CommandLine)
	options.parse(os.Args[1:])

	if options.help {
		options.flagSet.PrintDefaults()
		return
	}

	//TODO: allow configuring just a relativedifficulties file and run the whole pipeline

	//First, convert the file to arff.

	cmd := execJavaCommand("weka.core.converters.CSVLoader", options.inFile)

	out, err := os.Create(outputFile)

	if err != nil {
		fmt.Println(err)
		return
	}

	cmd.Stdout = out

	err = cmd.Run()

	if err != nil {
		fmt.Println(err)
		return
	}

	//Do the training
	trainCmd := execJavaCommand("weka.classifiers.functions.SMOreg",
		"-C", "1.0", "-N", "2", "-I", `weka.classifiers.functions.supportVector.RegSMOImproved -L 0.001 -W 1 -P 1.0E-12 -T 0.001 -V`,
		"-K", `weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0`, "-c", "first", "-i", "-t", "solves.arff")

	trainCmd.Stderr = os.Stderr

	output, err := trainCmd.Output()

	if err != nil {
		fmt.Println(err)
		return
	}

	//TODO: extract the r2 for comparison.

	ioutil.WriteFile(options.outFile, output, 0644)

	//Remove the temporary arff file.
	os.Remove(outputFile)

}

func execJavaCommand(input ...string) *exec.Cmd {

	var args []string
	args = append(args, "-cp")
	args = append(args, "/Applications/weka-3-6-11-oracle-jvm.app/Contents/Java/weka.jar")
	args = append(args, input...)

	return exec.Command("java", args...)
}
<commit_msg>Changed name of const in weka tool for clarity<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
)

const temporaryArff = "solves.arff"

type appOptions struct {
	inFile  string
	outFile string
	help    bool
	flagSet *flag.FlagSet
}

/*

#Instructions:
# * https://weka.wikispaces.com/Primer
# * https://weka.wikispaces.com/How+to+run+WEKA+schemes+from+commandline

#This program assumes that Weka is installed in /Applications

# Convert the provided CSV to arff, capture output, delete the arff.

# java -cp "/Applications/weka-3-6-11-oracle-jvm.app/Contents/Java/weka.jar" weka.core.converters.CSVLoader solves.csv > solves.arff
# java <CLASSPATH> weka.classifiers.functions.SMOreg -C 1.0 -N 2 -I "weka.classifiers.functions.supportVector.RegSMOImproved -L 0.001 -W 1 -P 1.0E-12 -T 0.001 -V" -K "weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0" -c first -i <ARFF FILE>

#java -cp "/Applications/weka-3-6-11-oracle-jvm.app/Contents/Java/weka.jar" weka.classifiers.functions.SMOreg -C 1.0 -N 2 -I "weka.classifiers.functions.supportVector.RegSMOImproved -L 0.001 -W 1 -P 1.0E-12 -T 0.001 -V" -K "weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0" -c first -i -t solves.arff

*/

func (a *appOptions) defineFlags() {
	if a.flagSet == nil {
		return
	}
	a.flagSet.StringVar(&a.inFile, "i", "solves.csv", "Which file to read from")
	a.flagSet.StringVar(&a.outFile, "o", "analysis.txt", "Which file to output analysis to")
	a.flagSet.BoolVar(&a.help, "h", false, "If provided, will print help and exit.")
}

func (a *appOptions) parse(args []string) {
	a.flagSet.Parse(args)
}

func newAppOptions(flagSet *flag.FlagSet) *appOptions {
	a := &appOptions{
		flagSet: flagSet,
	}
	a.defineFlags()
	return a
}

func main() {

	options := newAppOptions(flag.CommandLine)
	options.parse(os.Args[1:])

	if options.help {
		options.flagSet.PrintDefaults()
		return
	}

	//TODO: allow configuring just a relativedifficulties file and run the whole pipeline

	//First, convert the file to arff.

	cmd := execJavaCommand("weka.core.converters.CSVLoader", options.inFile)

	out, err := os.Create(temporaryArff)

	if err != nil {
		fmt.Println(err)
		return
	}

	cmd.Stdout = out

	err = cmd.Run()

	if err != nil {
		fmt.Println(err)
		return
	}

	//Do the training
	trainCmd := execJavaCommand("weka.classifiers.functions.SMOreg",
		"-C", "1.0", "-N", "2", "-I", `weka.classifiers.functions.supportVector.RegSMOImproved -L 0.001 -W 1 -P 1.0E-12 -T 0.001 -V`,
		"-K", `weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0`, "-c", "first", "-i", "-t", "solves.arff")

	trainCmd.Stderr = os.Stderr

	output, err := trainCmd.Output()

	if err != nil {
		fmt.Println(err)
		return
	}

	//TODO: extract the r2 for comparison.

	ioutil.WriteFile(options.outFile, output, 0644)

	//Remove the temporary arff file.
	os.Remove(temporaryArff)

}

func execJavaCommand(input ...string) *exec.Cmd {

	var args []string
	args = append(args, "-cp")
	args = append(args, "/Applications/weka-3-6-11-oracle-jvm.app/Contents/Java/weka.jar")
	args = append(args, input...)

	return exec.Command("java", args...)
}
<|endoftext|>


<commit_before>package instagram

import (
	"strings"
	"time"

	"sync"

	"net/url"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Seklfreak/Robyul2/metrics"
	"github.com/Seklfreak/Robyul2/models"
	"github.com/globalsign/mgo/bson"
)

var (
	instagramEntryLocks = make(map[string]*sync.Mutex)
)

const (
	InstagramGraphQlWorkers = 15
)

func (m *Handler) checkInstagramPublicFeedLoop() {
	log := cache.GetLogger().WithField("module", "instagram")

	defer helpers.Recover()
	defer func() {
		go func() {
			defer helpers.Recover()
			log.Error("The checkInstagramPublicFeedLoop died." +
				"Please investigate! Will be restarted in 60 seconds")
			time.Sleep(60 * time.Second)
			m.checkInstagramPublicFeedLoop()
		}()
	}()

	for {
		bundledEntries, entriesCount, err := m.getBundledEntries()
		helpers.Relax(err)

		log.Infof(
			"checking graphql feed on %d accounts for %d feeds with %d workers",
			len(bundledEntries), entriesCount, InstagramGraphQlWorkers)
		start := time.Now()

		jobs := make(chan map[string][]models.InstagramEntry, 0)
		results := make(chan int, 0)

		workerEntries := make(map[int]map[string][]models.InstagramEntry, 0)
		for w := 1; w <= InstagramGraphQlWorkers; w++ {
			go m.checkInstagramPublicFeedLoopWorker(w, jobs, results)
			workerEntries[w] = make(map[string][]models.InstagramEntry)
		}

		lastWorker := 1
		for code, codeEntries := range bundledEntries {
			workerEntries[lastWorker][code] = codeEntries
			lastWorker++
			if lastWorker > InstagramGraphQlWorkers {
				lastWorker = 1
			}
		}

		for _, workerEntry := range workerEntries {
			jobs <- workerEntry
		}
		close(jobs)

		for a := 1; a <= InstagramGraphQlWorkers; a++ {
			<-results
		}
		elapsed := time.Since(start)
		log.Infof(
			"checked graphql feed on %d accounts for %d feeds with %d workers, took %s",
			len(bundledEntries), entriesCount, InstagramGraphQlWorkers, elapsed)
		metrics.InstagramGraphQlFeedRefreshTime.Set(elapsed.Seconds())

		if entriesCount <= 10 {
			time.Sleep(60 * time.Second)
		}
	}
}

func (m *Handler) checkInstagramPublicFeedLoopWorker(id int, jobs <-chan map[string][]models.InstagramEntry, results chan<- int) {
	defer func() {
		helpers.Recover()
		if results != nil && jobs != nil {
			results <- len(jobs)
		}
	}()

	currentProxy, err := helpers.GetRandomProxy()
	helpers.Relax(err)

	for job := range jobs {
		//cache.GetLogger().WithField("module", "instagram").WithField("worker", id).Infof(
		//	"worker %d started for %d accounts", id, len(job))
	NextEntry:
		for instagramUsername, entries := range job {
			//cache.GetLogger().WithField("module", "instagram").WithField("worker", id).Infof(
			//	"checking graphql feed for %d for %d channels", instagramAccountID, len(entries))
		RetryGraphQl:
			_, receivedPosts, err := m.getInformationAndPosts(instagramUsername, currentProxy)
			if err != nil {
				if strings.Contains(err.Error(), "expected status 200; got 404") {
					// account got deleted/username got changed
					continue NextEntry
				}
				if m.retryOnError(err) {
					//cache.GetLogger().WithField("module", "instagram").Infof(
					//	"proxy error connecting to Instagram Account %s (GraphQL), "+
					//		"waiting 5 seconds, switching proxy and then trying again", instagramAccountID)
					time.Sleep(5 * time.Second)
					currentProxy, err = helpers.GetRandomProxy()
					helpers.Relax(err)
					goto RetryGraphQl
				}
				helpers.RelaxLog(err)
				continue NextEntry
			}

			for _, receivedPost := range receivedPosts {
				postHasBeenPostedEverywhere := true
				for _, entry := range entries {
					postAlreadyPosted := false
					if receivedPost.CreatedAt.Before(entry.LastPostCheck) {
						postAlreadyPosted = true
					}
					if !postAlreadyPosted {
						postHasBeenPostedEverywhere = false
					}
				}

				if postHasBeenPostedEverywhere {
					continue
				}

				// download specific post data
			RetryPost:
				post, err := m.getPostInformation(receivedPost.Shortcode, currentProxy)
				if err != nil {
					if m.retryOnError(err) {
						//cache.GetLogger().WithField("module", "instagram").Infof(
						//	"hit rate limit checking Instagram Account %s (GraphQL), "+
						//		"waiting 5 seconds, switching proxy and then trying again", instagramAccountID)
						time.Sleep(5 * time.Second)
						currentProxy, err = helpers.GetRandomProxy()
						helpers.Relax(err)
						goto RetryPost
					}
					if strings.Contains(err.Error(), "expected status 200; got 404") {
						// post got deleted
						continue
					}
					helpers.RelaxLog(err)
					continue NextEntry
				}

				for _, entry := range entries {
					entryID := entry.ID
					m.lockEntry(entryID)

					var entry models.InstagramEntry
					err = helpers.MdbOneWithoutLogging(
						helpers.MdbCollection(models.InstagramTable).Find(bson.M{"_id": entryID}),
						&entry,
					)

					if entry.LastPostCheck.IsZero() { // prevent spam
						entry.LastPostCheck = time.Now()
					}

					if err != nil {
						m.unlockEntry(entryID)
						helpers.RelaxLog(err)
						continue
					}

					postAlreadyPosted := false
					if receivedPost.CreatedAt.Before(entry.LastPostCheck) {
						postAlreadyPosted = true
					}

					if postAlreadyPosted == false {
						cache.GetLogger().WithField("module", "instagram").Infof("Posting Post (GraphQL): #%s", post.ID)
						go m.postPostToChannel(entry.ChannelID, post, entry.SendPostType)
					}

					entry.LastPostCheck = time.Now()
					err = helpers.MDbUpdateWithoutLogging(models.InstagramTable, entry.ID, entry)
					if err != nil {
						m.unlockEntry(entryID)
						helpers.RelaxLog(err)
						continue
					}

					m.unlockEntry(entryID)
				}
			}
		}
	}
	results <- len(jobs)
}

/*
func (m *Handler) checkInstagramStoryLoop() {
	log := cache.GetLogger()

	defer helpers.Recover()
	defer func() {
		go func() {
			log.WithField("module", "instagram").Error("The checkInstagramStoryLoop died." +
				"Please investigate! Will be restarted in 60 seconds")
			time.Sleep(60 * time.Second)
			m.checkInstagramStoryLoop()
		}()
	}()

	for {
		bundledEntries, entriesCount, err := m.getBundledEntries()
		helpers.Relax(err)

		cache.GetLogger().WithField("module", "instagram").Infof(
			"checking story on %d accounts for %d feeds", len(bundledEntries), entriesCount)
		start := time.Now()

		for instagramAccountID, entries := range bundledEntries {
		RetryAccount:
			// log.WithField("module", "instagram").Debug(fmt.Sprintf("checking Instagram Account @%s", instagramUsername))

			var posts goinstaResponse.UserFeedResponse
			userIdInt, err := strconv.Atoi(instagramAccountID)
			helpers.Relax(err)
			story, err := instagramClient.GetUserStories(int64(userIdInt))
			if err != nil || story.Status != "ok" {
				if m.retryOnError(err) {
					cache.GetLogger().WithField("module", "instagram").Infof(
						"hit rate limit checking Instagram Account (Stories) %s, "+
							"sleeping for 20 seconds and then trying again", instagramAccountID)
					time.Sleep(20 * time.Second)
					goto RetryAccount
				}
				log.WithField("module", "instagram").Warnf(
					"updating instagram account %s (Story) failed: %s", instagramAccountID, err)
				continue
			}

			// https://github.com/golang/go/wiki/SliceTricks#reversing
			for i := len(posts.Items)/2 - 1; i >= 0; i-- {
				opp := len(posts.Items) - 1 - i
				posts.Items[i], posts.Items[opp] = posts.Items[opp], posts.Items[i]
			}
			for i := len(story.Reel.Items)/2 - 1; i >= 0; i-- {
				opp := len(story.Reel.Items) - 1 - i
				story.Reel.Items[i], story.Reel.Items[opp] = story.Reel.Items[opp], story.Reel.Items[i]
			}

			for _, entry := range entries {
				changes := false

				entryID := entry.ID
				m.lockEntry(entryID)

				var entry models.InstagramEntry
				err = helpers.MdbOne(
					helpers.MdbCollection(models.InstagramTable).Find(bson.M{"_id": entryID}),
					&entry,
				)
				if err != nil {
					m.unlockEntry(entryID)
					if !strings.Contains(err.Error(), "The result does not contain any more rows") {
						helpers.RelaxLog(err)
					}
					continue
				}

				for n, reelMedia := range story.Reel.Items {
					reelMediaAlreadyPosted := false
					for _, reelMediaPostPosted := range entry.PostedPosts {
						if reelMediaPostPosted.Type == models.InstagramPostTypeReel && reelMediaPostPosted.ID == reelMedia.ID {
							reelMediaAlreadyPosted = true
						}
					}
					if reelMediaAlreadyPosted == false {
						log.WithField("module", "instagram").Infof(
							"Posting Reel Media (Story): #%s", reelMedia.ID)
						entry.PostedPosts = append(entry.PostedPosts,
							models.InstagramPostEntry{
								ID:            reelMedia.ID,
								Type:          models.InstagramPostTypeReel,
								CreatedAtTime: time.Unix(int64(reelMedia.DeviceTimestamp), 0),
							})
						changes = true
						go m.postReelMediaToChannel(entry.ChannelID, story, n, entry.SendPostType)
					}
				}

				if changes == true {
					err = helpers.MDbUpdate(models.InstagramTable, entry.ID, entry)
					if err != nil {
						m.unlockEntry(entryID)
						helpers.RelaxLog(err)
						continue
					}
				}

				m.unlockEntry(entryID)
			}
			time.Sleep(1 * time.Second)
		}

		elapsed := time.Since(start)
		cache.GetLogger().WithField("module", "instagram").Infof(
			"checked story on %d accounts for %d feeds, took %s",
			len(bundledEntries), entriesCount, elapsed)
		metrics.InstagramRefreshTime.Set(elapsed.Seconds())

		if entriesCount <= 10 {
			time.Sleep(30 * time.Second)
		}
	}
}
*/

func (m *Handler) lockEntry(entryID bson.ObjectId) {
	if _, ok := instagramEntryLocks[string(entryID)]; ok {
		instagramEntryLocks[string(entryID)].Lock()
		return
	}
	instagramEntryLocks[string(entryID)] = new(sync.Mutex)
	instagramEntryLocks[string(entryID)].Lock()
}

func (m *Handler) unlockEntry(entryID bson.ObjectId) {
	if _, ok := instagramEntryLocks[string(entryID)]; ok {
		instagramEntryLocks[string(entryID)].Unlock()
	}
}

func (m *Handler) retryOnError(err error) (retry bool) {
	if err != nil {
		if _, ok := err.(*url.Error); ok ||
			strings.Contains(err.Error(), "net/http") ||
			strings.Contains(err.Error(), "expected status 200; got 429") ||
			strings.Contains(err.Error(), "Please wait a few minutes before you try again.") ||
			strings.Contains(err.Error(), "expected status 200; got 500") ||
			strings.Contains(err.Error(), "expected status 200; got 502") {
			return true
		}
	}
	return false
}
<commit_msg>[instagram] switch proxy on 503 error 💣<commit_after>package instagram

import (
	"strings"
	"time"

	"sync"

	"net/url"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Seklfreak/Robyul2/metrics"
	"github.com/Seklfreak/Robyul2/models"
	"github.com/globalsign/mgo/bson"
)

var (
	instagramEntryLocks = make(map[string]*sync.Mutex)
)

const (
	InstagramGraphQlWorkers = 15
)

func (m *Handler) checkInstagramPublicFeedLoop() {
	log := cache.GetLogger().WithField("module", "instagram")

	defer helpers.Recover()
	defer func() {
		go func() {
			defer helpers.Recover()
			log.Error("The checkInstagramPublicFeedLoop died." +
				"Please investigate! Will be restarted in 60 seconds")
			time.Sleep(60 * time.Second)
			m.checkInstagramPublicFeedLoop()
		}()
	}()

	for {
		bundledEntries, entriesCount, err := m.getBundledEntries()
		helpers.Relax(err)

		log.Infof(
			"checking graphql feed on %d accounts for %d feeds with %d workers",
			len(bundledEntries), entriesCount, InstagramGraphQlWorkers)
		start := time.Now()

		jobs := make(chan map[string][]models.InstagramEntry, 0)
		results := make(chan int, 0)

		workerEntries := make(map[int]map[string][]models.InstagramEntry, 0)
		for w := 1; w <= InstagramGraphQlWorkers; w++ {
			go m.checkInstagramPublicFeedLoopWorker(w, jobs, results)
			workerEntries[w] = make(map[string][]models.InstagramEntry)
		}

		lastWorker := 1
		for code, codeEntries := range bundledEntries {
			workerEntries[lastWorker][code] = codeEntries
			lastWorker++
			if lastWorker > InstagramGraphQlWorkers {
				lastWorker = 1
			}
		}

		for _, workerEntry := range workerEntries {
			jobs <- workerEntry
		}
		close(jobs)

		for a := 1; a <= InstagramGraphQlWorkers; a++ {
			<-results
		}
		elapsed := time.Since(start)
		log.Infof(
			"checked graphql feed on %d accounts for %d feeds with %d workers, took %s",
			len(bundledEntries), entriesCount, InstagramGraphQlWorkers, elapsed)
		metrics.InstagramGraphQlFeedRefreshTime.Set(elapsed.Seconds())

		if entriesCount <= 10 {
			time.Sleep(60 * time.Second)
		}
	}
}

func (m *Handler) checkInstagramPublicFeedLoopWorker(id int, jobs <-chan map[string][]models.InstagramEntry, results chan<- int) {
	defer func() {
		helpers.Recover()
		if results != nil && jobs != nil {
			results <- len(jobs)
		}
	}()

	currentProxy, err := helpers.GetRandomProxy()
	helpers.Relax(err)

	for job := range jobs {
		//cache.GetLogger().WithField("module", "instagram").WithField("worker", id).Infof(
		//	"worker %d started for %d accounts", id, len(job))
	NextEntry:
		for instagramUsername, entries := range job {
			//cache.GetLogger().WithField("module", "instagram").WithField("worker", id).Infof(
			//	"checking graphql feed for %d for %d channels", instagramAccountID, len(entries))
		RetryGraphQl:
			_, receivedPosts, err := m.getInformationAndPosts(instagramUsername, currentProxy)
			if err != nil {
				if strings.Contains(err.Error(), "expected status 200; got 404") {
					// account got deleted/username got changed
					continue NextEntry
				}
				if m.retryOnError(err) {
					//cache.GetLogger().WithField("module", "instagram").Infof(
					//	"proxy error connecting to Instagram Account %s (GraphQL), "+
					//		"waiting 5 seconds, switching proxy and then trying again", instagramAccountID)
					time.Sleep(5 * time.Second)
					currentProxy, err = helpers.GetRandomProxy()
					helpers.Relax(err)
					goto RetryGraphQl
				}
				helpers.RelaxLog(err)
				continue NextEntry
			}

			for _, receivedPost := range receivedPosts {
				postHasBeenPostedEverywhere := true
				for _, entry := range entries {
					postAlreadyPosted := false
					if receivedPost.CreatedAt.Before(entry.LastPostCheck) {
						postAlreadyPosted = true
					}
					if !postAlreadyPosted {
						postHasBeenPostedEverywhere = false
					}
				}

				if postHasBeenPostedEverywhere {
					continue
				}

				// download specific post data
			RetryPost:
				post, err := m.getPostInformation(receivedPost.Shortcode, currentProxy)
				if err != nil {
					if m.retryOnError(err) {
						//cache.GetLogger().WithField("module", "instagram").Infof(
						//	"hit rate limit checking Instagram Account %s (GraphQL), "+
						//		"waiting 5 seconds, switching proxy and then trying again", instagramAccountID)
						time.Sleep(5 * time.Second)
						currentProxy, err = helpers.GetRandomProxy()
						helpers.Relax(err)
						goto RetryPost
					}
					if strings.Contains(err.Error(), "expected status 200; got 404") {
						// post got deleted
						continue
					}
					helpers.RelaxLog(err)
					continue NextEntry
				}

				for _, entry := range entries {
					entryID := entry.ID
					m.lockEntry(entryID)

					var entry models.InstagramEntry
					err = helpers.MdbOneWithoutLogging(
						helpers.MdbCollection(models.InstagramTable).Find(bson.M{"_id": entryID}),
						&entry,
					)

					if entry.LastPostCheck.IsZero() { // prevent spam
						entry.LastPostCheck = time.Now()
					}

					if err != nil {
						m.unlockEntry(entryID)
						helpers.RelaxLog(err)
						continue
					}

					postAlreadyPosted := false
					if receivedPost.CreatedAt.Before(entry.LastPostCheck) {
						postAlreadyPosted = true
					}

					if postAlreadyPosted == false {
						cache.GetLogger().WithField("module", "instagram").Infof("Posting Post (GraphQL): #%s", post.ID)
						go m.postPostToChannel(entry.ChannelID, post, entry.SendPostType)
					}

					entry.LastPostCheck = time.Now()
					err = helpers.MDbUpdateWithoutLogging(models.InstagramTable, entry.ID, entry)
					if err != nil {
						m.unlockEntry(entryID)
						helpers.RelaxLog(err)
						continue
					}

					m.unlockEntry(entryID)
				}
			}
		}
	}
	results <- len(jobs)
}

/*
func (m *Handler) checkInstagramStoryLoop() {
	log := cache.GetLogger()

	defer helpers.Recover()
	defer func() {
		go func() {
			log.WithField("module", "instagram").Error("The checkInstagramStoryLoop died." +
				"Please investigate! Will be restarted in 60 seconds")
			time.Sleep(60 * time.Second)
			m.checkInstagramStoryLoop()
		}()
	}()

	for {
		bundledEntries, entriesCount, err := m.getBundledEntries()
		helpers.Relax(err)

		cache.GetLogger().WithField("module", "instagram").Infof(
			"checking story on %d accounts for %d feeds", len(bundledEntries), entriesCount)
		start := time.Now()

		for instagramAccountID, entries := range bundledEntries {
		RetryAccount:
			// log.WithField("module", "instagram").Debug(fmt.Sprintf("checking Instagram Account @%s", instagramUsername))

			var posts goinstaResponse.UserFeedResponse
			userIdInt, err := strconv.Atoi(instagramAccountID)
			helpers.Relax(err)
			story, err := instagramClient.GetUserStories(int64(userIdInt))
			if err != nil || story.Status != "ok" {
				if m.retryOnError(err) {
					cache.GetLogger().WithField("module", "instagram").Infof(
						"hit rate limit checking Instagram Account (Stories) %s, "+
							"sleeping for 20 seconds and then trying again", instagramAccountID)
					time.Sleep(20 * time.Second)
					goto RetryAccount
				}
				log.WithField("module", "instagram").Warnf(
					"updating instagram account %s (Story) failed: %s", instagramAccountID, err)
				continue
			}

			// https://github.com/golang/go/wiki/SliceTricks#reversing
			for i := len(posts.Items)/2 - 1; i >= 0; i-- {
				opp := len(posts.Items) - 1 - i
				posts.Items[i], posts.Items[opp] = posts.Items[opp], posts.Items[i]
			}
			for i := len(story.Reel.Items)/2 - 1; i >= 0; i-- {
				opp := len(story.Reel.Items) - 1 - i
				story.Reel.Items[i], story.Reel.Items[opp] = story.Reel.Items[opp], story.Reel.Items[i]
			}

			for _, entry := range entries {
				changes := false

				entryID := entry.ID
				m.lockEntry(entryID)

				var entry models.InstagramEntry
				err = helpers.MdbOne(
					helpers.MdbCollection(models.InstagramTable).Find(bson.M{"_id": entryID}),
					&entry,
				)
				if err != nil {
					m.unlockEntry(entryID)
					if !strings.Contains(err.Error(), "The result does not contain any more rows") {
						helpers.RelaxLog(err)
					}
					continue
				}

				for n, reelMedia := range story.Reel.Items {
					reelMediaAlreadyPosted := false
					for _, reelMediaPostPosted := range entry.PostedPosts {
						if reelMediaPostPosted.Type == models.InstagramPostTypeReel && reelMediaPostPosted.ID == reelMedia.ID {
							reelMediaAlreadyPosted = true
						}
					}
					if reelMediaAlreadyPosted == false {
						log.WithField("module", "instagram").Infof(
							"Posting Reel Media (Story): #%s", reelMedia.ID)
						entry.PostedPosts = append(entry.PostedPosts,
							models.InstagramPostEntry{
								ID:            reelMedia.ID,
								Type:          models.InstagramPostTypeReel,
								CreatedAtTime: time.Unix(int64(reelMedia.DeviceTimestamp), 0),
							})
						changes = true
						go m.postReelMediaToChannel(entry.ChannelID, story, n, entry.SendPostType)
					}
				}

				if changes == true {
					err = helpers.MDbUpdate(models.InstagramTable, entry.ID, entry)
					if err != nil {
						m.unlockEntry(entryID)
						helpers.RelaxLog(err)
						continue
					}
				}

				m.unlockEntry(entryID)
			}
			time.Sleep(1 * time.Second)
		}

		elapsed := time.Since(start)
		cache.GetLogger().WithField("module", "instagram").Infof(
			"checked story on %d accounts for %d feeds, took %s",
			len(bundledEntries), entriesCount, elapsed)
		metrics.InstagramRefreshTime.Set(elapsed.Seconds())

		if entriesCount <= 10 {
			time.Sleep(30 * time.Second)
		}
	}
}
*/

func (m *Handler) lockEntry(entryID bson.ObjectId) {
	if _, ok := instagramEntryLocks[string(entryID)]; ok {
		instagramEntryLocks[string(entryID)].Lock()
		return
	}
	instagramEntryLocks[string(entryID)] = new(sync.Mutex)
	instagramEntryLocks[string(entryID)].Lock()
}

func (m *Handler) unlockEntry(entryID bson.ObjectId) {
	if _, ok := instagramEntryLocks[string(entryID)]; ok {
		instagramEntryLocks[string(entryID)].Unlock()
	}
}

func (m *Handler) retryOnError(err error) (retry bool) {
	if err != nil {
		if _, ok := err.(*url.Error); ok ||
			strings.Contains(err.Error(), "net/http") ||
			strings.Contains(err.Error(), "expected status 200; got 429") ||
			strings.Contains(err.Error(), "Please wait a few minutes before you try again.") ||
			strings.Contains(err.Error(), "expected status 200; got 500") ||
			strings.Contains(err.Error(), "expected status 200; got 502") ||
			strings.Contains(err.Error(), "expected status 200; got 503") {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>/*
 * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
 *
 * 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.
 *
 * @author		Aeneas Rekkas <aeneas+oss@aeneas.io>
 * @copyright 	2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
 * @license 	Apache-2.0
 *
 */

package oauth2

import (
	"fmt"
	"net/url"
	"testing"
	"time"

	"github.com/golang/mock/gomock"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/ory/fosite"
	"github.com/ory/fosite/internal"
)

func TestResourceOwnerFlow_HandleTokenEndpointRequest(t *testing.T) {
	ctrl := gomock.NewController(t)
	store := internal.NewMockResourceOwnerPasswordCredentialsGrantStorage(ctrl)
	defer ctrl.Finish()

	areq := fosite.NewAccessRequest(new(fosite.DefaultSession))
	areq.Form = url.Values{}

	h := ResourceOwnerPasswordCredentialsGrantHandler{
		ResourceOwnerPasswordCredentialsGrantStorage: store,
		HandleHelper: &HandleHelper{
			AccessTokenStorage:   store,
			AccessTokenLifespan:  time.Hour,
			RefreshTokenLifespan: time.Hour,
		},
		ScopeStrategy:            fosite.HierarchicScopeStrategy,
		AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy,
	}
	for k, c := range []struct {
		description string
		setup       func()
		expectErr   error
		check       func(areq *fosite.AccessRequest)
	}{
		{
			description: "should fail because not responsible",
			expectErr:   fosite.ErrUnknownRequest,
			setup: func() {
				areq.GrantTypes = fosite.Arguments{"123"}
			},
		},
		{
			description: "should fail because scope missing",
			setup: func() {
				areq.GrantTypes = fosite.Arguments{"password"}
				areq.Client = &fosite.DefaultClient{GrantTypes: fosite.Arguments{"password"}, Scopes: []string{}}
				areq.RequestedScope = []string{"foo-scope"}
			},
			expectErr: fosite.ErrInvalidScope,
		},
		{
			description: "should fail because audience missing",
			setup: func() {
				areq.RequestedAudience = fosite.Arguments{"https://www.ory.sh/api"}
				areq.Client = &fosite.DefaultClient{GrantTypes: fosite.Arguments{"password"}, Scopes: []string{"foo-scope"}}
			},
			expectErr: fosite.ErrInvalidRequest,
		},
		{
			description: "should fail because invalid grant_type specified",
			setup: func() {
				areq.GrantTypes = fosite.Arguments{"password"}
				areq.Client = &fosite.DefaultClient{GrantTypes: fosite.Arguments{"authoriation_code"}, Scopes: []string{"foo-scope"}}
			},
			expectErr: fosite.ErrUnauthorizedClient,
		},
		{
			description: "should fail because invalid credentials",
			setup: func() {
				areq.Form.Set("username", "peter")
				areq.Form.Set("password", "pan")
				areq.Client = &fosite.DefaultClient{GrantTypes: fosite.Arguments{"password"}, Scopes: []string{"foo-scope"}, Audience: []string{"https://www.ory.sh/api"}}

				store.EXPECT().Authenticate(nil, "peter", "pan").Return(fosite.ErrNotFound)
			},
			expectErr: fosite.ErrInvalidGrant,
		},
		{
			description: "should fail because error on lookup",
			setup: func() {
				store.EXPECT().Authenticate(nil, "peter", "pan").Return(errors.New(""))
			},
			expectErr: fosite.ErrServerError,
		},
		{
			description: "should pass",
			setup: func() {
				store.EXPECT().Authenticate(nil, "peter", "pan").Return(nil)
			},
			check: func(areq *fosite.AccessRequest) {
				//assert.NotEmpty(t, areq.GetSession().GetExpiresAt(fosite.AccessToken))
				assert.Equal(t, time.Now().Add(time.Hour).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.AccessToken))
				assert.Equal(t, time.Now().Add(time.Hour).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.RefreshToken))
			},
		},
	} {
		t.Run(fmt.Sprintf("case=%d/description=%s", k, c.description), func(t *testing.T) {
			c.setup()
			err := h.HandleTokenEndpointRequest(nil, areq)

			if c.expectErr != nil {
				require.EqualError(t, err, c.expectErr.Error())
			} else {
				require.NoError(t, err)
				if c.check != nil {
					c.check(areq)
				}
			}
		})
	}
}

func TestResourceOwnerFlow_PopulateTokenEndpointResponse(t *testing.T) {
	ctrl := gomock.NewController(t)
	store := internal.NewMockResourceOwnerPasswordCredentialsGrantStorage(ctrl)
	chgen := internal.NewMockAccessTokenStrategy(ctrl)
	rtstr := internal.NewMockRefreshTokenStrategy(ctrl)
	mockAT := "accesstoken.foo.bar"
	mockRT := "refreshtoken.bar.foo"
	defer ctrl.Finish()

	var areq *fosite.AccessRequest
	var aresp *fosite.AccessResponse
	var h ResourceOwnerPasswordCredentialsGrantHandler

	for k, c := range []struct {
		description string
		setup       func()
		expectErr   error
		expect      func()
	}{
		{
			description: "should fail because not responsible",
			expectErr:   fosite.ErrUnknownRequest,
			setup: func() {
				areq.GrantTypes = fosite.Arguments{""}
			},
		},
		{
			description: "should pass",
			setup: func() {
				areq.GrantTypes = fosite.Arguments{"password"}
				chgen.EXPECT().GenerateAccessToken(nil, areq).Return(mockAT, "bar", nil)
				store.EXPECT().CreateAccessTokenSession(nil, "bar", gomock.Eq(areq.Sanitize([]string{}))).Return(nil)
			},
			expect: func() {
				assert.Nil(t, aresp.GetExtra("refresh_token"), "unexpected refresh token")
			},
		},
		{
			description: "should pass - offline scope",
			setup: func() {
				areq.GrantTypes = fosite.Arguments{"password"}
				areq.GrantScope("offline")
				rtstr.EXPECT().GenerateRefreshToken(nil, areq).Return(mockRT, "bar", nil)
				store.EXPECT().CreateRefreshTokenSession(nil, "bar", gomock.Eq(areq.Sanitize([]string{}))).Return(nil)
				chgen.EXPECT().GenerateAccessToken(nil, areq).Return(mockAT, "bar", nil)
				store.EXPECT().CreateAccessTokenSession(nil, "bar", gomock.Eq(areq.Sanitize([]string{}))).Return(nil)
			},
			expect: func() {
				assert.NotNil(t, aresp.GetExtra("refresh_token"), "expected refresh token")
			},
		},
		{
			description: "should pass - refresh token without offline scope",
			setup: func() {
				h.RefreshTokenScopes = []string{}
				areq.GrantTypes = fosite.Arguments{"password"}
				rtstr.EXPECT().GenerateRefreshToken(nil, areq).Return(mockRT, "bar", nil)
				store.EXPECT().CreateRefreshTokenSession(nil, "bar", gomock.Eq(areq.Sanitize([]string{}))).Return(nil)
				chgen.EXPECT().GenerateAccessToken(nil, areq).Return(mockAT, "bar", nil)
				store.EXPECT().CreateAccessTokenSession(nil, "bar", gomock.Eq(areq.Sanitize([]string{}))).Return(nil)
			},
			expect: func() {
				assert.NotNil(t, aresp.GetExtra("refresh_token"), "expected refresh token")
			},
		},
	} {
		t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) {
			areq = fosite.NewAccessRequest(nil)
			aresp = fosite.NewAccessResponse()
			areq.Session = &fosite.DefaultSession{}
			h = ResourceOwnerPasswordCredentialsGrantHandler{
				ResourceOwnerPasswordCredentialsGrantStorage: store,
				HandleHelper: &HandleHelper{
					AccessTokenStorage:  store,
					AccessTokenStrategy: chgen,
					AccessTokenLifespan: time.Hour,
				},
				RefreshTokenStrategy: rtstr,
				RefreshTokenScopes:   []string{"offline"},
			}
			c.setup()
			err := h.PopulateTokenEndpointResponse(nil, areq, aresp)
			if c.expectErr != nil {
				require.EqualError(t, err, c.expectErr.Error())
			} else {
				require.NoError(t, err)
				if c.expect != nil {
					c.expect()
				}
			}
		})
	}
}
<commit_msg>chore: fix typo of authorization_code in test (#476)<commit_after>/*
 * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
 *
 * 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.
 *
 * @author		Aeneas Rekkas <aeneas+oss@aeneas.io>
 * @copyright 	2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
 * @license 	Apache-2.0
 *
 */

package oauth2

import (
	"fmt"
	"net/url"
	"testing"
	"time"

	"github.com/golang/mock/gomock"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/ory/fosite"
	"github.com/ory/fosite/internal"
)

func TestResourceOwnerFlow_HandleTokenEndpointRequest(t *testing.T) {
	ctrl := gomock.NewController(t)
	store := internal.NewMockResourceOwnerPasswordCredentialsGrantStorage(ctrl)
	defer ctrl.Finish()

	areq := fosite.NewAccessRequest(new(fosite.DefaultSession))
	areq.Form = url.Values{}

	h := ResourceOwnerPasswordCredentialsGrantHandler{
		ResourceOwnerPasswordCredentialsGrantStorage: store,
		HandleHelper: &HandleHelper{
			AccessTokenStorage:   store,
			AccessTokenLifespan:  time.Hour,
			RefreshTokenLifespan: time.Hour,
		},
		ScopeStrategy:            fosite.HierarchicScopeStrategy,
		AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy,
	}
	for k, c := range []struct {
		description string
		setup       func()
		expectErr   error
		check       func(areq *fosite.AccessRequest)
	}{
		{
			description: "should fail because not responsible",
			expectErr:   fosite.ErrUnknownRequest,
			setup: func() {
				areq.GrantTypes = fosite.Arguments{"123"}
			},
		},
		{
			description: "should fail because scope missing",
			setup: func() {
				areq.GrantTypes = fosite.Arguments{"password"}
				areq.Client = &fosite.DefaultClient{GrantTypes: fosite.Arguments{"password"}, Scopes: []string{}}
				areq.RequestedScope = []string{"foo-scope"}
			},
			expectErr: fosite.ErrInvalidScope,
		},
		{
			description: "should fail because audience missing",
			setup: func() {
				areq.RequestedAudience = fosite.Arguments{"https://www.ory.sh/api"}
				areq.Client = &fosite.DefaultClient{GrantTypes: fosite.Arguments{"password"}, Scopes: []string{"foo-scope"}}
			},
			expectErr: fosite.ErrInvalidRequest,
		},
		{
			description: "should fail because invalid grant_type specified",
			setup: func() {
				areq.GrantTypes = fosite.Arguments{"password"}
				areq.Client = &fosite.DefaultClient{GrantTypes: fosite.Arguments{"authorization_code"}, Scopes: []string{"foo-scope"}}
			},
			expectErr: fosite.ErrUnauthorizedClient,
		},
		{
			description: "should fail because invalid credentials",
			setup: func() {
				areq.Form.Set("username", "peter")
				areq.Form.Set("password", "pan")
				areq.Client = &fosite.DefaultClient{GrantTypes: fosite.Arguments{"password"}, Scopes: []string{"foo-scope"}, Audience: []string{"https://www.ory.sh/api"}}

				store.EXPECT().Authenticate(nil, "peter", "pan").Return(fosite.ErrNotFound)
			},
			expectErr: fosite.ErrInvalidGrant,
		},
		{
			description: "should fail because error on lookup",
			setup: func() {
				store.EXPECT().Authenticate(nil, "peter", "pan").Return(errors.New(""))
			},
			expectErr: fosite.ErrServerError,
		},
		{
			description: "should pass",
			setup: func() {
				store.EXPECT().Authenticate(nil, "peter", "pan").Return(nil)
			},
			check: func(areq *fosite.AccessRequest) {
				//assert.NotEmpty(t, areq.GetSession().GetExpiresAt(fosite.AccessToken))
				assert.Equal(t, time.Now().Add(time.Hour).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.AccessToken))
				assert.Equal(t, time.Now().Add(time.Hour).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.RefreshToken))
			},
		},
	} {
		t.Run(fmt.Sprintf("case=%d/description=%s", k, c.description), func(t *testing.T) {
			c.setup()
			err := h.HandleTokenEndpointRequest(nil, areq)

			if c.expectErr != nil {
				require.EqualError(t, err, c.expectErr.Error())
			} else {
				require.NoError(t, err)
				if c.check != nil {
					c.check(areq)
				}
			}
		})
	}
}

func TestResourceOwnerFlow_PopulateTokenEndpointResponse(t *testing.T) {
	ctrl := gomock.NewController(t)
	store := internal.NewMockResourceOwnerPasswordCredentialsGrantStorage(ctrl)
	chgen := internal.NewMockAccessTokenStrategy(ctrl)
	rtstr := internal.NewMockRefreshTokenStrategy(ctrl)
	mockAT := "accesstoken.foo.bar"
	mockRT := "refreshtoken.bar.foo"
	defer ctrl.Finish()

	var areq *fosite.AccessRequest
	var aresp *fosite.AccessResponse
	var h ResourceOwnerPasswordCredentialsGrantHandler

	for k, c := range []struct {
		description string
		setup       func()
		expectErr   error
		expect      func()
	}{
		{
			description: "should fail because not responsible",
			expectErr:   fosite.ErrUnknownRequest,
			setup: func() {
				areq.GrantTypes = fosite.Arguments{""}
			},
		},
		{
			description: "should pass",
			setup: func() {
				areq.GrantTypes = fosite.Arguments{"password"}
				chgen.EXPECT().GenerateAccessToken(nil, areq).Return(mockAT, "bar", nil)
				store.EXPECT().CreateAccessTokenSession(nil, "bar", gomock.Eq(areq.Sanitize([]string{}))).Return(nil)
			},
			expect: func() {
				assert.Nil(t, aresp.GetExtra("refresh_token"), "unexpected refresh token")
			},
		},
		{
			description: "should pass - offline scope",
			setup: func() {
				areq.GrantTypes = fosite.Arguments{"password"}
				areq.GrantScope("offline")
				rtstr.EXPECT().GenerateRefreshToken(nil, areq).Return(mockRT, "bar", nil)
				store.EXPECT().CreateRefreshTokenSession(nil, "bar", gomock.Eq(areq.Sanitize([]string{}))).Return(nil)
				chgen.EXPECT().GenerateAccessToken(nil, areq).Return(mockAT, "bar", nil)
				store.EXPECT().CreateAccessTokenSession(nil, "bar", gomock.Eq(areq.Sanitize([]string{}))).Return(nil)
			},
			expect: func() {
				assert.NotNil(t, aresp.GetExtra("refresh_token"), "expected refresh token")
			},
		},
		{
			description: "should pass - refresh token without offline scope",
			setup: func() {
				h.RefreshTokenScopes = []string{}
				areq.GrantTypes = fosite.Arguments{"password"}
				rtstr.EXPECT().GenerateRefreshToken(nil, areq).Return(mockRT, "bar", nil)
				store.EXPECT().CreateRefreshTokenSession(nil, "bar", gomock.Eq(areq.Sanitize([]string{}))).Return(nil)
				chgen.EXPECT().GenerateAccessToken(nil, areq).Return(mockAT, "bar", nil)
				store.EXPECT().CreateAccessTokenSession(nil, "bar", gomock.Eq(areq.Sanitize([]string{}))).Return(nil)
			},
			expect: func() {
				assert.NotNil(t, aresp.GetExtra("refresh_token"), "expected refresh token")
			},
		},
	} {
		t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) {
			areq = fosite.NewAccessRequest(nil)
			aresp = fosite.NewAccessResponse()
			areq.Session = &fosite.DefaultSession{}
			h = ResourceOwnerPasswordCredentialsGrantHandler{
				ResourceOwnerPasswordCredentialsGrantStorage: store,
				HandleHelper: &HandleHelper{
					AccessTokenStorage:  store,
					AccessTokenStrategy: chgen,
					AccessTokenLifespan: time.Hour,
				},
				RefreshTokenStrategy: rtstr,
				RefreshTokenScopes:   []string{"offline"},
			}
			c.setup()
			err := h.PopulateTokenEndpointResponse(nil, areq, aresp)
			if c.expectErr != nil {
				require.EqualError(t, err, c.expectErr.Error())
			} else {
				require.NoError(t, err)
				if c.expect != nil {
					c.expect()
				}
			}
		})
	}
}
<|endoftext|>