<commit_before>package indicators_test

import (
	. "github.com/onsi/ginkgo"
	"github.com/thetruetrade/gotrade"
	. "github.com/thetruetrade/gotrade/indicators"
)

var _ = Describe("when calculating a weighted moving average (wma)", func() {
	var (
		period    int = 3
		indicator *Wma
		inputs    IndicatorWithFloatBoundsSharedSpecInputs
	)

	BeforeEach(func() {
		indicator, _ = NewWma(period, gotrade.UseClosePrice)

		inputs = NewIndicatorWithFloatBoundsSharedSpecInputs(indicator, len(sourceDOHLCVData), indicator,
			func() float64 {
				return GetFloatDataMax(indicator.Data)
			},
			func() float64 {
				return GetFloatDataMin(indicator.Data)
			})
	})

	Context("and the indicator has not yet received any ticks", func() {
		ShouldBeAnInitialisedIndicator(&inputs)

		ShouldNotHaveAnyFloatBoundsSetYet(&inputs)
	})

	Context("and the indicator has received less ticks than the lookback period", func() {

		BeforeEach(func() {
			for i := 0; i < indicator.GetLookbackPeriod(); i++ {
				indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
			}
		})

		ShouldBeAnIndicatorThatHasReceivedFewerTicksThanItsLookbackPeriod(&inputs)

		ShouldNotHaveAnyFloatBoundsSetYet(&inputs)
	})

	Context("and the indicator has received ticks equal to the lookback period", func() {

		BeforeEach(func() {
			for i := 0; i <= indicator.GetLookbackPeriod(); i++ {
				indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
			}
		})

		ShouldBeAnIndicatorThatHasReceivedTicksEqualToItsLookbackPeriod(&inputs)

		ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)
	})

	Context("and the indicator has received more ticks than the lookback period", func() {

		BeforeEach(func() {
			for i := range sourceDOHLCVData {
				indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
			}
		})

		ShouldBeAnIndicatorThatHasReceivedMoreTicksThanItsLookbackPeriod(&inputs)

		ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)
	})

	Context("and the indicator has recieved all of its ticks", func() {
		BeforeEach(func() {
			for i := 0; i < len(sourceDOHLCVData); i++ {
				indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
			}
		})

		ShouldBeAnIndicatorThatHasReceivedAllOfItsTicks(&inputs)

		ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)
	})
})
<commit_msg>#77 achieve 100% test coverage for indicators - wma<commit_after>package indicators_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/thetruetrade/gotrade"
	"github.com/thetruetrade/gotrade/indicators"
)

var _ = Describe("when creating an wmawithoutstorage", func() {
	var (
		indicator      *indicators.WmaWithoutStorage
		indicatorError error
	)

	Context("and the indicator was not given a value available action", func() {
		BeforeEach(func() {
			indicator, indicatorError = indicators.NewWmaWithoutStorage(4, nil)
		})

		It("the indicator should not be created and return the appropriate error message", func() {
			Expect(indicator).To(BeNil())
			Expect(indicatorError).To(Equal(indicators.ErrValueAvailableActionIsNil))
		})
	})

	Context("and the indicator was given a timePeriod below the minimum", func() {
		BeforeEach(func() {
			indicator, indicatorError = indicators.NewWmaWithoutStorage(1, fakeFloatValAvailable)
		})

		It("the indicator should not be created and return the appropriate error message", func() {
			Expect(indicator).To(BeNil())
		})
	})

	Context("and the indicator was given a timePeriod above the maximum", func() {
		BeforeEach(func() {
			indicator, indicatorError = indicators.NewWmaWithoutStorage(indicators.MaximumLookbackPeriod+1, fakeFloatValAvailable)
		})

		It("the indicator should not be created and return the appropriate error message", func() {
			Expect(indicator).To(BeNil())
		})
	})
})

var _ = Describe("when calculating a weighted moving average (wma)", func() {
	var (
		period    int = 3
		indicator *indicators.Wma
		inputs    IndicatorWithFloatBoundsSharedSpecInputs
		stream    *fakeDOHLCVStreamSubscriber
	)

	Context("given the indicator is created via the standard constructor", func() {
		BeforeEach(func() {
			indicator, _ = indicators.NewWma(period, gotrade.UseClosePrice)

			inputs = NewIndicatorWithFloatBoundsSharedSpecInputs(indicator, len(sourceDOHLCVData), indicator,
				func() float64 {
					return GetFloatDataMax(indicator.Data)
				},
				func() float64 {
					return GetFloatDataMin(indicator.Data)
				})
		})

		Context("and the indicator has not yet received any ticks", func() {
			ShouldBeAnInitialisedIndicator(&inputs)

			ShouldNotHaveAnyFloatBoundsSetYet(&inputs)
		})

		Context("and the indicator has received less ticks than the lookback period", func() {

			BeforeEach(func() {
				for i := 0; i < indicator.GetLookbackPeriod(); i++ {
					indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
				}
			})

			ShouldBeAnIndicatorThatHasReceivedFewerTicksThanItsLookbackPeriod(&inputs)

			ShouldNotHaveAnyFloatBoundsSetYet(&inputs)
		})

		Context("and the indicator has received ticks equal to the lookback period", func() {

			BeforeEach(func() {
				for i := 0; i <= indicator.GetLookbackPeriod(); i++ {
					indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
				}
			})

			ShouldBeAnIndicatorThatHasReceivedTicksEqualToItsLookbackPeriod(&inputs)

			ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)
		})

		Context("and the indicator has received more ticks than the lookback period", func() {

			BeforeEach(func() {
				for i := range sourceDOHLCVData {
					indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
				}
			})

			ShouldBeAnIndicatorThatHasReceivedMoreTicksThanItsLookbackPeriod(&inputs)

			ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)
		})

		Context("and the indicator has recieved all of its ticks", func() {
			BeforeEach(func() {
				for i := 0; i < len(sourceDOHLCVData); i++ {
					indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
				}
			})

			ShouldBeAnIndicatorThatHasReceivedAllOfItsTicks(&inputs)

			ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)
		})
	})

	Context("given the indicator is created via the constructor with defaulted parameters", func() {
		BeforeEach(func() {
			indicator, _ = indicators.NewDefaultWma()
			inputs = NewIndicatorWithFloatBoundsSharedSpecInputs(indicator, len(sourceDOHLCVData), indicator,
				func() float64 {
					return GetFloatDataMax(indicator.Data)
				},
				func() float64 {
					return GetFloatDataMin(indicator.Data)
				})
		})

		Context("and the indicator has not yet received any ticks", func() {
			ShouldBeAnInitialisedIndicator(&inputs)

			ShouldNotHaveAnyFloatBoundsSetYet(&inputs)
		})

		Context("and the indicator has recieved all of its ticks", func() {
			BeforeEach(func() {
				for i := 0; i < len(sourceDOHLCVData); i++ {
					indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
				}
			})

			ShouldBeAnIndicatorThatHasReceivedAllOfItsTicks(&inputs)

			ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)
		})
	})

	Context("given the indicator is created via the constructor with fixed source length", func() {
		BeforeEach(func() {
			indicator, _ = indicators.NewWmaWithSrcLen(uint(len(sourceDOHLCVData)), 4, gotrade.UseClosePrice)
			inputs = NewIndicatorWithFloatBoundsSharedSpecInputs(indicator, len(sourceDOHLCVData), indicator,
				func() float64 {
					return GetFloatDataMax(indicator.Data)
				},
				func() float64 {
					return GetFloatDataMin(indicator.Data)
				})
		})

		It("should have pre-allocated storge for the output data", func() {
			Expect(cap(indicator.Data)).To(Equal(len(sourceDOHLCVData) - indicator.GetLookbackPeriod()))
		})

		Context("and the indicator has not yet received any ticks", func() {
			ShouldBeAnInitialisedIndicator(&inputs)

			ShouldNotHaveAnyFloatBoundsSetYet(&inputs)
		})

		Context("and the indicator has recieved all of its ticks", func() {
			BeforeEach(func() {
				for i := 0; i < len(sourceDOHLCVData); i++ {
					indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
				}
			})

			ShouldBeAnIndicatorThatHasReceivedAllOfItsTicks(&inputs)

			ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)

			It("no new storage capcity should have been allocated", func() {
				Expect(len(indicator.Data)).To(Equal(cap(indicator.Data)))
			})
		})
	})

	Context("given the indicator is created via the constructor with defaulted parameters and fixed source length", func() {
		BeforeEach(func() {
			indicator, _ = indicators.NewDefaultWmaWithSrcLen(uint(len(sourceDOHLCVData)))
			inputs = NewIndicatorWithFloatBoundsSharedSpecInputs(indicator, len(sourceDOHLCVData), indicator,
				func() float64 {
					return GetFloatDataMax(indicator.Data)
				},
				func() float64 {
					return GetFloatDataMin(indicator.Data)
				})
		})

		It("should have pre-allocated storge for the output data", func() {
			Expect(cap(indicator.Data)).To(Equal(len(sourceDOHLCVData) - indicator.GetLookbackPeriod()))
		})

		Context("and the indicator has not yet received any ticks", func() {
			ShouldBeAnInitialisedIndicator(&inputs)

			ShouldNotHaveAnyFloatBoundsSetYet(&inputs)
		})

		Context("and the indicator has recieved all of its ticks", func() {
			BeforeEach(func() {
				for i := 0; i < len(sourceDOHLCVData); i++ {
					indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
				}
			})

			ShouldBeAnIndicatorThatHasReceivedAllOfItsTicks(&inputs)

			ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)

			It("no new storage capcity should have been allocated", func() {
				Expect(len(indicator.Data)).To(Equal(cap(indicator.Data)))
			})
		})
	})

	Context("given the indicator is created via the constructor for use with a price stream", func() {
		BeforeEach(func() {
			stream = newFakeDOHLCVStreamSubscriber()
			indicator, _ = indicators.NewWmaForStream(stream, 4, gotrade.UseClosePrice)
			inputs = NewIndicatorWithFloatBoundsSharedSpecInputs(indicator, len(sourceDOHLCVData), indicator,
				func() float64 {
					return GetFloatDataMax(indicator.Data)
				},
				func() float64 {
					return GetFloatDataMin(indicator.Data)
				})
		})

		It("should have requested to be attached to the stream", func() {
			Expect(stream.lastCallToAddTickSubscriptionArg).To(Equal(indicator))
		})

		Context("and the indicator has not yet received any ticks", func() {
			ShouldBeAnInitialisedIndicator(&inputs)

			ShouldNotHaveAnyFloatBoundsSetYet(&inputs)
		})

		Context("and the indicator has recieved all of its ticks", func() {
			BeforeEach(func() {
				for i := 0; i < len(sourceDOHLCVData); i++ {
					indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
				}
			})

			ShouldBeAnIndicatorThatHasReceivedAllOfItsTicks(&inputs)

			ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)
		})
	})

	Context("given the indicator is created via the constructor for use with a price stream with defaulted parameters", func() {
		BeforeEach(func() {
			stream = newFakeDOHLCVStreamSubscriber()
			indicator, _ = indicators.NewDefaultWmaForStream(stream)
			inputs = NewIndicatorWithFloatBoundsSharedSpecInputs(indicator, len(sourceDOHLCVData), indicator,
				func() float64 {
					return GetFloatDataMax(indicator.Data)
				},
				func() float64 {
					return GetFloatDataMin(indicator.Data)
				})
		})

		It("should have requested to be attached to the stream", func() {
			Expect(stream.lastCallToAddTickSubscriptionArg).To(Equal(indicator))
		})

		Context("and the indicator has not yet received any ticks", func() {
			ShouldBeAnInitialisedIndicator(&inputs)

			ShouldNotHaveAnyFloatBoundsSetYet(&inputs)
		})

		Context("and the indicator has recieved all of its ticks", func() {
			BeforeEach(func() {
				for i := 0; i < len(sourceDOHLCVData); i++ {
					indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
				}
			})

			ShouldBeAnIndicatorThatHasReceivedAllOfItsTicks(&inputs)

			ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)
		})
	})

	Context("given the indicator is created via the constructor for use with a price stream with fixed source length", func() {
		BeforeEach(func() {
			stream = newFakeDOHLCVStreamSubscriber()
			indicator, _ = indicators.NewWmaForStreamWithSrcLen(uint(len(sourceDOHLCVData)), stream, 4, gotrade.UseClosePrice)
			inputs = NewIndicatorWithFloatBoundsSharedSpecInputs(indicator, len(sourceDOHLCVData), indicator,
				func() float64 {
					return GetFloatDataMax(indicator.Data)
				},
				func() float64 {
					return GetFloatDataMin(indicator.Data)
				})
		})

		It("should have pre-allocated storge for the output data", func() {
			Expect(cap(indicator.Data)).To(Equal(len(sourceDOHLCVData) - indicator.GetLookbackPeriod()))
		})

		It("should have requested to be attached to the stream", func() {
			Expect(stream.lastCallToAddTickSubscriptionArg).To(Equal(indicator))
		})

		Context("and the indicator has not yet received any ticks", func() {
			ShouldBeAnInitialisedIndicator(&inputs)

			ShouldNotHaveAnyFloatBoundsSetYet(&inputs)
		})

		Context("and the indicator has recieved all of its ticks", func() {
			BeforeEach(func() {
				for i := 0; i < len(sourceDOHLCVData); i++ {
					indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
				}
			})

			ShouldBeAnIndicatorThatHasReceivedAllOfItsTicks(&inputs)

			ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)

			It("no new storage capcity should have been allocated", func() {
				Expect(len(indicator.Data)).To(Equal(cap(indicator.Data)))
			})
		})
	})

	Context("given the indicator is created via the constructor for use with a price stream with fixed source length with defaulted parmeters", func() {
		BeforeEach(func() {
			stream = newFakeDOHLCVStreamSubscriber()
			indicator, _ = indicators.NewDefaultWmaForStreamWithSrcLen(uint(len(sourceDOHLCVData)), stream)
			inputs = NewIndicatorWithFloatBoundsSharedSpecInputs(indicator, len(sourceDOHLCVData), indicator,
				func() float64 {
					return GetFloatDataMax(indicator.Data)
				},
				func() float64 {
					return GetFloatDataMin(indicator.Data)
				})
		})

		It("should have pre-allocated storge for the output data", func() {
			Expect(cap(indicator.Data)).To(Equal(len(sourceDOHLCVData) - indicator.GetLookbackPeriod()))
		})

		It("should have requested to be attached to the stream", func() {
			Expect(stream.lastCallToAddTickSubscriptionArg).To(Equal(indicator))
		})

		Context("and the indicator has not yet received any ticks", func() {
			ShouldBeAnInitialisedIndicator(&inputs)

			ShouldNotHaveAnyFloatBoundsSetYet(&inputs)
		})

		Context("and the indicator has recieved all of its ticks", func() {
			BeforeEach(func() {
				for i := 0; i < len(sourceDOHLCVData); i++ {
					indicator.ReceiveDOHLCVTick(sourceDOHLCVData[i], i+1)
				}
			})

			ShouldBeAnIndicatorThatHasReceivedAllOfItsTicks(&inputs)

			ShouldHaveFloatBoundsSetToMinMaxOfResults(&inputs)

			It("no new storage capcity should have been allocated", func() {
				Expect(len(indicator.Data)).To(Equal(cap(indicator.Data)))
			})
		})
	})
})
<|endoftext|>


<commit_before>// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package inpututil provides utility functions of input like keyboard or mouse.
//
// Note: This package is experimental and API might be changed.
package inpututil

import (
	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/internal/hooks"
	"github.com/hajimehoshi/ebiten/internal/sync"
)

type inputState struct {
	keyStates     map[ebiten.Key]int
	prevKeyStates map[ebiten.Key]int

	mouseButtonStates     map[ebiten.MouseButton]int
	prevMouseButtonStates map[ebiten.MouseButton]int

	gamepadButtonStates map[int]map[ebiten.GamepadButton]int
	touchStates         map[int]int

	m sync.RWMutex
}

var theInputState = &inputState{
	keyStates:     map[ebiten.Key]int{},
	prevKeyStates: map[ebiten.Key]int{},

	mouseButtonStates:     map[ebiten.MouseButton]int{},
	prevMouseButtonStates: map[ebiten.MouseButton]int{},

	gamepadButtonStates: map[int]map[ebiten.GamepadButton]int{},
	touchStates:         map[int]int{},
}

func init() {
	hooks.AppendHookOnUpdate(func() error {
		theInputState.update()
		return nil
	})
}

func (i *inputState) update() {
	i.m.Lock()
	defer i.m.Unlock()

	// Keyboard
	for k := ebiten.Key(0); k <= ebiten.KeyMax; k++ {
		i.prevKeyStates[k] = i.keyStates[k]
		if ebiten.IsKeyPressed(k) {
			i.keyStates[k]++
		} else {
			i.keyStates[k] = 0
		}
	}

	// Mouse
	for _, b := range []ebiten.MouseButton{
		ebiten.MouseButtonLeft,
		ebiten.MouseButtonRight,
		ebiten.MouseButtonMiddle,
	} {
		i.prevMouseButtonStates[b] = i.mouseButtonStates[b]
		if ebiten.IsMouseButtonPressed(b) {
			i.mouseButtonStates[b]++
		} else {
			i.mouseButtonStates[b] = 0
		}
	}

	// Gamepads
	ids := map[int]struct{}{}
	for _, id := range ebiten.GamepadIDs() {
		ids[id] = struct{}{}
		if _, ok := i.gamepadButtonStates[id]; !ok {
			i.gamepadButtonStates[id] = map[ebiten.GamepadButton]int{}
		}
		n := ebiten.GamepadButtonNum(id)
		for b := ebiten.GamepadButton(0); b < ebiten.GamepadButton(n); b++ {
			if ebiten.IsGamepadButtonPressed(id, b) {
				i.gamepadButtonStates[id][b]++
			} else {
				i.gamepadButtonStates[id][b] = 0
			}
		}
	}
	idsToDelete := []int{}
	for id := range i.gamepadButtonStates {
		if _, ok := ids[id]; !ok {
			idsToDelete = append(idsToDelete, id)
		}
	}
	for _, id := range idsToDelete {
		delete(i.gamepadButtonStates, id)
	}

	// Touches
	ids = map[int]struct{}{}
	for _, t := range ebiten.Touches() {
		ids[t.ID()] = struct{}{}
		i.touchStates[t.ID()]++
	}
	idsToDelete = []int{}
	for id := range i.touchStates {
		if _, ok := ids[id]; !ok {
			idsToDelete = append(idsToDelete, id)
		}
	}
	for _, id := range idsToDelete {
		delete(i.touchStates, id)
	}
}

// IsKeyJustPressed returns a boolean value indicating
// whether the given key is pressed just in the current frame.
//
// IsKeyJustPressed is concurrent safe.
func IsKeyJustPressed(key ebiten.Key) bool {
	return KeyPressDuration(key) == 1
}

// IsKeyJustReleased returns a boolean value indicating
// whether the given key is released just in the current frame.
//
// IsKeyJustReleased is concurrent safe.
func IsKeyJustReleased(key ebiten.Key) bool {
	theInputState.m.RLock()
	r := theInputState.keyStates[key] == 0 && theInputState.prevKeyStates[key] > 0
	theInputState.m.RUnlock()
	return r
}

// KeyPressDuration returns how long the key is pressed in frames.
//
// KeyPressDuration is concurrent safe.
func KeyPressDuration(key ebiten.Key) int {
	theInputState.m.RLock()
	s := theInputState.keyStates[key]
	theInputState.m.RUnlock()
	return s
}

// IsMouseButtonJustPressed returns a boolean value indicating
// whether the given mouse button is pressed just in the current frame.
//
// IsMouseButtonJustPressed is concurrent safe.
func IsMouseButtonJustPressed(button ebiten.MouseButton) bool {
	return MouseButtonPressDuration(button) == 1
}

// IsMouseButtonJustReleased returns a boolean value indicating
// whether the given mouse button is released just in the current frame.
//
// IsMouseButtonJustReleased is concurrent safe.
func IsMouseButtonJustReleased(button ebiten.MouseButton) bool {
	theInputState.m.RLock()
	r := theInputState.mouseButtonStates[button] == 0 &&
		theInputState.prevMouseButtonStates[button] > 0
	theInputState.m.RUnlock()
	return r
}

// MouseButtonPressDuration returns how long the mouse button is pressed in frames.
//
// MouseButtonPressDuration is concurrent safe.
func MouseButtonPressDuration(button ebiten.MouseButton) int {
	theInputState.m.RLock()
	s := theInputState.mouseButtonStates[button]
	theInputState.m.RUnlock()
	return s
}

// IsGamepadButtonJustPressed returns a boolean value indicating
// whether the given gamepad button of the gamepad id is pressed just in the current frame.
//
// IsGamepadButtonJustPressed is concurrent safe.
func IsGamepadButtonJustPressed(id int, button ebiten.GamepadButton) bool {
	return GamepadButtonPressDuration(id, button) == 1
}

// GamepadButtonPressDuration returns how long the gamepad button of the gamepad id is pressed in frames.
//
// GamepadButtonPressDuration is concurrent safe.
func GamepadButtonPressDuration(id int, button ebiten.GamepadButton) int {
	theInputState.m.RLock()
	s := 0
	if _, ok := theInputState.gamepadButtonStates[id]; ok {
		s = theInputState.gamepadButtonStates[id][button]
	}
	theInputState.m.RUnlock()
	return s
}

// IsJustTouched returns a boolean value indicating
// whether the given touch is pressed just in the current frame.
//
// IsJustTouched is concurrent safe.
func IsJustTouched(id int) bool {
	return TouchDuration(id) == 1
}

// TouchDuration returns how long the touch remains in frames.
//
// TouchDuration is concurrent safe.
func TouchDuration(id int) int {
	theInputState.m.RLock()
	s := theInputState.touchStates[id]
	theInputState.m.RUnlock()
	return s
}
<commit_msg>inpututil: Add IsGamepadButtonJustReleased (#504)<commit_after>// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package inpututil provides utility functions of input like keyboard or mouse.
//
// Note: This package is experimental and API might be changed.
package inpututil

import (
	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/internal/hooks"
	"github.com/hajimehoshi/ebiten/internal/sync"
)

type inputState struct {
	keyStates     map[ebiten.Key]int
	prevKeyStates map[ebiten.Key]int

	mouseButtonStates     map[ebiten.MouseButton]int
	prevMouseButtonStates map[ebiten.MouseButton]int

	gamepadButtonStates     map[int]map[ebiten.GamepadButton]int
	prevGamepadButtonStates map[int]map[ebiten.GamepadButton]int

	touchStates map[int]int

	m sync.RWMutex
}

var theInputState = &inputState{
	keyStates:     map[ebiten.Key]int{},
	prevKeyStates: map[ebiten.Key]int{},

	mouseButtonStates:     map[ebiten.MouseButton]int{},
	prevMouseButtonStates: map[ebiten.MouseButton]int{},

	gamepadButtonStates:     map[int]map[ebiten.GamepadButton]int{},
	prevGamepadButtonStates: map[int]map[ebiten.GamepadButton]int{},

	touchStates: map[int]int{},
}

func init() {
	hooks.AppendHookOnUpdate(func() error {
		theInputState.update()
		return nil
	})
}

func (i *inputState) update() {
	i.m.Lock()
	defer i.m.Unlock()

	// Keyboard
	for k := ebiten.Key(0); k <= ebiten.KeyMax; k++ {
		i.prevKeyStates[k] = i.keyStates[k]
		if ebiten.IsKeyPressed(k) {
			i.keyStates[k]++
		} else {
			i.keyStates[k] = 0
		}
	}

	// Mouse
	for _, b := range []ebiten.MouseButton{
		ebiten.MouseButtonLeft,
		ebiten.MouseButtonRight,
		ebiten.MouseButtonMiddle,
	} {
		i.prevMouseButtonStates[b] = i.mouseButtonStates[b]
		if ebiten.IsMouseButtonPressed(b) {
			i.mouseButtonStates[b]++
		} else {
			i.mouseButtonStates[b] = 0
		}
	}

	// Gamepads

	// Reset the previous states first since some gamepad IDs might be already gone.
	for id := range i.prevGamepadButtonStates {
		for b := range i.prevGamepadButtonStates[id] {
			i.prevGamepadButtonStates[id][b] = 0
		}
	}
	ids := map[int]struct{}{}
	for _, id := range ebiten.GamepadIDs() {
		ids[id] = struct{}{}

		if _, ok := i.prevGamepadButtonStates[id]; !ok {
			i.prevGamepadButtonStates[id] = map[ebiten.GamepadButton]int{}
		}
		if _, ok := i.gamepadButtonStates[id]; !ok {
			i.gamepadButtonStates[id] = map[ebiten.GamepadButton]int{}
		}

		n := ebiten.GamepadButtonNum(id)
		for b := ebiten.GamepadButton(0); b < ebiten.GamepadButton(n); b++ {
			i.prevGamepadButtonStates[id][b] = i.gamepadButtonStates[id][b]
			if ebiten.IsGamepadButtonPressed(id, b) {
				i.gamepadButtonStates[id][b]++
			} else {
				i.gamepadButtonStates[id][b] = 0
			}
		}
	}
	idsToDelete := []int{}
	for id := range i.gamepadButtonStates {
		if _, ok := ids[id]; !ok {
			idsToDelete = append(idsToDelete, id)
		}
	}
	for _, id := range idsToDelete {
		delete(i.gamepadButtonStates, id)
	}

	// Touches
	ids = map[int]struct{}{}
	for _, t := range ebiten.Touches() {
		ids[t.ID()] = struct{}{}
		i.touchStates[t.ID()]++
	}
	idsToDelete = []int{}
	for id := range i.touchStates {
		if _, ok := ids[id]; !ok {
			idsToDelete = append(idsToDelete, id)
		}
	}
	for _, id := range idsToDelete {
		delete(i.touchStates, id)
	}
}

// IsKeyJustPressed returns a boolean value indicating
// whether the given key is pressed just in the current frame.
//
// IsKeyJustPressed is concurrent safe.
func IsKeyJustPressed(key ebiten.Key) bool {
	return KeyPressDuration(key) == 1
}

// IsKeyJustReleased returns a boolean value indicating
// whether the given key is released just in the current frame.
//
// IsKeyJustReleased is concurrent safe.
func IsKeyJustReleased(key ebiten.Key) bool {
	theInputState.m.RLock()
	r := theInputState.keyStates[key] == 0 && theInputState.prevKeyStates[key] > 0
	theInputState.m.RUnlock()
	return r
}

// KeyPressDuration returns how long the key is pressed in frames.
//
// KeyPressDuration is concurrent safe.
func KeyPressDuration(key ebiten.Key) int {
	theInputState.m.RLock()
	s := theInputState.keyStates[key]
	theInputState.m.RUnlock()
	return s
}

// IsMouseButtonJustPressed returns a boolean value indicating
// whether the given mouse button is pressed just in the current frame.
//
// IsMouseButtonJustPressed is concurrent safe.
func IsMouseButtonJustPressed(button ebiten.MouseButton) bool {
	return MouseButtonPressDuration(button) == 1
}

// IsMouseButtonJustReleased returns a boolean value indicating
// whether the given mouse button is released just in the current frame.
//
// IsMouseButtonJustReleased is concurrent safe.
func IsMouseButtonJustReleased(button ebiten.MouseButton) bool {
	theInputState.m.RLock()
	r := theInputState.mouseButtonStates[button] == 0 &&
		theInputState.prevMouseButtonStates[button] > 0
	theInputState.m.RUnlock()
	return r
}

// MouseButtonPressDuration returns how long the mouse button is pressed in frames.
//
// MouseButtonPressDuration is concurrent safe.
func MouseButtonPressDuration(button ebiten.MouseButton) int {
	theInputState.m.RLock()
	s := theInputState.mouseButtonStates[button]
	theInputState.m.RUnlock()
	return s
}

// IsGamepadButtonJustPressed returns a boolean value indicating
// whether the given gamepad button of the gamepad id is pressed just in the current frame.
//
// IsGamepadButtonJustPressed is concurrent safe.
func IsGamepadButtonJustPressed(id int, button ebiten.GamepadButton) bool {
	return GamepadButtonPressDuration(id, button) == 1
}

// IsGamepadButtonJustReleased returns a boolean value indicating
// whether the given gamepad button of the gamepad id is released just in the current frame.
//
// IsGamepadButtonJustReleased is concurrent safe.
func IsGamepadButtonJustReleased(id int, button ebiten.GamepadButton) bool {
	theInputState.m.RLock()
	prev := 0
	if _, ok := theInputState.prevGamepadButtonStates[id]; ok {
		prev = theInputState.prevGamepadButtonStates[id][button]
	}
	current := 0
	if _, ok := theInputState.gamepadButtonStates[id]; ok {
		current = theInputState.gamepadButtonStates[id][button]
	}
	theInputState.m.RUnlock()
	return current == 0 && prev > 0
}

// GamepadButtonPressDuration returns how long the gamepad button of the gamepad id is pressed in frames.
//
// GamepadButtonPressDuration is concurrent safe.
func GamepadButtonPressDuration(id int, button ebiten.GamepadButton) int {
	theInputState.m.RLock()
	s := 0
	if _, ok := theInputState.gamepadButtonStates[id]; ok {
		s = theInputState.gamepadButtonStates[id][button]
	}
	theInputState.m.RUnlock()
	return s
}

// IsJustTouched returns a boolean value indicating
// whether the given touch is pressed just in the current frame.
//
// IsJustTouched is concurrent safe.
func IsJustTouched(id int) bool {
	return TouchDuration(id) == 1
}

// TouchDuration returns how long the touch remains in frames.
//
// TouchDuration is concurrent safe.
func TouchDuration(id int) int {
	theInputState.m.RLock()
	s := theInputState.touchStates[id]
	theInputState.m.RUnlock()
	return s
}
<|endoftext|>


<commit_before>package prometheus

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

	"github.com/lomik/graphite-clickhouse/config"
	"github.com/lomik/graphite-clickhouse/finder"
	"github.com/lomik/graphite-clickhouse/helper/clickhouse"
	"github.com/prometheus/prometheus/storage"
)

// Querier returns a new Querier on the storage.
func (h *Handler) Querier(ctx context.Context, mint, maxt int64) (storage.Querier, error) {
	return &Querier{
		config: h.config,
		ctx:    ctx,
		mint:   mint,
		maxt:   maxt,
	}, nil
}

// Querier provides reading access to time series data.
type Querier struct {
	config *config.Config
	mint   int64
	maxt   int64
	ctx    context.Context
}

// Close releases the resources of the Querier.
func (q *Querier) Close() error {
	return nil
}

// LabelValues returns all potential values for a label name.
func (q *Querier) LabelValues(label string) ([]string, error) {
	where := finder.NewWhere()
	where.Andf("Tag1 LIKE %s", finder.Q(finder.LikeEscape(label)+"=%"))

	fromDate := time.Now().AddDate(0, 0, -q.config.ClickHouse.TaggedAutocompleDays)
	where.Andf("Date >= '%s'", fromDate.Format("2006-01-02"))

	sql := fmt.Sprintf("SELECT splitByChar('=', Tag1)[2] as value FROM %s %s GROUP BY value ORDER BY value",
		q.config.ClickHouse.TaggedTable,
		where.SQL(),
	)

	body, err := clickhouse.Query(q.ctx, q.config.ClickHouse.Url, sql, q.config.ClickHouse.TaggedTable,
		clickhouse.Options{Timeout: q.config.ClickHouse.IndexTimeout.Value(), ConnectTimeout: q.config.ClickHouse.ConnectTimeout.Value()})
	if err != nil {
		return nil, err
	}

	rows := strings.Split(string(body), "\n")
	if len(rows) > 0 && rows[len(rows)-1] == "" {
		rows = rows[:len(rows)-1]
	}

	return rows, nil
}

// LabelNames returns all the unique label names present in the block in sorted order.
func (q *Querier) LabelNames() ([]string, error) {
	panic("not implemented")
	return nil, nil
}
<commit_msg>implement labelNames<commit_after>package prometheus

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

	"github.com/lomik/graphite-clickhouse/config"
	"github.com/lomik/graphite-clickhouse/finder"
	"github.com/lomik/graphite-clickhouse/helper/clickhouse"
	"github.com/prometheus/prometheus/storage"
)

// Querier returns a new Querier on the storage.
func (h *Handler) Querier(ctx context.Context, mint, maxt int64) (storage.Querier, error) {
	return &Querier{
		config: h.config,
		ctx:    ctx,
		mint:   mint,
		maxt:   maxt,
	}, nil
}

// Querier provides reading access to time series data.
type Querier struct {
	config *config.Config
	mint   int64
	maxt   int64
	ctx    context.Context
}

// Close releases the resources of the Querier.
func (q *Querier) Close() error {
	return nil
}

// LabelValues returns all potential values for a label name.
func (q *Querier) LabelValues(label string) ([]string, error) {
	where := finder.NewWhere()
	where.Andf("Tag1 LIKE %s", finder.Q(finder.LikeEscape(label)+"=%"))

	fromDate := time.Now().AddDate(0, 0, -q.config.ClickHouse.TaggedAutocompleDays)
	where.Andf("Date >= '%s'", fromDate.Format("2006-01-02"))

	sql := fmt.Sprintf("SELECT splitByChar('=', Tag1)[2] as value FROM %s %s GROUP BY value ORDER BY value",
		q.config.ClickHouse.TaggedTable,
		where.SQL(),
	)

	body, err := clickhouse.Query(q.ctx, q.config.ClickHouse.Url, sql, q.config.ClickHouse.TaggedTable,
		clickhouse.Options{Timeout: q.config.ClickHouse.IndexTimeout.Value(), ConnectTimeout: q.config.ClickHouse.ConnectTimeout.Value()})
	if err != nil {
		return nil, err
	}

	rows := strings.Split(string(body), "\n")
	if len(rows) > 0 && rows[len(rows)-1] == "" {
		rows = rows[:len(rows)-1]
	}

	return rows, nil
}

// LabelNames returns all the unique label names present in the block in sorted order.
func (q *Querier) LabelNames() ([]string, error) {
	where := finder.NewWhere()
	fromDate := time.Now().AddDate(0, 0, -q.config.ClickHouse.TaggedAutocompleDays)
	where.Andf("Date >= '%s'", fromDate.Format("2006-01-02"))

	sql := fmt.Sprintf("SELECT splitByChar('=', Tag1)[1] as value FROM %s %s GROUP BY value ORDER BY value",
		q.config.ClickHouse.TaggedTable,
		where.SQL(),
	)

	body, err := clickhouse.Query(q.ctx, q.config.ClickHouse.Url, sql, q.config.ClickHouse.TaggedTable,
		clickhouse.Options{Timeout: q.config.ClickHouse.IndexTimeout.Value(), ConnectTimeout: q.config.ClickHouse.ConnectTimeout.Value()})
	if err != nil {
		return nil, err
	}

	rows := strings.Split(string(body), "\n")
	if len(rows) > 0 && rows[len(rows)-1] == "" {
		rows = rows[:len(rows)-1]
	}

	return rows, nil
}
<|endoftext|>


<commit_before>package aws

import (
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/autoscaling"
	"github.com/aws/aws-sdk-go/service/ecs"
	"github.com/aws/aws-sdk-go/service/elb"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/sns"
)

type AwsManager struct {
	session *session.Session
}

func NewAwsManager(region string) *AwsManager {

	cred := credentials.NewChainCredentials([]credentials.Provider{
		&credentials.EnvProvider{},
		&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
		&ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute},
	})

	conf := aws.NewConfig().WithCredentials(cred).WithMaxRetries(aws.UseServiceDefaultRetries).WithRegion(region)

	return &AwsManager{
		session: session.New(conf),
	}
}

func (self *AwsManager) EcsApi() *EcsApi {
	return &EcsApi{
		service: ecs.New(self.session),
	}
}

func (self *AwsManager) ElbApi() *ElbApi {
	return &ElbApi{
		service: elb.New(self.session),
	}
}

func (self *AwsManager) AutoscalingApi() *AutoscalingApi {
	return &AutoscalingApi{
		service: autoscaling.New(self.session),
	}
}

func (self *AwsManager) SnsApi() *SnsApi {
	return &SnsApi{
		service: sns.New(self.session),
	}
}

func (self *AwsManager) S3Api() *S3Api {
	return &S3Api{
		service: s3.New(self.session),
	}
}
<commit_msg>Fix cause panic when using EC2Role credential<commit_after>package aws

import (
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/autoscaling"
	"github.com/aws/aws-sdk-go/service/ecs"
	"github.com/aws/aws-sdk-go/service/elb"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/sns"
)

type AwsManager struct {
	session *session.Session
}

func NewAwsManager(region string) *AwsManager {

	sess := session.New()
	cred := credentials.NewChainCredentials([]credentials.Provider{
		&credentials.EnvProvider{},
		&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
		&ec2rolecreds.EC2RoleProvider{
			Client:       ec2metadata.New(sess),
			ExpiryWindow: 5 * time.Minute,
		},
	})

	conf := aws.NewConfig().WithCredentials(cred).WithMaxRetries(aws.UseServiceDefaultRetries).WithRegion(region)

	return &AwsManager{
		session: session.New(conf),
	}
}

func (self *AwsManager) EcsApi() *EcsApi {
	return &EcsApi{
		service: ecs.New(self.session),
	}
}

func (self *AwsManager) ElbApi() *ElbApi {
	return &ElbApi{
		service: elb.New(self.session),
	}
}

func (self *AwsManager) AutoscalingApi() *AutoscalingApi {
	return &AutoscalingApi{
		service: autoscaling.New(self.session),
	}
}

func (self *AwsManager) SnsApi() *SnsApi {
	return &SnsApi{
		service: sns.New(self.session),
	}
}

func (self *AwsManager) S3Api() *S3Api {
	return &S3Api{
		service: s3.New(self.session),
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"encoding/base64"

	"github.com/aws/aws-sdk-go/service/kms"
)

// KMS -
type KMS struct {
	Client *kms.KMS
}

// NewKMS - Create new KMS client
func NewKMS(option ClientOptions) *KMS {
	client := kms.New(SDKSession())
	return &KMS{
		Client: client,
	}
}

// Encrypt plaintext using the specified key.
// Returns a base64 encoded ciphertext
func (k *KMS) Encrypt(keyID, plaintext string) (string, error) {
	input := &kms.EncryptInput{
		KeyId:     &keyID,
		Plaintext: []byte(plaintext),
	}
	output, err := k.Client.Encrypt(input)
	if err != nil {
		return "", err
	}
	ciphertext := base64.StdEncoding.EncodeToString(output.CiphertextBlob)
	return ciphertext, nil
}

// Decrypt a base64 encoded cyphertext
func (k *KMS) Decrypt(ciphertext string) (string, error) {
	ciphertextBlob, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		return "", err
	}
	input := &kms.DecryptInput{
		CiphertextBlob: []byte(ciphertextBlob),
	}
	output, err := k.Client.Decrypt(input)
	if err != nil {
		return "", err
	}
	return string(output.Plaintext), nil
}
<commit_msg>use gomplate implementation of base64 enc/dec<commit_after>package aws

import (
	b64 "github.com/hairyhenderson/gomplate/base64"

	"github.com/aws/aws-sdk-go/service/kms"
)

// KMS -
type KMS struct {
	Client *kms.KMS
}

// NewKMS - Create new KMS client
func NewKMS(option ClientOptions) *KMS {
	client := kms.New(SDKSession())
	return &KMS{
		Client: client,
	}
}

// Encrypt plaintext using the specified key.
// Returns a base64 encoded ciphertext
func (k *KMS) Encrypt(keyID, plaintext string) (string, error) {
	input := &kms.EncryptInput{
		KeyId:     &keyID,
		Plaintext: []byte(plaintext),
	}
	output, err := k.Client.Encrypt(input)
	if err != nil {
		return "", err
	}
	ciphertext, _ := b64.Encode(output.CiphertextBlob)
	return ciphertext, nil
}

// Decrypt a base64 encoded cyphertext
func (k *KMS) Decrypt(ciphertext string) (string, error) {
	ciphertextBlob, err := b64.Decode(ciphertext)
	if err != nil {
		return "", err
	}
	input := &kms.DecryptInput{
		CiphertextBlob: []byte(ciphertextBlob),
	}
	output, err := k.Client.Decrypt(input)
	if err != nil {
		return "", err
	}
	return string(output.Plaintext), nil
}
<|endoftext|>


<commit_before>package app

import (
	"context"
	"errors"
	"fmt"

	"golang.org/x/net/websocket"
)

type wsKey int

const (
	ctxKey wsKey = iota
	websocketKey
)

// HandleWebsocket has the same semantics as App.Handle, but responds to websocket
// requests rather than normal HTTP(S) requests. Use Context.Websocket to retrieve
// the *websocket.Conn in the Handler.
func (app *App) HandleWebsocket(pattern string, handler Handler, opts ...HandlerOption) {
	if handler == nil {
		panic(fmt.Errorf("handler for websocket pattern %q can't be nil", pattern))
	}
	wsHandler := websocket.Handler(func(ws *websocket.Conn) {
		ctx := ws.Request().Context().Value(ctxKey).(*Context)
		ctx.Set(websocketKey, ws)
		handler(ctx)
	})
	reqHandler := func(ctx *Context) {
		req := ctx.Request()
		newCtx := context.WithValue(req.Context(), ctxKey, ctx)
		newReq := ctx.Request().WithContext(newCtx)
		rw := ctx.ResponseWriter
		ctx.ResponseWriter = nil
		wsHandler.ServeHTTP(rw, newReq)
	}
	app.Handle(pattern, reqHandler, opts...)
}

// Websocket returns the *websocket.Conn assocciated with the current request. If the handler
// wasn't added with App.HandleWebsocket, this function will panic.
func (c *Context) Websocket() *websocket.Conn {
	ws, ok := c.Get(websocketKey).(*websocket.Conn)
	if !ok {
		panic(errors.New("no websocket assocciated with handler - did you use App.Handle() rather than App.HandleWebsocket()?"))
	}
	return ws
}
<commit_msg>Add app/Context.WebsocketURL<commit_after>package app

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"strconv"
	"strings"

	"golang.org/x/net/websocket"

	"gnd.la/internal/devutil/devserver"
)

type wsKey int

const (
	ctxKey wsKey = iota
	websocketKey
)

// HandleWebsocket has the same semantics as App.Handle, but responds to websocket
// requests rather than normal HTTP(S) requests. Use Context.Websocket to retrieve
// the *websocket.Conn in the Handler.
func (app *App) HandleWebsocket(pattern string, handler Handler, opts ...HandlerOption) {
	if handler == nil {
		panic(fmt.Errorf("handler for websocket pattern %q can't be nil", pattern))
	}
	wsHandler := websocket.Handler(func(ws *websocket.Conn) {
		ctx := ws.Request().Context().Value(ctxKey).(*Context)
		ctx.Set(websocketKey, ws)
		handler(ctx)
	})
	reqHandler := func(ctx *Context) {
		req := ctx.Request()
		newCtx := context.WithValue(req.Context(), ctxKey, ctx)
		newReq := ctx.Request().WithContext(newCtx)
		rw := ctx.ResponseWriter
		ctx.ResponseWriter = nil
		wsHandler.ServeHTTP(rw, newReq)
	}
	app.Handle(pattern, reqHandler, opts...)
}

// Websocket returns the *websocket.Conn assocciated with the current request. If the handler
// wasn't added with App.HandleWebsocket, this function will panic.
func (c *Context) Websocket() *websocket.Conn {
	ws, ok := c.Get(websocketKey).(*websocket.Conn)
	if !ok {
		panic(errors.New("no websocket assocciated with handler - did you use App.Handle() rather than App.HandleWebsocket()?"))
	}
	return ws
}

// WebsocketURL returns an absolute websocket URL from a relative
// URL (relative to the current request), adjusting also the protocol
// (e.g. http to ws and https to wss). Note that this function calls
// Context.URL(), so check its documentation to make sure the current
// request URL can be correctly determined.
func (c *Context) WebsocketURL(rel string) (*url.URL, error) {
	u, err := c.URL().Parse(rel)
	if err != nil {
		return nil, err
	}
	switch u.Scheme {
	case "http":
		u.Scheme = "ws"
	case "https":
		u.Scheme = "wss"
	}
	if sep := strings.IndexByte(u.Host, ':'); sep >= 0 && devserver.IsActive() {
		// If we're running from the development server, we need to send
		// the request directly to the app, since the httputil proxy doesn't
		// support websockets.
		u.Host = u.Host[:sep] + ":" + strconv.Itoa(c.app.Config().Port)
	}
	return u, nil
}
<|endoftext|>


<commit_before>// +build integration

package integration

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"github.com/opentable/sous/dev_support/sous_qa_setup/desc"
	"github.com/opentable/sous/util/shelltest"
)

type templatedConfigs struct {
	desc.EnvDesc
	TestDir, Workdir, Homedir, Statedir string
	XDGConfig, SSHWrapper               string
	GitSSH, GitLocation, GitRemoteBase  string
	SSHExec                             string
	GoPath, ShellPath                   []string
}

func setupConfig(t *testing.T) templatedConfigs {
	descPath := os.Getenv("SOUS_QA_DESC")
	if descPath == "" {
		t.Fatalf("SOUS_QA_DESC is empty - you need to run sous_qa_setup and set that env var")
	}

	pwd := filepath.Dir(descPath)

	envDesc, err := desc.LoadDesc(descPath)
	if err != nil {
		t.Fatalf("Couldn't load a QA env description from SOUS_QA_DESC(%q): %s", descPath, err)
	}
	if !envDesc.Complete() {
		t.Fatal("Incomplete QA env description. Re-run sous_qa_setup?")
	}

	realworkdir, err := ioutil.TempDir("", "sous-cli-testing")
	if err != nil {
		t.Fatalf("Couldn't create temporary working directory: %s", err)
	}

	workdir := "/tmp/sous-work"

	_, err = os.Stat(workdir)
	if !os.IsExist(err) {
		os.Remove(workdir)
	}
	os.Symlink(realworkdir, workdir)

	sousExeDir := filepath.Join(workdir, "sous", "bin")
	sousExe := filepath.Join(sousExeDir, "sous")
	if out, err := exec.Command("go", "build", "-o", sousExe, "..").CombinedOutput(); err != nil {
		t.Fatal(err, string(out))
	}

	stateDir := filepath.Join(workdir, "gdm")

	exePATH, err := shelltest.BuildPath("go", "git", "ssh", "cp", "egrep", "bash")
	if err != nil {
		t.Fatal(err)
	}

	sshExecPath, err := exec.LookPath("ssh")
	if err != nil {
		t.Fatal(err)
	}

	testHome := filepath.Join(workdir, "home")

	gitLocation := fmt.Sprintf("%s/%d/repos", envDesc.Git.Host, envDesc.Git.Port)
	gitRemoteBase := fmt.Sprintf("ssh://root@%s/repos", envDesc.GitOrigin())
	gitSSH := envDesc.AgentIP.String()

	sshWrapper := filepath.Join(testHome, "bin/ssh_wrapper")
	firstGoPath := filepath.Join(testHome, "go")

	shellPath := []string{sousExeDir, "~/bin", exePATH, filepath.Join(firstGoPath, "bin")}

	goPath := []string{firstGoPath}

	if userGopath := os.Getenv("GOPATH"); userGopath != "" {
		for _, userGo := range strings.Split(userGopath, ":") {
			goPath = append(goPath, userGo)
			shellPath = append(shellPath, filepath.Join(userGo, "bin"))
		}
	}

	// Speculation: the size of this struct is a metric we should consider.
	return templatedConfigs{
		TestDir:       pwd,
		EnvDesc:       envDesc,
		Workdir:       workdir,
		Homedir:       testHome,
		Statedir:      stateDir,
		XDGConfig:     filepath.Join(testHome, "dot-config"),
		SSHWrapper:    sshWrapper,
		GoPath:        goPath,
		GitSSH:        gitSSH,
		SSHExec:       sshExecPath,
		GitLocation:   gitLocation,
		GitRemoteBase: gitRemoteBase,
		ShellPath:     shellPath,
	}
}

func buildShell(name string, t *testing.T) *shelltest.ShellTest {
	cfg := setupConfig(t)

	os.MkdirAll(cfg.Homedir, os.ModePerm)
	err := shelltest.TemplateConfigs(filepath.Join(cfg.TestDir, "integration/test-homedir"), cfg.Homedir, cfg)
	if err != nil {
		t.Fatalf("Templating configuration files: %+v", err)
	}

	shell := shelltest.New(t, name, cfg,
		shelltest.WithHostEnv([]string{"DOCKER_HOST", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH"},
			map[string]string{
				"HOME":       cfg.Homedir,
				"XDG_CONFIG": cfg.XDGConfig,
				"GIT_SSH":    cfg.SSHWrapper,
				"GOROOT":     cfg.GoPath[0],
				"GOPATH":     strings.Join(cfg.GoPath, ":"),
				"PATH":       strings.Join(cfg.ShellPath, ":"),
			}))

	shell.WriteTo("../doc/shellexamples")
	//shell.DebugPrefix("shell") //useful especially if test timeout interrupts

	return shell
}

func TestShellLevelIntegration(t *testing.T) {
	shell := buildShell("happypath", t)

	defaultCheck := func(name string, res shelltest.Result, t *testing.T) {
		if len(res.Errs) > 0 {
			t.Errorf("Error in %s: \n\t%s", name, res.Errs)
		}
	}

	preconditions := shell.Block("Preconditions for CLI integration tests", `
	go get github.com/nyarly/cygnus # cygnus lets us inspect Singularity for ports
	if echo "{{.EnvDesc.SingularityURL}}" | egrep -q '192.168|127.0.0'; then
		{{.TestDir}}/integration/test-registry/clean-singularity.sh {{.EnvDesc.SingularityURL}}
	fi
	cygnus -H {{.EnvDesc.SingularityURL}}
	ls {{.TestDir}}/dev_support
	`, func(name string, res shelltest.Result, t *testing.T) {
		if len(res.Errs) > 0 {
			t.Errorf("Error in %s: \n\t%s", name, res.Errs)
		}
		if res.Matches("repos>sous-server.*0_0_2") {
			msg, err := shell.Template("clean-sing", "Running sous-server already - try `./integration/test-registry/clean-singularity.sh {{.EnvDesc.SingularityURL}}`")
			if err != nil {
				t.Error("Running sous-server already - try `./integration/test-registry/clean-singularity.sh <singularity-url>`")
			}
			t.Error(msg)
		}
		if !res.Matches("sous_linux") {
			t.Error("No sous_linux available - run `make linux_build`")
		}
	})

	prologue := preconditions.Block("Test environment setup", `
	# These steps are required by the Sous integration tests
	# They're analogous to run-of-the-mill workstation maintenance.

	cd {{.TestDir}}
	env
	export SOUS_EXTRA_DOCKER_CA={{.TestDir}}/integration/test-registry/docker-registry/testing.crt
	mkdir -p {{index .GoPath 0}}/{src,bin}

	### This build gives me trouble in tests...
	### xgo does something weird and different with it's dep-cache dir
	# GOPATH={{index .GoPath 0}} make linux_build # we need Sous built for linux for the server
	go install . #install the current sous project
	cp integration/test-registry/git-server/git_pubkey_rsa* ~/dot-ssh/

	cd {{.Workdir}}
	chmod go-rwx -R ~/dot-ssh
	chmod +x -R ~/bin/*
	ssh -o ConnectTimeout=1 -o PasswordAuthentication=no -F "${HOME}/dot-ssh/config" root@{{.GitSSH}} -p 2222 /reset-repos < /dev/null
	`,
		defaultCheck)

	createGDM := prologue.Block("create the GDM", `
	git clone {{.GitRemoteBase}}/gdm
	cp ~/templated-configs/defs.yaml gdm/defs.yaml
	cat gdm/defs.yaml
	pushd gdm
	cat ~/.config/git/config >> .git/config # Eh?
	git add defs.yaml
	git commit -am "Adding defs.yaml"
	git push
	popd
	`, defaultCheck)

	// XXX There should be a `-cluster left,right` syntax, instead of two deploy commands
	setup := createGDM.Block("deploy sous server", `
	sous config
	cat ~/.config/sous/config.yaml
	git clone {{.GitRemoteBase}}/sous-server
	pushd sous-server
	export SOUS_USER_NAME=test SOUS_USER_EMAIL=test@test.com
	export SOUS_SERVER= SOUS_STATE_LOCATION={{.Statedir}}

	sous init
	sous manifest get
	sous manifest set < ~/templated-configs/sous-server.yaml
	sous manifest get # demonstrating this got to GDM

	# Last minute config
	cat Dockerfile
	cp ~/dot-ssh/git_pubkey_rsa key_sous@example.com
	cp {{.TestDir}}/dev_support/$(readlink {{.TestDir}}/dev_support/sous_linux) .
	cp {{.TestDir}}/integration/test-registry/docker-registry/testing.crt docker.crt

	ls -a
	ssh-keyscan -p 2222 {{.GitSSH}} > known_hosts

	git add key_sous@example.com known_hosts sous
	git commit -am "Adding ephemeral files"
	git tag -am "0.0.2" 0.0.2
	git push
	git push --tags

	sous build
	sous deploy -cluster left # We expect to see 'Sous is running ... in workstation mode' here:
	sous deploy -cluster right
	unset SOUS_SERVER
	unset SOUS_STATE_LOCATION
	popd
	`,
		func(name string, res shelltest.Result, t *testing.T) {
			if len(res.Errs) > 0 {
				t.Errorf("Trouble building GDM: \n\t%s", res.Errs)
			}
		})

	// This is where regular use starts
	config := setup.Block("configuration", `
	# This is kind of a hack - in normal operation, Sous would block until its
	# services had been accepted, but when bootstrapping, we need to wait for them
	# to come up.
	while [ $(cygnus -H {{.EnvDesc.SingularityURL}} | grep sous-server | wc -l) -lt 2 ]; do
	  sleep 0.1
	done
	cygnus --env TASK_HOST --env PORT0 {{.EnvDesc.SingularityURL}}

	leftport=$(cygnus --env PORT0 {{.EnvDesc.SingularityURL}} | grep 'sous-server.*left' | awk '{ print $3 }')
	rightport=$(cygnus --env PORT0 {{.EnvDesc.SingularityURL}} | grep 'sous-server.*right' | awk '{ print $3 }')
	serverURL=http://{{.EnvDesc.AgentIP}}:$leftport

	until curl -s -I $serverURL; do
	  sleep 0.1
	done
	sous config Server "$serverURL"
	echo "Server URL is:" $(sous config Server)

	ETAG=$(curl -s -v http://192.168.99.100:$leftport/servers 2>&1 | sed -n '/Etag:/{s/.*: //; P; }')
	echo $ETAG
	sed "s/LEFTPORT/$leftport/; s/RIGHTPORT/$rightport/" < ~/templated-configs/servers.json > ~/servers.json
	cat ~/servers.json
	curl -v -X PUT -H "If-Match: ${ETAG//[$'\t\r\n ']}" -H "Content-Type: application/json" "${serverURL}/servers" --data "$(< ~/servers.json)"
	curl -s "${serverURL}/servers"
	`,
		func(name string, res shelltest.Result, t *testing.T) {
			if len(res.Errs) > 0 {
				t.Errorf("Trouble building GDM: \n\t%s", res.Errs)
			}

			if !res.Matches(`URL is: http`) {
				t.Fatalf("Sous server not running!")
			}
		})

	deploy := config.Block("deploy project", `
	cat $XDG_CONFIG/sous/config.yaml
	sous config
	git clone {{.GitRemoteBase}}/sous-demo
	cd sous-demo
	git tag -am 'Release!' 0.0.23
	git push --tags
	sous init
	sous build
	sous deploy -cluster left
	`, defaultCheck)

	//check :=
	deploy.Block("confirm deployment", `
	cygnus -x 1 {{.EnvDesc.SingularityURL}}
	`, func(name string, res shelltest.Result, t *testing.T) {
		defaultCheck(name, res, t)
		if !res.Matches("sous-demo") {
			t.Error("No sous-demo request running!")
		}
	})
}
<commit_msg>Travis<commit_after>// +build integration

package integration

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"github.com/opentable/sous/dev_support/sous_qa_setup/desc"
	"github.com/opentable/sous/util/shelltest"
)

type templatedConfigs struct {
	desc.EnvDesc
	TestDir, Workdir, Homedir, Statedir string
	XDGConfig, SSHWrapper               string
	GitSSH, GitLocation, GitRemoteBase  string
	SSHExec                             string
	GoPath, ShellPath                   []string
}

func setupConfig(t *testing.T) templatedConfigs {
	descPath := os.Getenv("SOUS_QA_DESC")
	if descPath == "" {
		t.Fatalf("SOUS_QA_DESC is empty - you need to run sous_qa_setup and set that env var")
	}

	pwd := filepath.Dir(descPath)

	envDesc, err := desc.LoadDesc(descPath)
	if err != nil {
		t.Fatalf("Couldn't load a QA env description from SOUS_QA_DESC(%q): %s", descPath, err)
	}
	if !envDesc.Complete() {
		t.Fatal("Incomplete QA env description. Re-run sous_qa_setup?")
	}

	realworkdir, err := ioutil.TempDir("", "sous-cli-testing")
	if err != nil {
		t.Fatalf("Couldn't create temporary working directory: %s", err)
	}

	workdir := "/tmp/sous-work"

	_, err = os.Stat(workdir)
	if !os.IsExist(err) {
		os.Remove(workdir)
	}
	os.Symlink(realworkdir, workdir)

	sousExeDir := filepath.Join(workdir, "sous", "bin")
	sousExe := filepath.Join(sousExeDir, "sous")
	if out, err := exec.Command("go", "build", "-o", sousExe, "..").CombinedOutput(); err != nil {
		t.Fatal(err, string(out))
	}

	stateDir := filepath.Join(workdir, "gdm")

	exePATH, err := shelltest.BuildPath("go", "git", "ssh", "cp", "egrep", "bash")
	if err != nil {
		t.Fatal(err)
	}

	sshExecPath, err := exec.LookPath("ssh")
	if err != nil {
		t.Fatal(err)
	}

	testHome := filepath.Join(workdir, "home")

	gitLocation := fmt.Sprintf("%s/%d/repos", envDesc.Git.Host, envDesc.Git.Port)
	gitRemoteBase := fmt.Sprintf("ssh://root@%s/repos", envDesc.GitOrigin())
	gitSSH := envDesc.AgentIP.String()

	sshWrapper := filepath.Join(testHome, "bin/ssh_wrapper")
	firstGoPath := filepath.Join(testHome, "go")

	shellPath := []string{sousExeDir, "~/bin", exePATH, filepath.Join(firstGoPath, "bin")}

	goPath := []string{firstGoPath}

	if userGopath := os.Getenv("GOPATH"); userGopath != "" {
		for _, userGo := range strings.Split(userGopath, ":") {
			goPath = append(goPath, userGo)
			shellPath = append(shellPath, filepath.Join(userGo, "bin"))
		}
	}

	// Speculation: the size of this struct is a metric we should consider.
	return templatedConfigs{
		TestDir:       pwd,
		EnvDesc:       envDesc,
		Workdir:       workdir,
		Homedir:       testHome,
		Statedir:      stateDir,
		XDGConfig:     filepath.Join(testHome, "dot-config"),
		SSHWrapper:    sshWrapper,
		GoPath:        goPath,
		GitSSH:        gitSSH,
		SSHExec:       sshExecPath,
		GitLocation:   gitLocation,
		GitRemoteBase: gitRemoteBase,
		ShellPath:     shellPath,
	}
}

func buildShell(name string, t *testing.T) *shelltest.ShellTest {
	cfg := setupConfig(t)

	os.MkdirAll(cfg.Homedir, os.ModePerm)
	err := shelltest.TemplateConfigs(filepath.Join(cfg.TestDir, "integration/test-homedir"), cfg.Homedir, cfg)
	if err != nil {
		t.Fatalf("Templating configuration files: %+v", err)
	}

	shell := shelltest.New(t, name, cfg,
		shelltest.WithHostEnv([]string{"DOCKER_HOST", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH"},
			map[string]string{
				"HOME":       cfg.Homedir,
				"XDG_CONFIG": cfg.XDGConfig,
				"GIT_SSH":    cfg.SSHWrapper,
				"GOROOT":     cfg.GoPath[0],
				"GOPATH":     strings.Join(cfg.GoPath, ":"),
				"PATH":       strings.Join(cfg.ShellPath, ":"),
			}))

	shell.WriteTo("../doc/shellexamples")
	//shell.DebugPrefix("shell") //useful especially if test timeout interrupts

	return shell
}

func TestShellLevelIntegration(t *testing.T) {
	shell := buildShell("happypath", t)

	defaultCheck := func(name string, res shelltest.Result, t *testing.T) {
		if len(res.Errs) > 0 {
			t.Errorf("Error in %s: \n\t%s", name, res.Errs)
		}
	}

	preconditions := shell.Block("Preconditions for CLI integration tests", `
	mkdir -p $GOROOT
	go get github.com/nyarly/cygnus # cygnus lets us inspect Singularity for ports
	if echo "{{.EnvDesc.SingularityURL}}" | egrep -q '192.168|127.0.0'; then
		{{.TestDir}}/integration/test-registry/clean-singularity.sh {{.EnvDesc.SingularityURL}}
	fi
	cygnus -H {{.EnvDesc.SingularityURL}}
	ls {{.TestDir}}/dev_support
	`, func(name string, res shelltest.Result, t *testing.T) {
		if len(res.Errs) > 0 {
			t.Errorf("Error in %s: \n\t%s", name, res.Errs)
		}
		if res.Matches("repos>sous-server.*0_0_2") {
			msg, err := shell.Template("clean-sing", "Running sous-server already - try `./integration/test-registry/clean-singularity.sh {{.EnvDesc.SingularityURL}}`")
			if err != nil {
				t.Error("Running sous-server already - try `./integration/test-registry/clean-singularity.sh <singularity-url>`")
			}
			t.Error(msg)
		}
		if !res.Matches("sous_linux") {
			t.Error("No sous_linux available - run `make linux_build`")
		}
	})

	prologue := preconditions.Block("Test environment setup", `
	# These steps are required by the Sous integration tests
	# They're analogous to run-of-the-mill workstation maintenance.

	cd {{.TestDir}}
	env
	export SOUS_EXTRA_DOCKER_CA={{.TestDir}}/integration/test-registry/docker-registry/testing.crt
	mkdir -p {{index .GoPath 0}}/{src,bin}

	### This build gives me trouble in tests...
	### xgo does something weird and different with it's dep-cache dir
	# GOPATH={{index .GoPath 0}} make linux_build # we need Sous built for linux for the server
	go install . #install the current sous project
	cp integration/test-registry/git-server/git_pubkey_rsa* ~/dot-ssh/

	cd {{.Workdir}}
	chmod go-rwx -R ~/dot-ssh
	chmod +x -R ~/bin/*
	ssh -o ConnectTimeout=1 -o PasswordAuthentication=no -F "${HOME}/dot-ssh/config" root@{{.GitSSH}} -p 2222 /reset-repos < /dev/null
	`,
		defaultCheck)

	createGDM := prologue.Block("create the GDM", `
	git clone {{.GitRemoteBase}}/gdm
	cp ~/templated-configs/defs.yaml gdm/defs.yaml
	cat gdm/defs.yaml
	pushd gdm
	cat ~/.config/git/config >> .git/config # Eh?
	git add defs.yaml
	git commit -am "Adding defs.yaml"
	git push
	popd
	`, defaultCheck)

	// XXX There should be a `-cluster left,right` syntax, instead of two deploy commands
	setup := createGDM.Block("deploy sous server", `
	sous config
	cat ~/.config/sous/config.yaml
	git clone {{.GitRemoteBase}}/sous-server
	pushd sous-server
	export SOUS_USER_NAME=test SOUS_USER_EMAIL=test@test.com
	export SOUS_SERVER= SOUS_STATE_LOCATION={{.Statedir}}

	sous init
	sous manifest get
	sous manifest set < ~/templated-configs/sous-server.yaml
	sous manifest get # demonstrating this got to GDM

	# Last minute config
	cat Dockerfile
	cp ~/dot-ssh/git_pubkey_rsa key_sous@example.com
	cp {{.TestDir}}/dev_support/$(readlink {{.TestDir}}/dev_support/sous_linux) .
	cp {{.TestDir}}/integration/test-registry/docker-registry/testing.crt docker.crt

	ls -a
	ssh-keyscan -p 2222 {{.GitSSH}} > known_hosts

	git add key_sous@example.com known_hosts sous
	git commit -am "Adding ephemeral files"
	git tag -am "0.0.2" 0.0.2
	git push
	git push --tags

	sous build
	sous deploy -cluster left # We expect to see 'Sous is running ... in workstation mode' here:
	sous deploy -cluster right
	unset SOUS_SERVER
	unset SOUS_STATE_LOCATION
	popd
	`,
		func(name string, res shelltest.Result, t *testing.T) {
			if len(res.Errs) > 0 {
				t.Errorf("Trouble building GDM: \n\t%s", res.Errs)
			}
		})

	// This is where regular use starts
	config := setup.Block("configuration", `
	# This is kind of a hack - in normal operation, Sous would block until its
	# services had been accepted, but when bootstrapping, we need to wait for them
	# to come up.
	while [ $(cygnus -H {{.EnvDesc.SingularityURL}} | grep sous-server | wc -l) -lt 2 ]; do
	  sleep 0.1
	done
	cygnus --env TASK_HOST --env PORT0 {{.EnvDesc.SingularityURL}}

	leftport=$(cygnus --env PORT0 {{.EnvDesc.SingularityURL}} | grep 'sous-server.*left' | awk '{ print $3 }')
	rightport=$(cygnus --env PORT0 {{.EnvDesc.SingularityURL}} | grep 'sous-server.*right' | awk '{ print $3 }')
	serverURL=http://{{.EnvDesc.AgentIP}}:$leftport

	until curl -s -I $serverURL; do
	  sleep 0.1
	done
	sous config Server "$serverURL"
	echo "Server URL is:" $(sous config Server)

	ETAG=$(curl -s -v http://192.168.99.100:$leftport/servers 2>&1 | sed -n '/Etag:/{s/.*: //; P; }')
	echo $ETAG
	sed "s/LEFTPORT/$leftport/; s/RIGHTPORT/$rightport/" < ~/templated-configs/servers.json > ~/servers.json
	cat ~/servers.json
	curl -v -X PUT -H "If-Match: ${ETAG//[$'\t\r\n ']}" -H "Content-Type: application/json" "${serverURL}/servers" --data "$(< ~/servers.json)"
	curl -s "${serverURL}/servers"
	`,
		func(name string, res shelltest.Result, t *testing.T) {
			if len(res.Errs) > 0 {
				t.Errorf("Trouble building GDM: \n\t%s", res.Errs)
			}

			if !res.Matches(`URL is: http`) {
				t.Fatalf("Sous server not running!")
			}
		})

	deploy := config.Block("deploy project", `
	cat $XDG_CONFIG/sous/config.yaml
	sous config
	git clone {{.GitRemoteBase}}/sous-demo
	cd sous-demo
	git tag -am 'Release!' 0.0.23
	git push --tags
	sous init
	sous build
	sous deploy -cluster left
	`, defaultCheck)

	//check :=
	deploy.Block("confirm deployment", `
	cygnus -x 1 {{.EnvDesc.SingularityURL}}
	`, func(name string, res shelltest.Result, t *testing.T) {
		defaultCheck(name, res, t)
		if !res.Matches("sous-demo") {
			t.Error("No sous-demo request running!")
		}
	})
}
<|endoftext|>


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

package gps

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sync/atomic"

	"github.com/golang/dep/internal/gps/pkgtree"
)

// sourceBridge is an adapter to SourceManagers that tailor operations for a
// single solve run.
type sourceBridge interface {
	// sourceBridge includes all the methods in the SourceManager interface except
	// for Release().
	SourceExists(ProjectIdentifier) (bool, error)
	SyncSourceFor(ProjectIdentifier) error
	RevisionPresentIn(ProjectIdentifier, Revision) (bool, error)
	ListPackages(ProjectIdentifier, Version) (pkgtree.PackageTree, error)
	GetManifestAndLock(ProjectIdentifier, Version, ProjectAnalyzer) (Manifest, Lock, error)
	ExportProject(ProjectIdentifier, Version, string) error
	DeduceProjectRoot(ip string) (ProjectRoot, error)

	//sourceExists(ProjectIdentifier) (bool, error)
	//syncSourceFor(ProjectIdentifier) error
	listVersions(ProjectIdentifier) ([]Version, error)
	//revisionPresentIn(ProjectIdentifier, Revision) (bool, error)
	//listPackages(ProjectIdentifier, Version) (pkgtree.PackageTree, error)
	//getManifestAndLock(ProjectIdentifier, Version, ProjectAnalyzer) (Manifest, Lock, error)
	//exportProject(ProjectIdentifier, Version, string) error
	//deduceProjectRoot(ip string) (ProjectRoot, error)
	verifyRootDir(path string) error
	vendorCodeExists(ProjectIdentifier) (bool, error)
	breakLock()
}

// bridge is an adapter around a proper SourceManager. It provides localized
// caching that's tailored to the requirements of a particular solve run.
//
// Finally, it provides authoritative version/constraint operations, ensuring
// that any possible approach to a match - even those not literally encoded in
// the inputs - is achieved.
type bridge struct {
	// The underlying, adapted-to SourceManager
	sm SourceManager

	// The solver which we're assisting.
	//
	// The link between solver and bridge is circular, which is typically a bit
	// awkward, but the bridge needs access to so many of the input arguments
	// held by the solver that it ends up being easier and saner to do this.
	s *solver

	// Map of project root name to their available version list. This cache is
	// layered on top of the proper SourceManager's cache; the only difference
	// is that this keeps the versions sorted in the direction required by the
	// current solve run.
	vlists map[ProjectIdentifier][]Version

	// Indicates whether lock breaking has already been run
	lockbroken int32

	// Whether to sort version lists for downgrade.
	down bool

	// The cancellation context provided to the solver. Threading it through the
	// various solver methods is needlessly verbose so long as we maintain the
	// lifetime guarantees that a solver can only be run once, so we
	ctx context.Context
}

// mkBridge creates a bridge
func mkBridge(s *solver, sm SourceManager, down bool) *bridge {
	return &bridge{
		sm:     sm,
		s:      s,
		down:   down,
		vlists: make(map[ProjectIdentifier][]Version),
	}
}

func (b *bridge) GetManifestAndLock(id ProjectIdentifier, v Version, an ProjectAnalyzer) (Manifest, Lock, error) {
	if b.s.rd.isRoot(id.ProjectRoot) {
		return b.s.rd.rm, b.s.rd.rl, nil
	}

	b.s.mtr.push("b-gmal")
	m, l, e := b.sm.GetManifestAndLock(id, v, an)
	b.s.mtr.pop()
	return m, l, e
}

func (b *bridge) listVersions(id ProjectIdentifier) ([]Version, error) {
	if vl, exists := b.vlists[id]; exists {
		return vl, nil
	}

	b.s.mtr.push("b-list-versions")
	pvl, err := b.sm.ListVersions(id)
	if err != nil {
		b.s.mtr.pop()
		return nil, err
	}

	vl := hidePair(pvl)
	if b.down {
		SortForDowngrade(vl)
	} else {
		SortForUpgrade(vl)
	}

	b.vlists[id] = vl
	b.s.mtr.pop()
	return vl, nil
}

func (b *bridge) RevisionPresentIn(id ProjectIdentifier, r Revision) (bool, error) {
	b.s.mtr.push("b-rev-present-in")
	i, e := b.sm.RevisionPresentIn(id, r)
	b.s.mtr.pop()
	return i, e
}

func (b *bridge) SourceExists(id ProjectIdentifier) (bool, error) {
	b.s.mtr.push("b-source-exists")
	i, e := b.sm.SourceExists(id)
	b.s.mtr.pop()
	return i, e
}

func (b *bridge) vendorCodeExists(id ProjectIdentifier) (bool, error) {
	fi, err := os.Stat(filepath.Join(b.s.rd.dir, "vendor", string(id.ProjectRoot)))
	if err != nil {
		return false, err
	} else if fi.IsDir() {
		return true, nil
	}

	return false, nil
}

// listPackages lists all the packages contained within the given project at a
// particular version.
//
// The root project is handled separately, as the source manager isn't
// responsible for that code.
func (b *bridge) ListPackages(id ProjectIdentifier, v Version) (pkgtree.PackageTree, error) {
	if b.s.rd.isRoot(id.ProjectRoot) {
		return b.s.rd.rpt, nil
	}

	b.s.mtr.push("b-list-pkgs")
	pt, err := b.sm.ListPackages(id, v)
	b.s.mtr.pop()
	return pt, err
}

func (b *bridge) ExportProject(id ProjectIdentifier, v Version, path string) error {
	panic("bridge should never be used to ExportProject")
}

// verifyRoot ensures that the provided path to the project root is in good
// working condition. This check is made only once, at the beginning of a solve
// run.
func (b *bridge) verifyRootDir(path string) error {
	if fi, err := os.Stat(path); err != nil {
		return badOptsFailure(fmt.Sprintf("could not read project root (%s): %s", path, err))
	} else if !fi.IsDir() {
		return badOptsFailure(fmt.Sprintf("project root (%s) is a file, not a directory", path))
	}

	return nil
}

func (b *bridge) DeduceProjectRoot(ip string) (ProjectRoot, error) {
	b.s.mtr.push("b-deduce-proj-root")
	pr, e := b.sm.DeduceProjectRoot(ip)
	b.s.mtr.pop()
	return pr, e
}

// breakLock is called when the solver has to break a version recorded in the
// lock file. It prefetches all the projects in the solver's lock, so that the
// information is already on hand if/when the solver needs it.
//
// Projects that have already been selected are skipped, as it's generally unlikely that the
// solver will have to backtrack through and fully populate their version queues.
func (b *bridge) breakLock() {
	// No real conceivable circumstance in which multiple calls are made to
	// this, but being that this is the entrance point to a bunch of async work,
	// protect it with an atomic CAS in case things change in the future.
	//
	// We avoid using a sync.Once here, as there's no reason for other callers
	// to block until completion.
	if !atomic.CompareAndSwapInt32(&b.lockbroken, 0, 1) {
		return
	}

	for _, lp := range b.s.rd.rl.Projects() {
		if _, is := b.s.sel.selected(lp.pi); !is {
			pi, v := lp.pi, lp.Version()
			go func() {
				// Sync first
				b.sm.SyncSourceFor(pi)
				// Preload the package info for the locked version, too, as
				// we're more likely to need that
				b.sm.ListPackages(pi, v)
			}()
		}
	}
}

func (b *bridge) SyncSourceFor(id ProjectIdentifier) error {
	// we don't track metrics here b/c this is often called in its own goroutine
	// by the solver, and the metrics design is for wall time on a single thread
	return b.sm.SyncSourceFor(id)
}
<commit_msg>gps: Chop off incomplete comment<commit_after>// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gps

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sync/atomic"

	"github.com/golang/dep/internal/gps/pkgtree"
)

// sourceBridge is an adapter to SourceManagers that tailor operations for a
// single solve run.
type sourceBridge interface {
	// sourceBridge includes all the methods in the SourceManager interface except
	// for Release().
	SourceExists(ProjectIdentifier) (bool, error)
	SyncSourceFor(ProjectIdentifier) error
	RevisionPresentIn(ProjectIdentifier, Revision) (bool, error)
	ListPackages(ProjectIdentifier, Version) (pkgtree.PackageTree, error)
	GetManifestAndLock(ProjectIdentifier, Version, ProjectAnalyzer) (Manifest, Lock, error)
	ExportProject(ProjectIdentifier, Version, string) error
	DeduceProjectRoot(ip string) (ProjectRoot, error)

	//sourceExists(ProjectIdentifier) (bool, error)
	//syncSourceFor(ProjectIdentifier) error
	listVersions(ProjectIdentifier) ([]Version, error)
	//revisionPresentIn(ProjectIdentifier, Revision) (bool, error)
	//listPackages(ProjectIdentifier, Version) (pkgtree.PackageTree, error)
	//getManifestAndLock(ProjectIdentifier, Version, ProjectAnalyzer) (Manifest, Lock, error)
	//exportProject(ProjectIdentifier, Version, string) error
	//deduceProjectRoot(ip string) (ProjectRoot, error)
	verifyRootDir(path string) error
	vendorCodeExists(ProjectIdentifier) (bool, error)
	breakLock()
}

// bridge is an adapter around a proper SourceManager. It provides localized
// caching that's tailored to the requirements of a particular solve run.
//
// Finally, it provides authoritative version/constraint operations, ensuring
// that any possible approach to a match - even those not literally encoded in
// the inputs - is achieved.
type bridge struct {
	// The underlying, adapted-to SourceManager
	sm SourceManager

	// The solver which we're assisting.
	//
	// The link between solver and bridge is circular, which is typically a bit
	// awkward, but the bridge needs access to so many of the input arguments
	// held by the solver that it ends up being easier and saner to do this.
	s *solver

	// Map of project root name to their available version list. This cache is
	// layered on top of the proper SourceManager's cache; the only difference
	// is that this keeps the versions sorted in the direction required by the
	// current solve run.
	vlists map[ProjectIdentifier][]Version

	// Indicates whether lock breaking has already been run
	lockbroken int32

	// Whether to sort version lists for downgrade.
	down bool

	// The cancellation context provided to the solver. Threading it through the
	// various solver methods is needlessly verbose so long as we maintain the
	// lifetime guarantees that a solver can only be run once.
	ctx context.Context
}

// mkBridge creates a bridge
func mkBridge(s *solver, sm SourceManager, down bool) *bridge {
	return &bridge{
		sm:     sm,
		s:      s,
		down:   down,
		vlists: make(map[ProjectIdentifier][]Version),
	}
}

func (b *bridge) GetManifestAndLock(id ProjectIdentifier, v Version, an ProjectAnalyzer) (Manifest, Lock, error) {
	if b.s.rd.isRoot(id.ProjectRoot) {
		return b.s.rd.rm, b.s.rd.rl, nil
	}

	b.s.mtr.push("b-gmal")
	m, l, e := b.sm.GetManifestAndLock(id, v, an)
	b.s.mtr.pop()
	return m, l, e
}

func (b *bridge) listVersions(id ProjectIdentifier) ([]Version, error) {
	if vl, exists := b.vlists[id]; exists {
		return vl, nil
	}

	b.s.mtr.push("b-list-versions")
	pvl, err := b.sm.ListVersions(id)
	if err != nil {
		b.s.mtr.pop()
		return nil, err
	}

	vl := hidePair(pvl)
	if b.down {
		SortForDowngrade(vl)
	} else {
		SortForUpgrade(vl)
	}

	b.vlists[id] = vl
	b.s.mtr.pop()
	return vl, nil
}

func (b *bridge) RevisionPresentIn(id ProjectIdentifier, r Revision) (bool, error) {
	b.s.mtr.push("b-rev-present-in")
	i, e := b.sm.RevisionPresentIn(id, r)
	b.s.mtr.pop()
	return i, e
}

func (b *bridge) SourceExists(id ProjectIdentifier) (bool, error) {
	b.s.mtr.push("b-source-exists")
	i, e := b.sm.SourceExists(id)
	b.s.mtr.pop()
	return i, e
}

func (b *bridge) vendorCodeExists(id ProjectIdentifier) (bool, error) {
	fi, err := os.Stat(filepath.Join(b.s.rd.dir, "vendor", string(id.ProjectRoot)))
	if err != nil {
		return false, err
	} else if fi.IsDir() {
		return true, nil
	}

	return false, nil
}

// listPackages lists all the packages contained within the given project at a
// particular version.
//
// The root project is handled separately, as the source manager isn't
// responsible for that code.
func (b *bridge) ListPackages(id ProjectIdentifier, v Version) (pkgtree.PackageTree, error) {
	if b.s.rd.isRoot(id.ProjectRoot) {
		return b.s.rd.rpt, nil
	}

	b.s.mtr.push("b-list-pkgs")
	pt, err := b.sm.ListPackages(id, v)
	b.s.mtr.pop()
	return pt, err
}

func (b *bridge) ExportProject(id ProjectIdentifier, v Version, path string) error {
	panic("bridge should never be used to ExportProject")
}

// verifyRoot ensures that the provided path to the project root is in good
// working condition. This check is made only once, at the beginning of a solve
// run.
func (b *bridge) verifyRootDir(path string) error {
	if fi, err := os.Stat(path); err != nil {
		return badOptsFailure(fmt.Sprintf("could not read project root (%s): %s", path, err))
	} else if !fi.IsDir() {
		return badOptsFailure(fmt.Sprintf("project root (%s) is a file, not a directory", path))
	}

	return nil
}

func (b *bridge) DeduceProjectRoot(ip string) (ProjectRoot, error) {
	b.s.mtr.push("b-deduce-proj-root")
	pr, e := b.sm.DeduceProjectRoot(ip)
	b.s.mtr.pop()
	return pr, e
}

// breakLock is called when the solver has to break a version recorded in the
// lock file. It prefetches all the projects in the solver's lock, so that the
// information is already on hand if/when the solver needs it.
//
// Projects that have already been selected are skipped, as it's generally unlikely that the
// solver will have to backtrack through and fully populate their version queues.
func (b *bridge) breakLock() {
	// No real conceivable circumstance in which multiple calls are made to
	// this, but being that this is the entrance point to a bunch of async work,
	// protect it with an atomic CAS in case things change in the future.
	//
	// We avoid using a sync.Once here, as there's no reason for other callers
	// to block until completion.
	if !atomic.CompareAndSwapInt32(&b.lockbroken, 0, 1) {
		return
	}

	for _, lp := range b.s.rd.rl.Projects() {
		if _, is := b.s.sel.selected(lp.pi); !is {
			pi, v := lp.pi, lp.Version()
			go func() {
				// Sync first
				b.sm.SyncSourceFor(pi)
				// Preload the package info for the locked version, too, as
				// we're more likely to need that
				b.sm.ListPackages(pi, v)
			}()
		}
	}
}

func (b *bridge) SyncSourceFor(id ProjectIdentifier) error {
	// we don't track metrics here b/c this is often called in its own goroutine
	// by the solver, and the metrics design is for wall time on a single thread
	return b.sm.SyncSourceFor(id)
}
<|endoftext|>


<commit_before>package crawler

import (
	"errors"
	"net/http"
	"time"

	log "github.com/meifamily/logrus"

	"github.com/meifamily/ptt-alertor/models/ptt/article"

	"regexp"

	"strings"

	"golang.org/x/net/html"
)

const pttHostURL = "https://www.ptt.cc"

// BuildArticles makes board's index articles to a article slice
func BuildArticles(board string) (article.Articles, error) {

	reqURL := makeBoardURL(board)
	rsp, err := fetchHTML(reqURL)
	if err != nil {
		return article.Articles{}, err
	}
	htmlNodes := parseHTML(rsp)

	articleBlocks := traverseHTMLNode(htmlNodes, findArticleBlocks)
	initialTargetNodes()
	articles := make(article.Articles, len(articleBlocks))
	for index, articleBlock := range articleBlocks {
		for _, titleDiv := range traverseHTMLNode(articleBlock, findTitleDiv) {
			initialTargetNodes()

			anchors := traverseHTMLNode(titleDiv, findAnchor)

			if len(anchors) == 0 {
				articles[index].Title = titleDiv.FirstChild.Data
				articles[index].Link = ""
				continue
			}

			for _, anchor := range traverseHTMLNode(titleDiv, findAnchor) {
				articles[index].Title = anchor.FirstChild.Data
				link := pttHostURL + getAnchorLink(anchor)
				articles[index].Link = link
				articles[index].ID = articles[index].ParseID(link)
			}
		}
		for _, metaDiv := range traverseHTMLNode(articleBlock, findMetaDiv) {
			initialTargetNodes()

			for _, date := range traverseHTMLNode(metaDiv, findDateDiv) {
				articles[index].Date = date.FirstChild.Data
			}
			for _, author := range traverseHTMLNode(metaDiv, findAuthorDiv) {
				articles[index].Author = author.FirstChild.Data
			}
		}
	}
	return articles, nil
}

// BuildArticle build article object from html
func BuildArticle(board, articleCode string) (article.Article, error) {

	reqURL := makeArticleURL(board, articleCode)
	rsp, err := fetchHTML(reqURL)
	if err != nil {
		return article.Article{}, err
	}
	htmlNodes := parseHTML(rsp)
	atcl := article.Article{
		Link:  reqURL,
		Code:  articleCode,
		Board: board,
	}
	nodes := traverseHTMLNode(htmlNodes, findOgTitleMeta)
	if len(nodes) > 0 {
		atcl.Title = getMetaContent(nodes[0])
	} else {
		atcl.Title = "[內文標題已被刪除]"
	}
	atcl.ID = atcl.ParseID(reqURL)
	pushBlocks := traverseHTMLNode(htmlNodes, findPushBlocks)
	initialTargetNodes()
	pushes := make([]article.Push, len(pushBlocks))
	for index, pushBlock := range pushBlocks {
		for _, pushTag := range traverseHTMLNode(pushBlock, findPushTag) {
			initialTargetNodes()
			pushes[index].Tag = pushTag.FirstChild.Data
		}
		for _, pushUserID := range traverseHTMLNode(pushBlock, findPushUserID) {
			initialTargetNodes()
			pushes[index].UserID = pushUserID.FirstChild.Data
		}
		for _, pushContent := range traverseHTMLNode(pushBlock, findPushContent) {
			initialTargetNodes()
			content := pushContent.FirstChild.Data
			for n := pushContent.FirstChild.NextSibling; n != nil; n = n.NextSibling {
				if findEmailProtected(n) != nil {
					break
				}
				if n.FirstChild != nil {
					content += n.FirstChild.Data
				}
				if n.NextSibling != nil {
					content += n.NextSibling.Data
				}
			}
			pushes[index].Content = content
		}
		for _, pushIPDateTime := range traverseHTMLNode(pushBlock, findPushIPDateTime) {
			initialTargetNodes()
			ipdatetime := strings.TrimSpace(pushIPDateTime.FirstChild.Data)
			if ipdatetime == "" {
				break
			}
			dateTime, err := parseDateTime(ipdatetime)
			if err != nil {
				log.WithFields(log.Fields{
					"ipdatetime": ipdatetime,
					"board":      board,
					"code":       articleCode,
				}).WithError(err).Error("Parse DateTime Error")
			}
			pushes[index].DateTime = dateTime
			if index == len(pushBlocks)-1 {
				atcl.LastPushDateTime = pushes[index].DateTime
			}
		}
	}
	atcl.PushList = pushes
	return atcl, nil
}

func parseDateTime(ipdatetime string) (time.Time, error) {
	re, _ := regexp.Compile("(\\d+\\.\\d+\\.\\d+\\.\\d+)?\\s*(.*)")
	subMatches := re.FindStringSubmatch(ipdatetime)
	dateTime := strings.TrimSpace(subMatches[len(subMatches)-1])
	loc, err := time.LoadLocation("UTC")
	if err != nil {
		return time.Time{}, err
	}
	t, err := time.ParseInLocation("01/02 15:04", dateTime, loc)
	if err != nil {
		return time.Time{}, err
	}
	t = t.AddDate(getYear(t), 0, 0)
	return t, nil
}

func getYear(pushTime time.Time) int {
	t := time.Now()
	if t.Month() == 1 && pushTime.Month() == 12 {
		return t.Year() - 1
	}
	return t.Year()
}

// CheckBoardExist use for checking board exist or not
func CheckBoardExist(board string) bool {
	reqURL := makeBoardURL(board)
	_, err := fetchHTML(reqURL)
	if _, ok := err.(URLNotFoundError); ok {
		return false
	}
	return true
}

// CheckArticleExist user for checking article exist or not
func CheckArticleExist(board, articleCode string) bool {
	reqURL := makeArticleURL(board, articleCode)
	_, err := fetchHTML(reqURL)
	if _, ok := err.(URLNotFoundError); ok {
		return false
	}
	return true
}

func makeBoardURL(board string) string {
	return pttHostURL + "/bbs/" + board + "/index.html"
}

func makeArticleURL(board, articleCode string) string {
	return pttHostURL + "/bbs/" + board + "/" + articleCode + ".html"
}

type URLNotFoundError struct {
	URL string
}

func (u URLNotFoundError) Error() string {
	return "Fetched URL Not Found"
}

func fetchHTML(reqURL string) (response *http.Response, err error) {

	client := &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return errors.New("Redirect")
		},
	}

	response, err = client.Get(reqURL)

	if response.StatusCode == http.StatusNotFound {
		err = URLNotFoundError{reqURL}
	}

	if err != nil && response.StatusCode == http.StatusFound {
		req := passR18(reqURL)
		response, err = client.Do(req)
	}

	if err != nil {
		log.WithField("url", reqURL).WithError(err).Error("Fetch URL Failed")
	}

	return response, err
}

func passR18(reqURL string) (req *http.Request) {

	req, _ = http.NewRequest("GET", reqURL, nil)

	over18Cookie := http.Cookie{
		Name:       "over18",
		Value:      "1",
		Domain:     "www.ptt.cc",
		Path:       "/",
		RawExpires: "Session",
		MaxAge:     0,
		HttpOnly:   false,
	}

	req.AddCookie(&over18Cookie)

	return req
}

func parseHTML(response *http.Response) *html.Node {
	doc, err := html.Parse(response.Body)
	if err != nil {
		log.Error(err)
	}
	return doc
}
<commit_msg>add timezone when parseDateTime<commit_after>package crawler

import (
	"errors"
	"net/http"
	"time"

	log "github.com/meifamily/logrus"

	"github.com/meifamily/ptt-alertor/models/ptt/article"

	"regexp"

	"strings"

	"golang.org/x/net/html"
)

const pttHostURL = "https://www.ptt.cc"

// BuildArticles makes board's index articles to a article slice
func BuildArticles(board string) (article.Articles, error) {

	reqURL := makeBoardURL(board)
	rsp, err := fetchHTML(reqURL)
	if err != nil {
		return article.Articles{}, err
	}
	htmlNodes := parseHTML(rsp)

	articleBlocks := traverseHTMLNode(htmlNodes, findArticleBlocks)
	initialTargetNodes()
	articles := make(article.Articles, len(articleBlocks))
	for index, articleBlock := range articleBlocks {
		for _, titleDiv := range traverseHTMLNode(articleBlock, findTitleDiv) {
			initialTargetNodes()

			anchors := traverseHTMLNode(titleDiv, findAnchor)

			if len(anchors) == 0 {
				articles[index].Title = titleDiv.FirstChild.Data
				articles[index].Link = ""
				continue
			}

			for _, anchor := range traverseHTMLNode(titleDiv, findAnchor) {
				articles[index].Title = anchor.FirstChild.Data
				link := pttHostURL + getAnchorLink(anchor)
				articles[index].Link = link
				articles[index].ID = articles[index].ParseID(link)
			}
		}
		for _, metaDiv := range traverseHTMLNode(articleBlock, findMetaDiv) {
			initialTargetNodes()

			for _, date := range traverseHTMLNode(metaDiv, findDateDiv) {
				articles[index].Date = date.FirstChild.Data
			}
			for _, author := range traverseHTMLNode(metaDiv, findAuthorDiv) {
				articles[index].Author = author.FirstChild.Data
			}
		}
	}
	return articles, nil
}

// BuildArticle build article object from html
func BuildArticle(board, articleCode string) (article.Article, error) {

	reqURL := makeArticleURL(board, articleCode)
	rsp, err := fetchHTML(reqURL)
	if err != nil {
		return article.Article{}, err
	}
	htmlNodes := parseHTML(rsp)
	atcl := article.Article{
		Link:  reqURL,
		Code:  articleCode,
		Board: board,
	}
	nodes := traverseHTMLNode(htmlNodes, findOgTitleMeta)
	if len(nodes) > 0 {
		atcl.Title = getMetaContent(nodes[0])
	} else {
		atcl.Title = "[內文標題已被刪除]"
	}
	atcl.ID = atcl.ParseID(reqURL)
	pushBlocks := traverseHTMLNode(htmlNodes, findPushBlocks)
	initialTargetNodes()
	pushes := make([]article.Push, len(pushBlocks))
	for index, pushBlock := range pushBlocks {
		for _, pushTag := range traverseHTMLNode(pushBlock, findPushTag) {
			initialTargetNodes()
			pushes[index].Tag = pushTag.FirstChild.Data
		}
		for _, pushUserID := range traverseHTMLNode(pushBlock, findPushUserID) {
			initialTargetNodes()
			pushes[index].UserID = pushUserID.FirstChild.Data
		}
		for _, pushContent := range traverseHTMLNode(pushBlock, findPushContent) {
			initialTargetNodes()
			content := pushContent.FirstChild.Data
			for n := pushContent.FirstChild.NextSibling; n != nil; n = n.NextSibling {
				if findEmailProtected(n) != nil {
					break
				}
				if n.FirstChild != nil {
					content += n.FirstChild.Data
				}
				if n.NextSibling != nil {
					content += n.NextSibling.Data
				}
			}
			pushes[index].Content = content
		}
		for _, pushIPDateTime := range traverseHTMLNode(pushBlock, findPushIPDateTime) {
			initialTargetNodes()
			ipdatetime := strings.TrimSpace(pushIPDateTime.FirstChild.Data)
			if ipdatetime == "" {
				break
			}
			dateTime, err := parseDateTime(ipdatetime)
			if err != nil {
				log.WithFields(log.Fields{
					"ipdatetime": ipdatetime,
					"board":      board,
					"code":       articleCode,
				}).WithError(err).Error("Parse DateTime Error")
			}
			pushes[index].DateTime = dateTime
			if index == len(pushBlocks)-1 {
				atcl.LastPushDateTime = pushes[index].DateTime
			}
		}
	}
	atcl.PushList = pushes
	return atcl, nil
}

func parseDateTime(ipdatetime string) (time.Time, error) {
	re, _ := regexp.Compile("(\\d+\\.\\d+\\.\\d+\\.\\d+)?\\s*(.*)")
	subMatches := re.FindStringSubmatch(ipdatetime)
	dateTime := strings.TrimSpace(subMatches[len(subMatches)-1])
	loc := time.FixedZone("CST", 8*60*60)
	t, err := time.ParseInLocation("01/02 15:04", dateTime, loc)
	if err != nil {
		return time.Time{}, err
	}
	t = t.AddDate(getYear(t), 0, 0)
	return t, nil
}

func getYear(pushTime time.Time) int {
	t := time.Now()
	if t.Month() == 1 && pushTime.Month() == 12 {
		return t.Year() - 1
	}
	return t.Year()
}

// CheckBoardExist use for checking board exist or not
func CheckBoardExist(board string) bool {
	reqURL := makeBoardURL(board)
	_, err := fetchHTML(reqURL)
	if _, ok := err.(URLNotFoundError); ok {
		return false
	}
	return true
}

// CheckArticleExist user for checking article exist or not
func CheckArticleExist(board, articleCode string) bool {
	reqURL := makeArticleURL(board, articleCode)
	_, err := fetchHTML(reqURL)
	if _, ok := err.(URLNotFoundError); ok {
		return false
	}
	return true
}

func makeBoardURL(board string) string {
	return pttHostURL + "/bbs/" + board + "/index.html"
}

func makeArticleURL(board, articleCode string) string {
	return pttHostURL + "/bbs/" + board + "/" + articleCode + ".html"
}

type URLNotFoundError struct {
	URL string
}

func (u URLNotFoundError) Error() string {
	return "Fetched URL Not Found"
}

func fetchHTML(reqURL string) (response *http.Response, err error) {

	client := &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return errors.New("Redirect")
		},
	}

	response, err = client.Get(reqURL)

	if response.StatusCode == http.StatusNotFound {
		err = URLNotFoundError{reqURL}
	}

	if err != nil && response.StatusCode == http.StatusFound {
		req := passR18(reqURL)
		response, err = client.Do(req)
	}

	if err != nil {
		log.WithField("url", reqURL).WithError(err).Error("Fetch URL Failed")
	}

	return response, err
}

func passR18(reqURL string) (req *http.Request) {

	req, _ = http.NewRequest("GET", reqURL, nil)

	over18Cookie := http.Cookie{
		Name:       "over18",
		Value:      "1",
		Domain:     "www.ptt.cc",
		Path:       "/",
		RawExpires: "Session",
		MaxAge:     0,
		HttpOnly:   false,
	}

	req.AddCookie(&over18Cookie)

	return req
}

func parseHTML(response *http.Response) *html.Node {
	doc, err := html.Parse(response.Body)
	if err != nil {
		log.Error(err)
	}
	return doc
}
<|endoftext|>


<commit_before>// Copyright 2015 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package factom

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

type Balance struct {
	Balance int64
}

func ECBalance(key string) (*Balance, error) {
	resp, err := http.Get(
		fmt.Sprintf("http://%s/v1/entry-credit-balance/%s", server, key))
	if err != nil {
		return nil, err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	resp.Body.Close()

	b := new(Balance)
	if err := json.Unmarshal(body, b); err != nil {
		return nil, err
	}

	return b, nil
}

func FctBalance(key string) (*Balance, error) {

    str := fmt.Sprintf("http://%s/v1/factoid-balance/%s", serverFct, key)
    resp, err := http.Get(str)
    if err != nil {
        return nil, err
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    resp.Body.Close()

    b := new(Balance)
    if err := json.Unmarshal(body, b); err != nil {
        return nil, err
    }

    return b, nil
}

func GenerateFactoidAddress(name string) (string, error) {

    type address struct {
        Address string
    }

    str := fmt.Sprintf("http://%s/v1/factoid-generate-address/%s", serverFct, name)
    resp, err := http.Get(str)
    if err != nil {
        return "", err
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }
    resp.Body.Close()

    b := new(address)
    if err := json.Unmarshal(body, b); err != nil {
        return "", err
    }

    return b.Address, nil
}<commit_msg>Much progress... Added 3/4 transaction support<commit_after>// Copyright 2015 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package factom

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

type Balance struct {
	Balance int64
}

func ECBalance(key string) (*Balance, error) {
	resp, err := http.Get(
		fmt.Sprintf("http://%s/v1/entry-credit-balance/%s", server, key))
	if err != nil {
		return nil, err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	resp.Body.Close()

	b := new(Balance)
	if err := json.Unmarshal(body, b); err != nil {
		return nil, err
	}

	return b, nil
}

func FctBalance(key string) (*Balance, error) {

    str := fmt.Sprintf("http://%s/v1/factoid-balance/%s", serverFct, key)
    resp, err := http.Get(str)
    if err != nil {
        return nil, err
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    resp.Body.Close()

    b := new(Balance)
    if err := json.Unmarshal(body, b); err != nil {
        return nil, err
    }

    return b, nil
}

func GenerateFactoidAddress(name string) (string, error) {

    type address struct {
        Address string
    }

    str := fmt.Sprintf("http://%s/v1/factoid-generate-address/%s", serverFct, name)
    resp, err := http.Get(str)
    if err != nil {
        return "", err
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }
    resp.Body.Close()

    b := new(address)
    if err := json.Unmarshal(body, b); err != nil || len(b.Address)==0  {
        return "", fmt.Errorf("Duplicate Address ")
    }

    return b.Address, nil
}<|endoftext|>


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

package play

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"sync/atomic"
	"time"

	"golang.org/x/website/internal/web"
)

type playVersion struct {
	Name    string
	Backend string
}

var playVersions atomic.Value

func playHandler(site *web.Site) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/play/" && !strings.HasPrefix(r.URL.Path, "/play/p/") || r.URL.Path == "/play/p/" {
			http.Redirect(w, r, "/play/", http.StatusFound)
			return
		}
		if r.Host == "golang.google.cn" && strings.HasPrefix(r.URL.Path, "/play/p/") {
			site.ServeError(w, r, errors.New("Sorry, but shared playground snippets are not visible in China."))
			return
		}
		if strings.HasSuffix(r.URL.Path, ".go") {
			simpleProxy(w, r, "https://"+backend(r)+strings.TrimPrefix(r.URL.Path, "/play"))
			return
		}
		site.ServePage(w, r, web.Page{
			"URL":          r.URL.Path,
			"layout":       "play",
			"title":        "Go Playground",
			"playVersions": playVersions.Load().([]playVersion),
		})
	})
}

func init() {
	// Default set until we get the full list from play.golang.org.
	playVersions.Store([]playVersion{
		{Name: "Go release", Backend: ""},
		{Name: "Go dev branch", Backend: "gotip"},
	})

	go watchVersions()
}

func readVersions() (list []playVersion, err error) {
	defer func() {
		if e := recover(); e != nil {
			list = nil
			err = fmt.Errorf("readVersions: %v", e)
		}
	}()

	client := &http.Client{
		Timeout: 2 * time.Minute,
	}
	resp, err := client.Get("https://play.golang.org/versions")
	if err != nil {
		return nil, fmt.Errorf("readVersions: %v", err)
	}
	defer resp.Body.Close()

	js, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("readVersions: %v", err)
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("readVersions: %s\n%s", resp.Status, js)
	}

	if err := json.Unmarshal(js, &list); err != nil || len(list) < 2 {
		return nil, fmt.Errorf("readVersions: bad response: %#q", js)
	}
	return list, nil
}

func watchVersions() {
	for ; ; time.Sleep(1 * time.Minute) {
		list, err := readVersions()
		if err != nil {
			log.Print(err)
			continue
		}
		playVersions.Store(list)
	}
}
<commit_msg>internal/play: add goprevplay, load versions from back ends<commit_after>// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package play

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"sync/atomic"
	"time"

	"golang.org/x/website/internal/web"
)

type playVersion struct {
	Name    string
	Backend string
}

var playVersions atomic.Value

func playHandler(site *web.Site) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/play/" && !strings.HasPrefix(r.URL.Path, "/play/p/") || r.URL.Path == "/play/p/" {
			http.Redirect(w, r, "/play/", http.StatusFound)
			return
		}
		if r.Host == "golang.google.cn" && strings.HasPrefix(r.URL.Path, "/play/p/") {
			site.ServeError(w, r, errors.New("Sorry, but shared playground snippets are not visible in China."))
			return
		}
		if strings.HasSuffix(r.URL.Path, ".go") {
			simpleProxy(w, r, "https://"+backend(r)+strings.TrimPrefix(r.URL.Path, "/play"))
			return
		}
		site.ServePage(w, r, web.Page{
			"URL":          r.URL.Path,
			"layout":       "play",
			"title":        "Go Playground",
			"playVersions": playVersions.Load().([]playVersion),
		})
	})
}

func init() {
	// Default set until we get the full list from play.golang.org.
	playVersions.Store([]playVersion{
		{Name: "Go release", Backend: ""},
		{Name: "Go previous release", Backend: "goprev"},
		{Name: "Go dev branch", Backend: "gotip"},
	})

	go watchVersions()
}

func readVersions() (list []playVersion, err error) {
	defer func() {
		if e := recover(); e != nil {
			list = nil
			err = fmt.Errorf("readVersions: %v", e)
		}
	}()

	client := &http.Client{
		Timeout: 2 * time.Minute,
	}

	list = append([]playVersion(nil), playVersions.Load().([]playVersion)...)
	for i, v := range list {
		resp, err := client.Get("https://" + v.Backend + "play.golang.org/version")
		if err != nil {
			log.Printf("readVersions: %v", err)
			continue
		}
		js, err := io.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			log.Printf("readVersions: %v", err)
			continue
		}
		if resp.StatusCode != 200 {
			log.Printf("readVersions: %s\n%s", resp.Status, js)
			continue
		}
		var data struct{ Name string }
		if err := json.Unmarshal(js, &data); err != nil {
			log.Printf("readVersions: %v", err)
			continue
		}
		if data.Name != "" {
			list[i].Name = data.Name
		}
	}
	return list, nil
}

func watchVersions() {
	for ; ; time.Sleep(1 * time.Minute) {
		list, err := readVersions()
		if err != nil {
			log.Print(err)
			continue
		}
		playVersions.Store(list)
	}
}
<|endoftext|>