<commit_before>/*
 * errors_test.go
 *
 * Copyright 2013 Krzysztof Wilczynski
 *
 * 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 magic

import (
	"reflect"
	"testing"
)

func TestMagicError(t *testing.T) {
	mgc, err := New()
	if err != nil {
		t.Fatalf("unable to create new Magic type: %s", err.Error())
	}
	defer mgc.Close()

	err = mgc.error()
	func(v interface{}) {
		if _, ok := v.(*MagicError); !ok {
			t.Fatalf("not a MagicError type: %s", reflect.TypeOf(v).String())
		}
	}(err)
}

func TestMagicError_Error(t *testing.T) {
	mgc, err := New()
	if err != nil {
		t.Fatalf("unable to create new Magic type: %s", err.Error())
	}
	defer mgc.Close()

	err = mgc.error()

	v := "magic: unknown error"
	if ok := CompareStrings(err.Error(), v); !ok {
		t.Errorf("value given \"%s\", want \"%s\"", err.Error(), v)
	}
}

func TestMagicError_Errno(t *testing.T) {
	mgc, err := New()
	if err != nil {
		t.Fatalf("unable to create new Magic type: %s", err.Error())
	}
	defer mgc.Close()

	e := mgc.error()
	if e.Errno != -1 {
		t.Errorf("value given %d, want %d", e.Errno, -1)
	}
}

func TestMagicError_Message(t *testing.T) {
	mgc, err := New()
	if err != nil {
		t.Fatalf("unable to create new Magic type: %s", err.Error())
	}
	defer mgc.Close()

	e := mgc.error()

	v := "unknown error"
	if ok := CompareStrings(e.Message, v); !ok {
		t.Errorf("value given \"%s\", want \"%s\"", e.Message, v)
	}
}
<commit_msg>Fix. So many versions to take care about.<commit_after>/*
 * errors_test.go
 *
 * Copyright 2013 Krzysztof Wilczynski
 *
 * 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 magic

import (
	"reflect"
	"testing"
)

func TestMagicError(t *testing.T) {
	mgc, err := New()
	if err != nil {
		t.Fatalf("unable to create new Magic type: %s", err.Error())
	}
	defer mgc.Close()

	err = mgc.error()
	func(v interface{}) {
		if _, ok := v.(*MagicError); !ok {
			t.Fatalf("not a MagicError type: %s", reflect.TypeOf(v).String())
		}
	}(err)
}

func TestMagicError_Error(t *testing.T) {
	var v string

	mgc, err := New()
	if err != nil {
		t.Fatalf("unable to create new Magic type: %s", err.Error())
	}
	defer mgc.Close()

	v = "magic: no magic files loaded"
	if rv, _ := Version(); rv < 0 {
		// Older version of libmagic behaves differently.
		v = "magic: unknown error"
	}

	err = mgc.error()
	if ok := CompareStrings(err.Error(), v); !ok {
		t.Errorf("value given \"%s\", want \"%s\"", err.Error(), v)
	}
}

func TestMagicError_Errno(t *testing.T) {
	var v int

	mgc, err := New()
	if err != nil {
		t.Fatalf("unable to create new Magic type: %s", err.Error())
	}
	defer mgc.Close()

	v = 0
	if rv, _ := Version(); rv < 0 {
		// Older version of libmagic behaves differently.
		v = -1
	}

	e := mgc.error()
	if e.Errno != v {
		t.Errorf("value given %d, want %d", e.Errno, v)
	}
}

func TestMagicError_Message(t *testing.T) {
	var v string

	mgc, err := New()
	if err != nil {
		t.Fatalf("unable to create new Magic type: %s", err.Error())
	}
	defer mgc.Close()

	v = "no magic files loaded"
	if rv, _ := Version(); rv < 0 {
		// Older version of libmagic behaves differently.
		v = "unknown error"
	}

	e := mgc.error()
	if ok := CompareStrings(e.Message, v); !ok {
		t.Errorf("value given \"%s\", want \"%s\"", e.Message, v)
	}
}
<|endoftext|>


<commit_before>package esi

import (
	"context"
	"errors"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/evepraisal/go-evepraisal"
	"github.com/sethgrid/pester"
)

// MarketOrder represents a market order in ESI
type MarketOrder struct {
	ID            int64   `json:"order_id"`
	Type          int64   `json:"type_id"`
	StationID     int64   `json:"location_id"`
	SystemID      int64   `json:"system_id"`
	Volume        int64   `json:"volume_remain"`
	MinVolume     int64   `json:"min_volume"`
	Price         float64 `json:"price"`
	Buy           bool    `json:"is_buy_order"`
	Duration      int64   `json:"duration"`
	Issued        string  `json:"issued"`
	VolumeEntered int64   `json:"volumeEntered"`
	Range         string  `json:"range"`
}

// SpecialRegions defines which regions we care about
var SpecialRegions = []struct {
	name     string
	stations []int64
	systems  []int64
}{
	{
		// 10000002
		name:    "jita",
		systems: []int64{30000142},
	},
	{
		name:    "perimeter",
		systems: []int64{30000144},
	},
	{
		// 10000043
		name:     "amarr",
		stations: []int64{60008950, 60002569, 60008494},
	}, {
		// 10000032
		name:     "dodixie",
		stations: []int64{60011866, 60001867},
	}, {
		// 10000042
		name:     "hek",
		stations: []int64{60005236, 60004516, 60015140, 60005686, 60011287, 60005236},
	}, {
		// 10000030
		name:    "rens",
		systems: []int64{30002510, 30002526},
	},
}

// PriceFetcher fetches prices and populates the given priceDB
type PriceFetcher struct {
	db      evepraisal.PriceDB
	client  *pester.Client
	baseURL string

	ctx  context.Context
	stop chan bool
	wg   *sync.WaitGroup
}

// NewPriceFetcher returns a new PriceFetcher
func NewPriceFetcher(ctx context.Context, priceDB evepraisal.PriceDB, baseURL string, client *pester.Client) (*PriceFetcher, error) {

	p := &PriceFetcher{
		db:      priceDB,
		client:  client,
		baseURL: baseURL,

		ctx:  ctx,
		stop: make(chan bool),
		wg:   &sync.WaitGroup{},
	}

	p.wg.Add(1)
	go func() {
		defer p.wg.Done()
		for {
			start := time.Now()
			p.runOnce()
			select {
			case <-time.After((6 * time.Minute) - time.Since(start)):
			case <-p.stop:
				return
			}
		}
	}()

	return p, nil
}

// Close should be called to stop the fetcher worker(s)
func (p *PriceFetcher) Close() error {
	close(p.stop)
	p.wg.Wait()
	return nil
}

func regionNames() []string {
	regions := make([]string, len(SpecialRegions)+1)
	regions[0] = "universe"
	for i, region := range SpecialRegions {
		regions[i+1] = region.name
	}
	return regions
}

func (p *PriceFetcher) runOnce() {
	log.Println("Fetch market data")
	priceMap, err := p.FetchOrderData(p.client, p.baseURL, []int{10000002, 10000042, 10000027, 10000032, 10000043, 10000030})
	if err != nil {
		log.Println("ERROR: fetching market data: ", err)
		return
	}

	pricesFromCCP, err := p.FetchPriceData(p.client, p.baseURL)
	if err != nil {
		log.Println("ERROR: fetching CCP price data: ", err)
		return
	}

	for _, regionName := range regionNames() {
		// Use CCP's price if our regional price is too low
		for typeID, prices := range pricesFromCCP {
			p, ok := priceMap[regionName][typeID]
			if !ok || p.Sell.Volume < 10 {
				priceMap[regionName][typeID] = prices
			}
		}

		// Use the universe price if our regional price is too low (override CCP's price)
		for typeID, p := range priceMap[regionName] {
			if p.Sell.Volume < 2 {
				universePrice, ok := priceMap["universe"][typeID]
				if ok && universePrice.Sell.Volume >= 2 {
					universePrice.Strategy = "orders_universe"
					priceMap[regionName][typeID] = universePrice
				}
			}

			if regionName != "universe" && p.Buy.Volume > 0 && p.Sell.Volume > 0 && p.Buy.Max > p.Sell.Min {
				delta := p.Buy.Max - p.Sell.Min
				if delta > 1000000 {
					log.Printf("MARKET: Prices are wack for %d in %s", typeID, regionName)
				}
			}
		}
	}

	for market, pmap := range priceMap {
		// this takes awhile, so let's check to see if we should stop between markets
		select {
		case <-p.stop:
			return
		default:
		}

		items := make([]evepraisal.MarketItemPrices, len(pmap))
		i := 0
		for typeID, prices := range pmap {
			items[i] = evepraisal.MarketItemPrices{
				Market: market,
				TypeID: typeID,
				Prices: prices,
			}
			i++
		}

		err = p.db.UpdatePrices(items)
		if err != nil {
			log.Printf("Error when updating prices: %s", err)
		}
	}
	log.Println("Done fetching market data")
}

func (p *PriceFetcher) freshPriceMap() map[string]map[int64]evepraisal.Prices {
	priceMap := make(map[string]map[int64]evepraisal.Prices)
	for _, region := range SpecialRegions {
		priceMap[region.name] = make(map[int64]evepraisal.Prices)
	}
	priceMap["universe"] = make(map[int64]evepraisal.Prices)
	return priceMap
}

// FetchPriceData fetches CCP's pricing information for every type
func (p *PriceFetcher) FetchPriceData(client *pester.Client, baseURL string) (map[int64]evepraisal.Prices, error) {
	start := time.Now()
	url := fmt.Sprintf("%s/markets/prices/?datasource=tranquility", baseURL)
	esiPrices := make([]struct {
		TypeID        int64   `json:"type_id"`
		AveragePrice  float64 `json:"average_price"`
		AdjustedPrice float64 `json:"adjusted_price"`
	}, 0)
	err := fetchURL(p.ctx, client, url, &esiPrices)
	if err != nil {
		return nil, err
	}

	allPrices := make(map[int64]evepraisal.Prices, len(esiPrices))
	for _, p := range esiPrices {
		priceToUse := p.AveragePrice
		if priceToUse == 0 {
			priceToUse = p.AdjustedPrice
		}
		stats := evepraisal.PriceStats{
			Average:    p.AveragePrice,
			Max:        priceToUse,
			Median:     priceToUse,
			Min:        priceToUse,
			Percentile: p.AdjustedPrice,
		}
		allPrices[p.TypeID] = evepraisal.Prices{
			All:      stats,
			Buy:      stats,
			Sell:     stats,
			Updated:  start,
			Strategy: "ccp",
		}
	}
	return allPrices, nil
}

// FetchOrderData concurrently fetches from each region that we care about
func (p *PriceFetcher) FetchOrderData(client *pester.Client, baseURL string, regionIDs []int) (map[string]map[int64]evepraisal.Prices, error) {
	allOrdersByType := make(map[int64][]MarketOrder)
	finished := make(chan bool, 1)
	workerStop := make(chan bool, 1)
	errChannel := make(chan error, 1)
	fetchStart := time.Now()

	l := &sync.Mutex{}
	requestAndProcess := func(url string) (bool, error) {
		var orders []MarketOrder
		err := fetchURL(p.ctx, client, url, &orders)
		if err != nil {
			return false, err
		}

		l.Lock()
		for _, order := range orders {
			allOrdersByType[order.Type] = append(allOrdersByType[order.Type], order)
		}
		l.Unlock()
		if len(orders) == 0 {
			return false, nil
		}
		return true, nil
	}

	wg := &sync.WaitGroup{}
	for _, regionID := range regionIDs {
		wg.Add(1)
		go func(regionID int) {
			defer wg.Done()
			page := 1
			for {
				select {
				case <-workerStop:
					return
				default:
				}

				url := fmt.Sprintf("%s/markets/%d/orders/?datasource=tranquility&order_type=all&page=%d", baseURL, regionID, page)
				hasMore, err := requestAndProcess(url)
				if err != nil {
					errChannel <- fmt.Errorf("Failed to fetch market orders: %s (%s)", err, url)
					return
				}

				if !hasMore {
					break
				}
				page++
			}
		}(regionID)
	}

	go func() {
		wg.Wait()
		close(finished)
	}()

	select {
	case <-finished:
	case <-p.stop:
		close(workerStop)
		return nil, errors.New("Stopping during price fetch")
	case err := <-errChannel:
		if err != nil {
			close(workerStop)
			return nil, err
		}
	}

	log.Println("Performing aggregates on order data")
	// Calculate aggregates that we care about:
	newPriceMap := p.freshPriceMap()
	for k, orders := range allOrdersByType {
		for _, region := range SpecialRegions {
			filteredOrders := make([]MarketOrder, 0)
			ordercount := 0
			for _, order := range orders {
				matched := false
				for _, station := range region.stations {
					if station == order.StationID {
						matched = true
						ordercount++
						break
					}
				}
				for _, system := range region.systems {
					if system == order.SystemID {
						matched = true
						ordercount++
						break
					}
				}
				if matched {
					filteredOrders = append(filteredOrders, order)
				}
			}
			agg := getPriceAggregatesForOrders(filteredOrders)
			agg.Updated = fetchStart
			agg.Strategy = "orders"
			newPriceMap[region.name][k] = agg
		}
		agg := getPriceAggregatesForOrders(orders)
		agg.Updated = fetchStart
		newPriceMap["universe"][k] = agg
	}

	log.Println("Finished performing aggregates on order data")

	return newPriceMap, nil
}
<commit_msg>Add Ashab to Amarr market and Botane to Dodixie<commit_after>package esi

import (
	"context"
	"errors"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/evepraisal/go-evepraisal"
	"github.com/sethgrid/pester"
)

// MarketOrder represents a market order in ESI
type MarketOrder struct {
	ID            int64   `json:"order_id"`
	Type          int64   `json:"type_id"`
	StationID     int64   `json:"location_id"`
	SystemID      int64   `json:"system_id"`
	Volume        int64   `json:"volume_remain"`
	MinVolume     int64   `json:"min_volume"`
	Price         float64 `json:"price"`
	Buy           bool    `json:"is_buy_order"`
	Duration      int64   `json:"duration"`
	Issued        string  `json:"issued"`
	VolumeEntered int64   `json:"volumeEntered"`
	Range         string  `json:"range"`
}

// SpecialRegions defines which regions we care about
var SpecialRegions = []struct {
	name     string
	stations []int64
	systems  []int64
}{
	{
		// 10000002
		name:    "jita",
		systems: []int64{30000142},
	}, {
		name:    "perimeter",
		systems: []int64{30000144},
	}, {
		// 10000043
		name:     "amarr",
		stations: []int64{60008950, 60002569, 60008494},
		systems:  []int64{30003491},
	}, {
		// 10000032
		name:     "dodixie",
		stations: []int64{60011866, 60001867},
		systems:  []int64{30002661},
	}, {
		// 10000042
		name:     "hek",
		stations: []int64{60005236, 60004516, 60015140, 60005686, 60011287, 60005236},
	}, {
		// 10000030
		name:    "rens",
		systems: []int64{30002510, 30002526},
	},
}

// PriceFetcher fetches prices and populates the given priceDB
type PriceFetcher struct {
	db      evepraisal.PriceDB
	client  *pester.Client
	baseURL string

	ctx  context.Context
	stop chan bool
	wg   *sync.WaitGroup
}

// NewPriceFetcher returns a new PriceFetcher
func NewPriceFetcher(ctx context.Context, priceDB evepraisal.PriceDB, baseURL string, client *pester.Client) (*PriceFetcher, error) {

	p := &PriceFetcher{
		db:      priceDB,
		client:  client,
		baseURL: baseURL,

		ctx:  ctx,
		stop: make(chan bool),
		wg:   &sync.WaitGroup{},
	}

	p.wg.Add(1)
	go func() {
		defer p.wg.Done()
		for {
			start := time.Now()
			p.runOnce()
			select {
			case <-time.After((6 * time.Minute) - time.Since(start)):
			case <-p.stop:
				return
			}
		}
	}()

	return p, nil
}

// Close should be called to stop the fetcher worker(s)
func (p *PriceFetcher) Close() error {
	close(p.stop)
	p.wg.Wait()
	return nil
}

func regionNames() []string {
	regions := make([]string, len(SpecialRegions)+1)
	regions[0] = "universe"
	for i, region := range SpecialRegions {
		regions[i+1] = region.name
	}
	return regions
}

func (p *PriceFetcher) runOnce() {
	log.Println("Fetch market data")
	priceMap, err := p.FetchOrderData(p.client, p.baseURL, []int{10000002, 10000042, 10000027, 10000032, 10000043, 10000030})
	if err != nil {
		log.Println("ERROR: fetching market data: ", err)
		return
	}

	pricesFromCCP, err := p.FetchPriceData(p.client, p.baseURL)
	if err != nil {
		log.Println("ERROR: fetching CCP price data: ", err)
		return
	}

	for _, regionName := range regionNames() {
		// Use CCP's price if our regional price is too low
		for typeID, prices := range pricesFromCCP {
			p, ok := priceMap[regionName][typeID]
			if !ok || p.Sell.Volume < 10 {
				priceMap[regionName][typeID] = prices
			}
		}

		// Use the universe price if our regional price is too low (override CCP's price)
		for typeID, p := range priceMap[regionName] {
			if p.Sell.Volume < 2 {
				universePrice, ok := priceMap["universe"][typeID]
				if ok && universePrice.Sell.Volume >= 2 {
					universePrice.Strategy = "orders_universe"
					priceMap[regionName][typeID] = universePrice
				}
			}

			if regionName != "universe" && p.Buy.Volume > 0 && p.Sell.Volume > 0 && p.Buy.Max > p.Sell.Min {
				delta := p.Buy.Max - p.Sell.Min
				if delta > 1000000 {
					log.Printf("MARKET: Prices are wack for %d in %s", typeID, regionName)
				}
			}
		}
	}

	for market, pmap := range priceMap {
		// this takes awhile, so let's check to see if we should stop between markets
		select {
		case <-p.stop:
			return
		default:
		}

		items := make([]evepraisal.MarketItemPrices, len(pmap))
		i := 0
		for typeID, prices := range pmap {
			items[i] = evepraisal.MarketItemPrices{
				Market: market,
				TypeID: typeID,
				Prices: prices,
			}
			i++
		}

		err = p.db.UpdatePrices(items)
		if err != nil {
			log.Printf("Error when updating prices: %s", err)
		}
	}
	log.Println("Done fetching market data")
}

func (p *PriceFetcher) freshPriceMap() map[string]map[int64]evepraisal.Prices {
	priceMap := make(map[string]map[int64]evepraisal.Prices)
	for _, region := range SpecialRegions {
		priceMap[region.name] = make(map[int64]evepraisal.Prices)
	}
	priceMap["universe"] = make(map[int64]evepraisal.Prices)
	return priceMap
}

// FetchPriceData fetches CCP's pricing information for every type
func (p *PriceFetcher) FetchPriceData(client *pester.Client, baseURL string) (map[int64]evepraisal.Prices, error) {
	start := time.Now()
	url := fmt.Sprintf("%s/markets/prices/?datasource=tranquility", baseURL)
	esiPrices := make([]struct {
		TypeID        int64   `json:"type_id"`
		AveragePrice  float64 `json:"average_price"`
		AdjustedPrice float64 `json:"adjusted_price"`
	}, 0)
	err := fetchURL(p.ctx, client, url, &esiPrices)
	if err != nil {
		return nil, err
	}

	allPrices := make(map[int64]evepraisal.Prices, len(esiPrices))
	for _, p := range esiPrices {
		priceToUse := p.AveragePrice
		if priceToUse == 0 {
			priceToUse = p.AdjustedPrice
		}
		stats := evepraisal.PriceStats{
			Average:    p.AveragePrice,
			Max:        priceToUse,
			Median:     priceToUse,
			Min:        priceToUse,
			Percentile: p.AdjustedPrice,
		}
		allPrices[p.TypeID] = evepraisal.Prices{
			All:      stats,
			Buy:      stats,
			Sell:     stats,
			Updated:  start,
			Strategy: "ccp",
		}
	}
	return allPrices, nil
}

// FetchOrderData concurrently fetches from each region that we care about
func (p *PriceFetcher) FetchOrderData(client *pester.Client, baseURL string, regionIDs []int) (map[string]map[int64]evepraisal.Prices, error) {
	allOrdersByType := make(map[int64][]MarketOrder)
	finished := make(chan bool, 1)
	workerStop := make(chan bool, 1)
	errChannel := make(chan error, 1)
	fetchStart := time.Now()

	l := &sync.Mutex{}
	requestAndProcess := func(url string) (bool, error) {
		var orders []MarketOrder
		err := fetchURL(p.ctx, client, url, &orders)
		if err != nil {
			return false, err
		}

		l.Lock()
		for _, order := range orders {
			allOrdersByType[order.Type] = append(allOrdersByType[order.Type], order)
		}
		l.Unlock()
		if len(orders) == 0 {
			return false, nil
		}
		return true, nil
	}

	wg := &sync.WaitGroup{}
	for _, regionID := range regionIDs {
		wg.Add(1)
		go func(regionID int) {
			defer wg.Done()
			page := 1
			for {
				select {
				case <-workerStop:
					return
				default:
				}

				url := fmt.Sprintf("%s/markets/%d/orders/?datasource=tranquility&order_type=all&page=%d", baseURL, regionID, page)
				hasMore, err := requestAndProcess(url)
				if err != nil {
					errChannel <- fmt.Errorf("Failed to fetch market orders: %s (%s)", err, url)
					return
				}

				if !hasMore {
					break
				}
				page++
			}
		}(regionID)
	}

	go func() {
		wg.Wait()
		close(finished)
	}()

	select {
	case <-finished:
	case <-p.stop:
		close(workerStop)
		return nil, errors.New("Stopping during price fetch")
	case err := <-errChannel:
		if err != nil {
			close(workerStop)
			return nil, err
		}
	}

	log.Println("Performing aggregates on order data")
	// Calculate aggregates that we care about:
	newPriceMap := p.freshPriceMap()
	for k, orders := range allOrdersByType {
		for _, region := range SpecialRegions {
			filteredOrders := make([]MarketOrder, 0)
			ordercount := 0
			for _, order := range orders {
				matched := false
				for _, station := range region.stations {
					if station == order.StationID {
						matched = true
						ordercount++
						break
					}
				}
				for _, system := range region.systems {
					if system == order.SystemID {
						matched = true
						ordercount++
						break
					}
				}
				if matched {
					filteredOrders = append(filteredOrders, order)
				}
			}
			agg := getPriceAggregatesForOrders(filteredOrders)
			agg.Updated = fetchStart
			agg.Strategy = "orders"
			newPriceMap[region.name][k] = agg
		}
		agg := getPriceAggregatesForOrders(orders)
		agg.Updated = fetchStart
		newPriceMap["universe"][k] = agg
	}

	log.Println("Finished performing aggregates on order data")

	return newPriceMap, nil
}
<|endoftext|>


<commit_before>// Copyright 2017 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 convert

import (
	"io"
	"math"

	"github.com/hajimehoshi/ebiten/audio"
)

func sinc(x float64) float64 {
	if x == 0 {
		return 1
	}
	return math.Sin(x) / x
}

type Resampling struct {
	source       audio.ReadSeekCloser
	size         int64
	from         int
	to           int
	pos          int64
	srcBlock     int64
	srcBufL      map[int64][]float64
	srcBufR      map[int64][]float64
	lruSrcBlocks []int64
}

const resamplingBufferSize = 4096

func NewResampling(source audio.ReadSeekCloser, size int64, from, to int) *Resampling {
	r := &Resampling{
		source:   source,
		size:     size,
		from:     from,
		to:       to,
		srcBlock: -1,
		srcBufL:  map[int64][]float64{},
		srcBufR:  map[int64][]float64{},
	}
	return r
}

func (r *Resampling) Size() int64 {
	s := int64(float64(r.size) * float64(r.to) / float64(r.from))
	return s / 4 * 4
}

func (r *Resampling) src(i int) (float64, float64, error) {
	// Use int here since int64 is very slow on browsers.
	// TODO: Resampling is too heavy on browsers. How about using OfflineAudioContext?
	if i < 0 {
		return 0, 0, nil
	}
	if r.size/4 <= int64(i) {
		return 0, 0, nil
	}
	nextPos := int64(i) / resamplingBufferSize
	if _, ok := r.srcBufL[nextPos]; !ok {
		if r.srcBlock+1 != nextPos {
			if _, err := r.source.Seek(nextPos*resamplingBufferSize*4, io.SeekStart); err != nil {
				return 0, 0, err
			}
		}
		buf := make([]uint8, resamplingBufferSize*4)
		c := 0
		for c < len(buf) {
			n, err := r.source.Read(buf[c:])
			c += n
			if err != nil {
				if err == io.EOF {
					break
				}
				return 0, 0, err
			}
		}
		buf = buf[:c]
		sl := make([]float64, resamplingBufferSize)
		sr := make([]float64, resamplingBufferSize)
		for i := 0; i < len(buf)/4; i++ {
			sl[i] = float64(int16(buf[4*i])|(int16(buf[4*i+1])<<8)) / (1<<15 - 1)
			sr[i] = float64(int16(buf[4*i+2])|(int16(buf[4*i+3])<<8)) / (1<<15 - 1)
		}
		r.srcBlock = nextPos
		r.srcBufL[r.srcBlock] = sl
		r.srcBufR[r.srcBlock] = sr
		// To keep srcBufL/R not too big, let's remove the least used buffers.
		if len(r.lruSrcBlocks) >= 4 {
			p := r.lruSrcBlocks[0]
			delete(r.srcBufL, p)
			delete(r.srcBufR, p)
			r.lruSrcBlocks = r.lruSrcBlocks[1:]
		}
		r.lruSrcBlocks = append(r.lruSrcBlocks, r.srcBlock)
	} else {
		r.srcBlock = nextPos
		idx := -1
		for i, p := range r.lruSrcBlocks {
			if p == r.srcBlock {
				idx = i
				break
			}
		}
		if idx == -1 {
			panic("not reach")
		}
		r.lruSrcBlocks = append(r.lruSrcBlocks[:idx], r.lruSrcBlocks[idx+1:]...)
		r.lruSrcBlocks = append(r.lruSrcBlocks, r.srcBlock)
	}
	ii := i % resamplingBufferSize
	return r.srcBufL[r.srcBlock][ii], r.srcBufR[r.srcBlock][ii], nil
}

func (r *Resampling) at(t int64) (float64, float64, error) {
	windowSize := 4.0
	tInSrc := float64(t) * float64(r.from) / float64(r.to)
	startN := tInSrc - windowSize
	if startN < 0 {
		startN = 0
	}
	if float64(r.size/4) <= startN {
		startN = float64(r.size/4) - 1
	}
	endN := tInSrc + windowSize + 1
	if float64(r.size/4) <= endN {
		endN = float64(r.size/4) - 1
	}
	lv := 0.0
	rv := 0.0
	for n := startN; n < endN; n++ {
		srcL, srcR, err := r.src(int(n))
		if err != nil {
			return 0, 0, err
		}
		w := 0.5 + 0.5*math.Cos(2*math.Pi*(tInSrc-n)/(windowSize*2+1))
		s := sinc(math.Pi*(tInSrc-n)) * w
		lv += srcL * s
		rv += srcR * s
	}
	if lv < -1 {
		lv = -1
	}
	if lv > 1 {
		lv = 1
	}
	if rv < -1 {
		rv = -1
	}
	if rv > 1 {
		rv = 1
	}
	return lv, rv, nil
}

func (r *Resampling) Read(b []uint8) (int, error) {
	if r.pos == r.Size() {
		return 0, io.EOF
	}
	n := len(b) / 4 * 4
	if r.Size()-r.pos <= int64(n) {
		n = int(r.Size() - r.pos)
	}
	for i := 0; i < n/4; i++ {
		l, r, err := r.at(r.pos/4 + int64(i))
		if err != nil {
			return 0, err
		}
		l16 := int16(l * (1<<15 - 1))
		r16 := int16(r * (1<<15 - 1))
		b[4*i] = uint8(l16)
		b[4*i+1] = uint8(l16 >> 8)
		b[4*i+2] = uint8(r16)
		b[4*i+3] = uint8(r16 >> 8)
	}
	r.pos += int64(n)
	return n, nil
}

func (r *Resampling) Seek(offset int64, whence int) (int64, error) {
	switch whence {
	case io.SeekStart:
		r.pos = offset
	case io.SeekCurrent:
		r.pos += offset
	case io.SeekEnd:
		r.pos += r.Size() + offset
	}
	if r.pos < 0 {
		r.pos = 0
	}
	if r.Size() <= r.pos {
		r.pos = r.Size()
	}
	return r.pos, nil
}

func (r *Resampling) Close() error {
	return r.source.Close()
}
<commit_msg>audio/internal/convert: Fix algorithm<commit_after>// Copyright 2017 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 convert

import (
	"io"
	"math"

	"github.com/hajimehoshi/ebiten/audio"
)

func sinc(x float64) float64 {
	if math.Abs(x) < 1e-8 {
		return 1
	}
	return math.Sin(x) / x
}

type Resampling struct {
	source       audio.ReadSeekCloser
	size         int64
	from         int
	to           int
	pos          int64
	srcBlock     int64
	srcBufL      map[int64][]float64
	srcBufR      map[int64][]float64
	lruSrcBlocks []int64
}

const resamplingBufferSize = 4096

func NewResampling(source audio.ReadSeekCloser, size int64, from, to int) *Resampling {
	r := &Resampling{
		source:   source,
		size:     size,
		from:     from,
		to:       to,
		srcBlock: -1,
		srcBufL:  map[int64][]float64{},
		srcBufR:  map[int64][]float64{},
	}
	return r
}

func (r *Resampling) Size() int64 {
	s := int64(float64(r.size) * float64(r.to) / float64(r.from))
	return s / 4 * 4
}

func (r *Resampling) src(i int) (float64, float64, error) {
	// Use int here since int64 is very slow on browsers.
	// TODO: Resampling is too heavy on browsers. How about using OfflineAudioContext?
	if i < 0 {
		return 0, 0, nil
	}
	if r.size/4 <= int64(i) {
		return 0, 0, nil
	}
	nextPos := int64(i) / resamplingBufferSize
	if _, ok := r.srcBufL[nextPos]; !ok {
		if r.srcBlock+1 != nextPos {
			if _, err := r.source.Seek(nextPos*resamplingBufferSize*4, io.SeekStart); err != nil {
				return 0, 0, err
			}
		}
		buf := make([]uint8, resamplingBufferSize*4)
		c := 0
		for c < len(buf) {
			n, err := r.source.Read(buf[c:])
			c += n
			if err != nil {
				if err == io.EOF {
					break
				}
				return 0, 0, err
			}
		}
		buf = buf[:c]
		sl := make([]float64, resamplingBufferSize)
		sr := make([]float64, resamplingBufferSize)
		for i := 0; i < len(buf)/4; i++ {
			sl[i] = float64(int16(buf[4*i])|(int16(buf[4*i+1])<<8)) / (1<<15 - 1)
			sr[i] = float64(int16(buf[4*i+2])|(int16(buf[4*i+3])<<8)) / (1<<15 - 1)
		}
		r.srcBlock = nextPos
		r.srcBufL[r.srcBlock] = sl
		r.srcBufR[r.srcBlock] = sr
		// To keep srcBufL/R not too big, let's remove the least used buffers.
		if len(r.lruSrcBlocks) >= 4 {
			p := r.lruSrcBlocks[0]
			delete(r.srcBufL, p)
			delete(r.srcBufR, p)
			r.lruSrcBlocks = r.lruSrcBlocks[1:]
		}
		r.lruSrcBlocks = append(r.lruSrcBlocks, r.srcBlock)
	} else {
		r.srcBlock = nextPos
		idx := -1
		for i, p := range r.lruSrcBlocks {
			if p == r.srcBlock {
				idx = i
				break
			}
		}
		if idx == -1 {
			panic("not reach")
		}
		r.lruSrcBlocks = append(r.lruSrcBlocks[:idx], r.lruSrcBlocks[idx+1:]...)
		r.lruSrcBlocks = append(r.lruSrcBlocks, r.srcBlock)
	}
	ii := i % resamplingBufferSize
	return r.srcBufL[r.srcBlock][ii], r.srcBufR[r.srcBlock][ii], nil
}

func (r *Resampling) at(t int64) (float64, float64, error) {
	windowSize := 4.0
	tInSrc := float64(t) * float64(r.from) / float64(r.to)
	startN := int64(tInSrc - windowSize)
	if startN < 0 {
		startN = 0
	}
	if r.size/4 <= startN {
		startN = r.size/4 - 1
	}
	endN := int64(tInSrc + windowSize)
	if r.size/4 <= endN {
		endN = r.size/4 - 1
	}
	lv := 0.0
	rv := 0.0
	for n := startN; n <= endN; n++ {
		srcL, srcR, err := r.src(int(n))
		if err != nil {
			return 0, 0, err
		}
		d := tInSrc - float64(n)
		w := 0.5 + 0.5*math.Cos(2*math.Pi*d/(windowSize*2+1))
		s := sinc(math.Pi*d) * w
		lv += srcL * s
		rv += srcR * s
	}
	if lv < -1 {
		lv = -1
	}
	if lv > 1 {
		lv = 1
	}
	if rv < -1 {
		rv = -1
	}
	if rv > 1 {
		rv = 1
	}
	return lv, rv, nil
}

func (r *Resampling) Read(b []uint8) (int, error) {
	if r.pos == r.Size() {
		return 0, io.EOF
	}
	n := len(b) / 4 * 4
	if r.Size()-r.pos <= int64(n) {
		n = int(r.Size() - r.pos)
	}
	for i := 0; i < n/4; i++ {
		l, r, err := r.at(r.pos/4 + int64(i))
		if err != nil {
			return 0, err
		}
		l16 := int16(l * (1<<15 - 1))
		r16 := int16(r * (1<<15 - 1))
		b[4*i] = uint8(l16)
		b[4*i+1] = uint8(l16 >> 8)
		b[4*i+2] = uint8(r16)
		b[4*i+3] = uint8(r16 >> 8)
	}
	r.pos += int64(n)
	return n, nil
}

func (r *Resampling) Seek(offset int64, whence int) (int64, error) {
	switch whence {
	case io.SeekStart:
		r.pos = offset
	case io.SeekCurrent:
		r.pos += offset
	case io.SeekEnd:
		r.pos += r.Size() + offset
	}
	if r.pos < 0 {
		r.pos = 0
	}
	if r.Size() <= r.pos {
		r.pos = r.Size()
	}
	return r.pos, nil
}

func (r *Resampling) Close() error {
	return r.source.Close()
}
<|endoftext|>


<commit_before>// Copyright 2017 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 convert

import (
	"io"
	"math"
)

var cosTable = [65536]float64{}

func init() {
	for i := range cosTable {
		cosTable[i] = math.Cos(float64(i) * math.Pi / 2 / float64(len(cosTable)))
	}
}

func fastCos01(x float64) float64 {
	if x < 0 {
		x = -x
	}
	i := int(4 * float64(len(cosTable)) * x)
	if 4*len(cosTable) < i {
		i %= 4 * len(cosTable)
	}
	sign := 1
	switch {
	case i < len(cosTable):
	case i < len(cosTable)*2:
		i = len(cosTable)*2 - i
		sign = -1
	case i < len(cosTable)*3:
		i -= len(cosTable) * 2
		sign = -1
	default:
		i = len(cosTable)*4 - i
	}
	if i == len(cosTable) {
		return 0
	}
	return float64(sign) * cosTable[i]
}

func fastSin01(x float64) float64 {
	return fastCos01(x - 0.25)
}

func sinc01(x float64) float64 {
	if math.Abs(x) < 1e-8 {
		return 1
	}
	return fastSin01(x) / (x * 2 * math.Pi)
}

type Resampling struct {
	source       io.ReadSeeker
	size         int64
	from         int
	to           int
	pos          int64
	srcBlock     int64
	srcBufL      map[int64][]float64
	srcBufR      map[int64][]float64
	lruSrcBlocks []int64
}

func NewResampling(source io.ReadSeeker, size int64, from, to int) *Resampling {
	r := &Resampling{
		source:   source,
		size:     size,
		from:     from,
		to:       to,
		srcBlock: -1,
		srcBufL:  map[int64][]float64{},
		srcBufR:  map[int64][]float64{},
	}
	return r
}

func (r *Resampling) Length() int64 {
	s := int64(float64(r.size) * float64(r.to) / float64(r.from))
	return s / 4 * 4
}

func (r *Resampling) src(i int64) (float64, float64, error) {
	const resamplingBufferSize = 4096

	if i < 0 {
		return 0, 0, nil
	}
	if r.size/4 <= int64(i) {
		return 0, 0, nil
	}
	nextPos := int64(i) / resamplingBufferSize
	if _, ok := r.srcBufL[nextPos]; !ok {
		if r.srcBlock+1 != nextPos {
			if _, err := r.source.Seek(nextPos*resamplingBufferSize*4, io.SeekStart); err != nil {
				return 0, 0, err
			}
		}
		buf := make([]byte, resamplingBufferSize*4)
		c := 0
		for c < len(buf) {
			n, err := r.source.Read(buf[c:])
			c += n
			if err != nil {
				if err == io.EOF {
					break
				}
				return 0, 0, err
			}
		}
		buf = buf[:c]
		sl := make([]float64, resamplingBufferSize)
		sr := make([]float64, resamplingBufferSize)
		for i := 0; i < len(buf)/4; i++ {
			sl[i] = float64(int16(buf[4*i])|(int16(buf[4*i+1])<<8)) / (1<<15 - 1)
			sr[i] = float64(int16(buf[4*i+2])|(int16(buf[4*i+3])<<8)) / (1<<15 - 1)
		}
		r.srcBlock = nextPos
		r.srcBufL[r.srcBlock] = sl
		r.srcBufR[r.srcBlock] = sr
		// To keep srcBufL/R not too big, let's remove the least used buffers.
		if len(r.lruSrcBlocks) >= 4 {
			p := r.lruSrcBlocks[0]
			delete(r.srcBufL, p)
			delete(r.srcBufR, p)
			copy(r.lruSrcBlocks, r.lruSrcBlocks[1:])
			r.lruSrcBlocks = r.lruSrcBlocks[:len(r.lruSrcBlocks)-1]
		}
		r.lruSrcBlocks = append(r.lruSrcBlocks, r.srcBlock)
	} else {
		r.srcBlock = nextPos
		idx := -1
		for i, p := range r.lruSrcBlocks {
			if p == r.srcBlock {
				idx = i
				break
			}
		}
		if idx == -1 {
			panic("not reach")
		}
		r.lruSrcBlocks = append(r.lruSrcBlocks[:idx], r.lruSrcBlocks[idx+1:]...)
		r.lruSrcBlocks = append(r.lruSrcBlocks, r.srcBlock)
	}
	ii := i % resamplingBufferSize
	return r.srcBufL[r.srcBlock][ii], r.srcBufR[r.srcBlock][ii], nil
}

func (r *Resampling) at(t int64) (float64, float64, error) {
	windowSize := 8.0
	tInSrc := float64(t) * float64(r.from) / float64(r.to)
	startN := int64(tInSrc - windowSize)
	if startN < 0 {
		startN = 0
	}
	if r.size/4 <= startN {
		startN = r.size/4 - 1
	}
	endN := int64(tInSrc + windowSize)
	if r.size/4 <= endN {
		endN = r.size/4 - 1
	}
	lv := 0.0
	rv := 0.0
	for n := startN; n <= endN; n++ {
		srcL, srcR, err := r.src(n)
		if err != nil {
			return 0, 0, err
		}
		d := tInSrc - float64(n)
		w := 0.5 + 0.5*fastCos01(d/(windowSize*2+1))
		s := sinc01(d/2) * w
		lv += srcL * s
		rv += srcR * s
	}
	if lv < -1 {
		lv = -1
	}
	if lv > 1 {
		lv = 1
	}
	if rv < -1 {
		rv = -1
	}
	if rv > 1 {
		rv = 1
	}
	return lv, rv, nil
}

func (r *Resampling) Read(b []byte) (int, error) {
	if r.pos == r.Length() {
		return 0, io.EOF
	}
	n := len(b) / 4 * 4
	if r.Length()-r.pos <= int64(n) {
		n = int(r.Length() - r.pos)
	}
	for i := 0; i < n/4; i++ {
		l, r, err := r.at(r.pos/4 + int64(i))
		if err != nil {
			return 0, err
		}
		l16 := int16(l * (1<<15 - 1))
		r16 := int16(r * (1<<15 - 1))
		b[4*i] = byte(l16)
		b[4*i+1] = byte(l16 >> 8)
		b[4*i+2] = byte(r16)
		b[4*i+3] = byte(r16 >> 8)
	}
	r.pos += int64(n)
	return n, nil
}

func (r *Resampling) Seek(offset int64, whence int) (int64, error) {
	switch whence {
	case io.SeekStart:
		r.pos = offset
	case io.SeekCurrent:
		r.pos += offset
	case io.SeekEnd:
		r.pos += r.Length() + offset
	}
	if r.pos < 0 {
		r.pos = 0
	}
	if r.Length() <= r.pos {
		r.pos = r.Length()
	}
	return r.pos, nil
}
<commit_msg>audio/internal/convert: add a lazy-load getter for `cosTable` (#2404)<commit_after>// Copyright 2017 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 convert

import (
	"io"
	"math"
	"sync"
)

var (
	// cosTable contains values of cosine applied to the range [0, π/2).
	// It must be initialised the first time it is referenced
	// in a function via its lazy load wrapper getCosTable().
	cosTable     []float64
	cosTableOnce sync.Once
)

func getCosTable() []float64 {
	cosTableOnce.Do(func() {
		cosTable = make([]float64, 65536)
		for i := range cosTable {
			cosTable[i] = math.Cos(float64(i) * math.Pi / 2 / float64(len(cosTable)))
		}
	})
	return cosTable
}

func fastCos01(x float64) float64 {
	if x < 0 {
		x = -x
	}
	i := int(4 * float64(len(getCosTable())) * x)
	if 4*len(cosTable) < i {
		i %= 4 * len(cosTable)
	}
	sign := 1
	switch {
	case i < len(cosTable):
	case i < len(cosTable)*2:
		i = len(cosTable)*2 - i
		sign = -1
	case i < len(cosTable)*3:
		i -= len(cosTable) * 2
		sign = -1
	default:
		i = len(cosTable)*4 - i
	}
	if i == len(cosTable) {
		return 0
	}
	return float64(sign) * cosTable[i]
}

func fastSin01(x float64) float64 {
	return fastCos01(x - 0.25)
}

func sinc01(x float64) float64 {
	if math.Abs(x) < 1e-8 {
		return 1
	}
	return fastSin01(x) / (x * 2 * math.Pi)
}

type Resampling struct {
	source       io.ReadSeeker
	size         int64
	from         int
	to           int
	pos          int64
	srcBlock     int64
	srcBufL      map[int64][]float64
	srcBufR      map[int64][]float64
	lruSrcBlocks []int64
}

func NewResampling(source io.ReadSeeker, size int64, from, to int) *Resampling {
	r := &Resampling{
		source:   source,
		size:     size,
		from:     from,
		to:       to,
		srcBlock: -1,
		srcBufL:  map[int64][]float64{},
		srcBufR:  map[int64][]float64{},
	}
	return r
}

func (r *Resampling) Length() int64 {
	s := int64(float64(r.size) * float64(r.to) / float64(r.from))
	return s / 4 * 4
}

func (r *Resampling) src(i int64) (float64, float64, error) {
	const resamplingBufferSize = 4096

	if i < 0 {
		return 0, 0, nil
	}
	if r.size/4 <= int64(i) {
		return 0, 0, nil
	}
	nextPos := int64(i) / resamplingBufferSize
	if _, ok := r.srcBufL[nextPos]; !ok {
		if r.srcBlock+1 != nextPos {
			if _, err := r.source.Seek(nextPos*resamplingBufferSize*4, io.SeekStart); err != nil {
				return 0, 0, err
			}
		}
		buf := make([]byte, resamplingBufferSize*4)
		c := 0
		for c < len(buf) {
			n, err := r.source.Read(buf[c:])
			c += n
			if err != nil {
				if err == io.EOF {
					break
				}
				return 0, 0, err
			}
		}
		buf = buf[:c]
		sl := make([]float64, resamplingBufferSize)
		sr := make([]float64, resamplingBufferSize)
		for i := 0; i < len(buf)/4; i++ {
			sl[i] = float64(int16(buf[4*i])|(int16(buf[4*i+1])<<8)) / (1<<15 - 1)
			sr[i] = float64(int16(buf[4*i+2])|(int16(buf[4*i+3])<<8)) / (1<<15 - 1)
		}
		r.srcBlock = nextPos
		r.srcBufL[r.srcBlock] = sl
		r.srcBufR[r.srcBlock] = sr
		// To keep srcBufL/R not too big, let's remove the least used buffers.
		if len(r.lruSrcBlocks) >= 4 {
			p := r.lruSrcBlocks[0]
			delete(r.srcBufL, p)
			delete(r.srcBufR, p)
			copy(r.lruSrcBlocks, r.lruSrcBlocks[1:])
			r.lruSrcBlocks = r.lruSrcBlocks[:len(r.lruSrcBlocks)-1]
		}
		r.lruSrcBlocks = append(r.lruSrcBlocks, r.srcBlock)
	} else {
		r.srcBlock = nextPos
		idx := -1
		for i, p := range r.lruSrcBlocks {
			if p == r.srcBlock {
				idx = i
				break
			}
		}
		if idx == -1 {
			panic("not reach")
		}
		r.lruSrcBlocks = append(r.lruSrcBlocks[:idx], r.lruSrcBlocks[idx+1:]...)
		r.lruSrcBlocks = append(r.lruSrcBlocks, r.srcBlock)
	}
	ii := i % resamplingBufferSize
	return r.srcBufL[r.srcBlock][ii], r.srcBufR[r.srcBlock][ii], nil
}

func (r *Resampling) at(t int64) (float64, float64, error) {
	windowSize := 8.0
	tInSrc := float64(t) * float64(r.from) / float64(r.to)
	startN := int64(tInSrc - windowSize)
	if startN < 0 {
		startN = 0
	}
	if r.size/4 <= startN {
		startN = r.size/4 - 1
	}
	endN := int64(tInSrc + windowSize)
	if r.size/4 <= endN {
		endN = r.size/4 - 1
	}
	lv := 0.0
	rv := 0.0
	for n := startN; n <= endN; n++ {
		srcL, srcR, err := r.src(n)
		if err != nil {
			return 0, 0, err
		}
		d := tInSrc - float64(n)
		w := 0.5 + 0.5*fastCos01(d/(windowSize*2+1))
		s := sinc01(d/2) * w
		lv += srcL * s
		rv += srcR * s
	}
	if lv < -1 {
		lv = -1
	}
	if lv > 1 {
		lv = 1
	}
	if rv < -1 {
		rv = -1
	}
	if rv > 1 {
		rv = 1
	}
	return lv, rv, nil
}

func (r *Resampling) Read(b []byte) (int, error) {
	if r.pos == r.Length() {
		return 0, io.EOF
	}
	n := len(b) / 4 * 4
	if r.Length()-r.pos <= int64(n) {
		n = int(r.Length() - r.pos)
	}
	for i := 0; i < n/4; i++ {
		l, r, err := r.at(r.pos/4 + int64(i))
		if err != nil {
			return 0, err
		}
		l16 := int16(l * (1<<15 - 1))
		r16 := int16(r * (1<<15 - 1))
		b[4*i] = byte(l16)
		b[4*i+1] = byte(l16 >> 8)
		b[4*i+2] = byte(r16)
		b[4*i+3] = byte(r16 >> 8)
	}
	r.pos += int64(n)
	return n, nil
}

func (r *Resampling) Seek(offset int64, whence int) (int64, error) {
	switch whence {
	case io.SeekStart:
		r.pos = offset
	case io.SeekCurrent:
		r.pos += offset
	case io.SeekEnd:
		r.pos += r.Length() + offset
	}
	if r.pos < 0 {
		r.pos = 0
	}
	if r.Length() <= r.pos {
		r.pos = r.Length()
	}
	return r.pos, nil
}
<|endoftext|>


<commit_before>package workitem

import (
	"fmt"

	"golang.org/x/net/context"

	"github.com/almighty/almighty-core/errors"
	"github.com/almighty/almighty-core/log"
	"github.com/almighty/almighty-core/path"

	"github.com/jinzhu/gorm"
	errs "github.com/pkg/errors"
	uuid "github.com/satori/go.uuid"
)

var cache = NewWorkItemTypeCache()

// WorkItemTypeRepository encapsulates storage & retrieval of work item types
type WorkItemTypeRepository interface {
	Load(ctx context.Context, spaceID uuid.UUID, id uuid.UUID) (*WorkItemType, error)
	Create(ctx context.Context, spaceID uuid.UUID, id *uuid.UUID, extendedTypeID *uuid.UUID, name string, description *string, icon string, fields map[string]FieldDefinition) (*WorkItemType, error)
	List(ctx context.Context, spaceID uuid.UUID, start *int, length *int) ([]WorkItemType, error)
	ListPlannerItems(ctx context.Context, spaceID uuid.UUID) ([]WorkItemType, error)
}

// NewWorkItemTypeRepository creates a wi type repository based on gorm
func NewWorkItemTypeRepository(db *gorm.DB) *GormWorkItemTypeRepository {
	return &GormWorkItemTypeRepository{db}
}

// GormWorkItemTypeRepository implements WorkItemTypeRepository using gorm
type GormWorkItemTypeRepository struct {
	db *gorm.DB
}

// LoadByID returns the work item for the given id
// returns NotFoundError, InternalError
func (r *GormWorkItemTypeRepository) LoadByID(ctx context.Context, id uuid.UUID) (*WorkItemType, error) {
	res, err := r.LoadTypeFromDB(ctx, id)
	if err != nil {
		return nil, errs.WithStack(err)
	}
	return res, nil
}

// Load returns the work item for the given spaceID and id
// returns NotFoundError, InternalError
func (r *GormWorkItemTypeRepository) Load(ctx context.Context, spaceID uuid.UUID, id uuid.UUID) (*WorkItemType, error) {
	log.Logger().Infoln("Loading work item type", id)
	res, ok := cache.Get(id)
	if !ok {
		log.Info(ctx, map[string]interface{}{
			"wit_id":   id,
			"space_id": spaceID,
		}, "Work item type doesn't exist in the cache. Loading from DB...")
		res = WorkItemType{}

		db := r.db.Model(&res).Where("id=? AND space_id=?", id, spaceID).First(&res)
		if db.RecordNotFound() {
			log.Error(ctx, map[string]interface{}{
				"wit_id":   id,
				"space_id": spaceID,
			}, "work item type not found")
			return nil, errors.NewNotFoundError("work item type", id.String())
		}
		if err := db.Error; err != nil {
			return nil, errors.NewInternalError(err.Error())
		}
		cache.Put(res)
	}
	return &res, nil
}

// LoadTypeFromDB return work item type for the given id
func (r *GormWorkItemTypeRepository) LoadTypeFromDB(ctx context.Context, id uuid.UUID) (*WorkItemType, error) {
	log.Logger().Infoln("Loading work item type", id)
	res, ok := cache.Get(id)
	if !ok {
		log.Info(ctx, map[string]interface{}{
			"wit_id": id,
		}, "Work item type doesn't exist in the cache. Loading from DB...")
		res = WorkItemType{}
		db := r.db.Model(&res).Where("id=?", id).First(&res)
		if db.RecordNotFound() {
			log.Error(ctx, map[string]interface{}{
				"wit_id": id,
			}, "work item type not found")
			return nil, errors.NewNotFoundError("work item type", id.String())
		}
		if err := db.Error; err != nil {
			log.Error(ctx, map[string]interface{}{
				"witID": id,
			}, "work item type retrieval error", err.Error())
			return nil, errors.NewInternalError(err.Error())
		}
		cache.Put(res)
	}
	return &res, nil
}

// ClearGlobalWorkItemTypeCache removes all work items from the global cache
func ClearGlobalWorkItemTypeCache() {
	cache.Clear()
}

// Create creates a new work item in the repository
// returns BadParameterError, ConversionError or InternalError
func (r *GormWorkItemTypeRepository) Create(ctx context.Context, spaceID uuid.UUID, id *uuid.UUID, extendedTypeID *uuid.UUID, name string, description *string, icon string, fields map[string]FieldDefinition) (*WorkItemType, error) {
	// Make sure this WIT has an ID
	if id == nil {
		tmpID := uuid.NewV4()
		id = &tmpID
	}

	existing, _ := r.LoadTypeFromDB(ctx, *id)
	if existing != nil {
		log.Error(ctx, map[string]interface{}{"wit_id": *id}, "unable to create new work item type")
		return nil, errors.NewBadParameterError("name", *id)
	}

	allFields := map[string]FieldDefinition{}
	path := LtreeSafeID(*id)
	if extendedTypeID != nil {
		extendedType := WorkItemType{}
		db := r.db.Model(&extendedType).Where("id=?", extendedTypeID).First(&extendedType)
		if db.RecordNotFound() {
			return nil, errors.NewBadParameterError("extendedTypeID", *extendedTypeID)
		}
		if err := db.Error; err != nil {
			return nil, errors.NewInternalError(err.Error())
		}
		// copy fields from extended type
		for key, value := range extendedType.Fields {
			allFields[key] = value
		}
		path = extendedType.Path + pathSep + path
	}
	// now process new fields, checking whether they are already there.
	for field, definition := range fields {
		existing, exists := allFields[field]
		if exists && !compatibleFields(existing, definition) {
			return nil, fmt.Errorf("incompatible change for field %s", field)
		}
		allFields[field] = definition
	}

	created := WorkItemType{
		Version:     0,
		ID:          *id,
		Name:        name,
		Description: description,
		Icon:        icon,
		Path:        path,
		Fields:      allFields,
		SpaceID:     spaceID,
	}

	if err := r.db.Create(&created).Error; err != nil {
		return nil, errors.NewInternalError(err.Error())
	}

	log.Debug(ctx, map[string]interface{}{"witID": created.ID}, "Work item type created successfully!")
	return &created, nil
}

// List returns work item types that derives from PlannerItem type
func (r *GormWorkItemTypeRepository) ListPlannerItems(ctx context.Context, spaceID uuid.UUID) ([]WorkItemType, error) {
	var rows []WorkItemType
	path := path.Path{}
	db := r.db.Select("id").Where("space_id = ? AND path::text LIKE '"+path.ConvertToLtree(SystemPlannerItem)+".%'", spaceID.String())

	if err := db.Find(&rows).Error; err != nil {
		log.Error(ctx, map[string]interface{}{
			"space_id": spaceID,
			"err":      err,
		}, "unable to list the work item types that derive of planner item")
		return nil, errs.WithStack(err)
	}
	return rows, nil
}

// List returns work item types selected by the given criteria.Expression, starting with start (zero-based) and returning at most "limit" item types
func (r *GormWorkItemTypeRepository) List(ctx context.Context, spaceID uuid.UUID, start *int, limit *int) ([]WorkItemType, error) {
	// Currently we don't implement filtering here, so leave this empty
	// TODO: (kwk) implement criteria parsing just like for work items
	var rows []WorkItemType
	db := r.db.Where("space_id = ?", spaceID)
	if start != nil {
		db = db.Offset(*start)
	}
	if limit != nil {
		db = db.Limit(*limit)
	}
	if err := db.Find(&rows).Error; err != nil {
		return nil, errs.WithStack(err)
	}
	return rows, nil
}
<commit_msg>Remove not needed check (#1158)<commit_after>package workitem

import (
	"fmt"

	"golang.org/x/net/context"

	"github.com/almighty/almighty-core/errors"
	"github.com/almighty/almighty-core/log"
	"github.com/almighty/almighty-core/path"

	"github.com/jinzhu/gorm"
	errs "github.com/pkg/errors"
	uuid "github.com/satori/go.uuid"
)

var cache = NewWorkItemTypeCache()

// WorkItemTypeRepository encapsulates storage & retrieval of work item types
type WorkItemTypeRepository interface {
	Load(ctx context.Context, spaceID uuid.UUID, id uuid.UUID) (*WorkItemType, error)
	Create(ctx context.Context, spaceID uuid.UUID, id *uuid.UUID, extendedTypeID *uuid.UUID, name string, description *string, icon string, fields map[string]FieldDefinition) (*WorkItemType, error)
	List(ctx context.Context, spaceID uuid.UUID, start *int, length *int) ([]WorkItemType, error)
	ListPlannerItems(ctx context.Context, spaceID uuid.UUID) ([]WorkItemType, error)
}

// NewWorkItemTypeRepository creates a wi type repository based on gorm
func NewWorkItemTypeRepository(db *gorm.DB) *GormWorkItemTypeRepository {
	return &GormWorkItemTypeRepository{db}
}

// GormWorkItemTypeRepository implements WorkItemTypeRepository using gorm
type GormWorkItemTypeRepository struct {
	db *gorm.DB
}

// LoadByID returns the work item for the given id
// returns NotFoundError, InternalError
func (r *GormWorkItemTypeRepository) LoadByID(ctx context.Context, id uuid.UUID) (*WorkItemType, error) {
	res, err := r.LoadTypeFromDB(ctx, id)
	if err != nil {
		return nil, errs.WithStack(err)
	}
	return res, nil
}

// Load returns the work item for the given spaceID and id
// returns NotFoundError, InternalError
func (r *GormWorkItemTypeRepository) Load(ctx context.Context, spaceID uuid.UUID, id uuid.UUID) (*WorkItemType, error) {
	log.Logger().Infoln("Loading work item type", id)
	res, ok := cache.Get(id)
	if !ok {
		log.Info(ctx, map[string]interface{}{
			"wit_id":   id,
			"space_id": spaceID,
		}, "Work item type doesn't exist in the cache. Loading from DB...")
		res = WorkItemType{}

		db := r.db.Model(&res).Where("id=? AND space_id=?", id, spaceID).First(&res)
		if db.RecordNotFound() {
			log.Error(ctx, map[string]interface{}{
				"wit_id":   id,
				"space_id": spaceID,
			}, "work item type not found")
			return nil, errors.NewNotFoundError("work item type", id.String())
		}
		if err := db.Error; err != nil {
			return nil, errors.NewInternalError(err.Error())
		}
		cache.Put(res)
	}
	return &res, nil
}

// LoadTypeFromDB return work item type for the given id
func (r *GormWorkItemTypeRepository) LoadTypeFromDB(ctx context.Context, id uuid.UUID) (*WorkItemType, error) {
	log.Logger().Infoln("Loading work item type", id)
	res, ok := cache.Get(id)
	if !ok {
		log.Info(ctx, map[string]interface{}{
			"wit_id": id,
		}, "Work item type doesn't exist in the cache. Loading from DB...")
		res = WorkItemType{}
		db := r.db.Model(&res).Where("id=?", id).First(&res)
		if db.RecordNotFound() {
			log.Error(ctx, map[string]interface{}{
				"wit_id": id,
			}, "work item type not found")
			return nil, errors.NewNotFoundError("work item type", id.String())
		}
		if err := db.Error; err != nil {
			log.Error(ctx, map[string]interface{}{
				"witID": id,
			}, "work item type retrieval error", err.Error())
			return nil, errors.NewInternalError(err.Error())
		}
		cache.Put(res)
	}
	return &res, nil
}

// ClearGlobalWorkItemTypeCache removes all work items from the global cache
func ClearGlobalWorkItemTypeCache() {
	cache.Clear()
}

// Create creates a new work item in the repository
// returns BadParameterError, ConversionError or InternalError
func (r *GormWorkItemTypeRepository) Create(ctx context.Context, spaceID uuid.UUID, id *uuid.UUID, extendedTypeID *uuid.UUID, name string, description *string, icon string, fields map[string]FieldDefinition) (*WorkItemType, error) {
	// Make sure this WIT has an ID
	if id == nil {
		tmpID := uuid.NewV4()
		id = &tmpID
	}

	allFields := map[string]FieldDefinition{}
	path := LtreeSafeID(*id)
	if extendedTypeID != nil {
		extendedType := WorkItemType{}
		db := r.db.Model(&extendedType).Where("id=?", extendedTypeID).First(&extendedType)
		if db.RecordNotFound() {
			return nil, errors.NewBadParameterError("extendedTypeID", *extendedTypeID)
		}
		if err := db.Error; err != nil {
			return nil, errors.NewInternalError(err.Error())
		}
		// copy fields from extended type
		for key, value := range extendedType.Fields {
			allFields[key] = value
		}
		path = extendedType.Path + pathSep + path
	}
	// now process new fields, checking whether they are already there.
	for field, definition := range fields {
		existing, exists := allFields[field]
		if exists && !compatibleFields(existing, definition) {
			return nil, fmt.Errorf("incompatible change for field %s", field)
		}
		allFields[field] = definition
	}

	created := WorkItemType{
		Version:     0,
		ID:          *id,
		Name:        name,
		Description: description,
		Icon:        icon,
		Path:        path,
		Fields:      allFields,
		SpaceID:     spaceID,
	}

	if err := r.db.Create(&created).Error; err != nil {
		return nil, errors.NewInternalError(err.Error())
	}

	log.Debug(ctx, map[string]interface{}{"witID": created.ID}, "Work item type created successfully!")
	return &created, nil
}

// List returns work item types that derives from PlannerItem type
func (r *GormWorkItemTypeRepository) ListPlannerItems(ctx context.Context, spaceID uuid.UUID) ([]WorkItemType, error) {
	var rows []WorkItemType
	path := path.Path{}
	db := r.db.Select("id").Where("space_id = ? AND path::text LIKE '"+path.ConvertToLtree(SystemPlannerItem)+".%'", spaceID.String())

	if err := db.Find(&rows).Error; err != nil {
		log.Error(ctx, map[string]interface{}{
			"space_id": spaceID,
			"err":      err,
		}, "unable to list the work item types that derive of planner item")
		return nil, errs.WithStack(err)
	}
	return rows, nil
}

// List returns work item types selected by the given criteria.Expression, starting with start (zero-based) and returning at most "limit" item types
func (r *GormWorkItemTypeRepository) List(ctx context.Context, spaceID uuid.UUID, start *int, limit *int) ([]WorkItemType, error) {
	// Currently we don't implement filtering here, so leave this empty
	// TODO: (kwk) implement criteria parsing just like for work items
	var rows []WorkItemType
	db := r.db.Where("space_id = ?", spaceID)
	if start != nil {
		db = db.Offset(*start)
	}
	if limit != nil {
		db = db.Limit(*limit)
	}
	if err := db.Find(&rows).Error; err != nil {
		return nil, errs.WithStack(err)
	}
	return rows, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state

import (
	"reflect"

	"github.com/juju/utils/set"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/charm.v6-unstable"
)

type MigrationSuite struct{}

var _ = gc.Suite(&MigrationSuite{})

func (s *MigrationSuite) TestKnownCollections(c *gc.C) {
	completedCollections := set.NewStrings(
		annotationsC,
		modelsC,
		modelUsersC,
		modelUserLastConnectionC,
		settingsC,
		statusesC,

		// machine
		instanceDataC,
		machinesC,
		openedPortsC,

		// service / unit
		servicesC,
		unitsC,
		meterStatusC, // red / green status for metrics of units

		// settings reference counts are only used for services
		settingsrefsC,

		// relation
		relationsC,
		relationScopesC,
	)

	ignoredCollections := set.NewStrings(
		// We don't export the controller model at this stage.
		controllersC,
		// Users aren't migrated.
		usersC,
		userLastLoginC,
		// userenvnameC is just to provide a unique key constraint.
		usermodelnameC,
		// Metrics aren't migrated.
		metricsC,
		// leaseC is deprecated in favour of leasesC.
		leaseC,
		// Backup and restore information is not migrated.
		restoreInfoC,
		// upgradeInfoC is used to coordinate upgrades and schema migrations,
		// and aren't needed for model migrations.
		upgradeInfoC,
		// Not exported, but the tools will possibly need to be either bundled
		// with the representation or sent separately.
		toolsmetadataC,
		// Transaction stuff.
		"txns",
		"txns.log",

		// We don't import any of the migration collections.
		modelMigrationsC,
		modelMigrationStatusC,
		modelMigrationsActiveC,

		// The container ref document is primarily there to keep track
		// of a particular machine's containers. The migration format
		// uses object containment for this purpose.
		containerRefsC,
		// The min units collection is only used to trigger a watcher
		// in order to have the service add or remove units if the minimum
		// number of units is changed. The Service doc has all we need
		// for migratino.
		minUnitsC,
		// This is a transitory collection of units that need to be assigned
		// to machines.
		assignUnitC,
	)

	// THIS SET WILL BE REMOVED WHEN MIGRATIONS ARE COMPLETE
	todoCollections := set.NewStrings(
		// model
		blocksC,
		cleanupsC,
		cloudimagemetadataC,
		sequenceC,

		// machine
		rebootC,

		// service / unit
		charmsC,
		leasesC,
		"payloads",
		"resources",
		endpointBindingsC,

		// storage
		blockDevicesC,
		filesystemsC,
		filesystemAttachmentsC,
		storageInstancesC,
		storageAttachmentsC,
		storageConstraintsC,
		volumesC,
		volumeAttachmentsC,

		// network
		ipaddressesC,
		networksC,
		networkInterfacesC,
		requestedNetworksC,
		subnetsC,
		spacesC,

		// actions
		actionsC,
		actionNotificationsC,
		actionresultsC,

		// done as part of machines/services/units
		constraintsC,
		statusesHistoryC,

		// uncategorised
		metricsManagerC, // should really be copied across
	)

	envCollections := set.NewStrings()
	for name := range allCollections() {
		envCollections.Add(name)
	}

	known := completedCollections.Union(ignoredCollections)

	remainder := envCollections.Difference(known)
	remainder = remainder.Difference(todoCollections)

	// If this test fails, it means that a new collection has been added
	// but migrations for it has not been done. This is a Bad Thing™.
	c.Assert(remainder, gc.HasLen, 0)
}

func (s *MigrationSuite) TestModelDocFields(c *gc.C) {
	fields := set.NewStrings(
		// UUID and Mame are constructed from the model config.
		"UUID",
		"Name",
		// Life will always be alive, or we won't be migrating.
		"Life",
		"Owner",
		"LatestAvailableTools",
		// ServerUUID is recreated when the new model is created in the
		// new controller (yay name changes).
		"ServerUUID",
		// Both of the times for dying and death are empty as the model
		// is alive.
		"TimeOfDying",
		"TimeOfDeath",
	)
	s.AssertExportedFields(c, modelDoc{}, fields)
}

func (s *MigrationSuite) TestEnvUserDocFields(c *gc.C) {
	fields := set.NewStrings(
		// ID is the same as UserName (but lowercased)
		"ID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		// Tracked fields:
		"UserName",
		"DisplayName",
		"CreatedBy",
		"DateCreated",
		"ReadOnly",
	)
	s.AssertExportedFields(c, modelUserDoc{}, fields)
}

func (s *MigrationSuite) TestEnvUserLastConnectionDocFields(c *gc.C) {
	fields := set.NewStrings(
		// ID is the same as UserName (but lowercased)
		"ID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		// UserName is captured in the migration.User.
		"UserName",
		"LastConnection",
	)
	s.AssertExportedFields(c, modelUserLastConnectionDoc{}, fields)
}

func (s *MigrationSuite) TestMachineDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID is the env + machine id
		"DocID",
		// ID is the machine id
		"Id",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		// Life is always alive, confirmed by export precheck.
		"Life",

		"Addresses",
		"ContainerType",
		"Jobs",
		"MachineAddresses",
		"Nonce",
		"PasswordHash",
		"Placement",
		"PreferredPrivateAddress",
		"PreferredPublicAddress",
		"Series",
		"SupportedContainers",
		"SupportedContainersKnown",
		"Tools",

		// Ignored at this stage, could be an issue if mongo 3.0 isn't
		// available.
		"StopMongoUntilVersion",
	)
	todo := set.NewStrings(
		"Principals",
		"Volumes",
		"NoVote",
		"Clean",
		"Filesystems",
		"HasVote",
	)
	s.AssertExportedFields(c, machineDoc{}, fields.Union(todo))
}

func (s *MigrationSuite) TestInstanceDataFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID is the env + machine id
		"DocID",
		"MachineId",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",

		"InstanceId",
		"Status",
		"Arch",
		"Mem",
		"RootDisk",
		"CpuCores",
		"CpuPower",
		"Tags",
		"AvailZone",
	)
	s.AssertExportedFields(c, instanceData{}, fields)
}

func (s *MigrationSuite) TestServiceDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID is the env + name
		"DocID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		// Always alive, not explicitly exported.
		"Life",
		// OwnerTag is deprecated and should be deleted.
		"OwnerTag",
		// TxnRevno is mgo internals and should not be migrated.
		"TxnRevno",

		"Name",
		"Series",
		"Subordinate",
		"CharmURL",
		"ForceCharm",
		"Exposed",
		"MinUnits",
		"MetricCredentials",
		// UnitCount is handled by the number of units for the exported service.
		"UnitCount",
	)
	todo := set.NewStrings(
		"RelationCount",
	)
	s.AssertExportedFields(c, serviceDoc{}, fields.Union(todo))
}

func (s *MigrationSuite) TestSettingsRefsDocFields(c *gc.C) {
	fields := set.NewStrings(
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",

		"RefCount",
	)
	s.AssertExportedFields(c, settingsRefsDoc{}, fields)
}

func (s *MigrationSuite) TestUnitDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID itself isn't migrated
		"DocID",
		"Name",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		// Service is implicit in the migration structure through containment.
		"Service",
		// Series and CharmURL also come from the service.
		"Series",
		"CharmURL",
		"Principal",
		"Subordinates",
		"MachineId",
		// Resolved is not migrated as we check that all is good before we start.
		"Resolved",
		"Tools",
		// Life isn't migrated as we only migrate live things.
		"Life",
		// TxnRevno isn't migrated.
		"TxnRevno",
		"PasswordHash",
		// Obsolete and not migrated.
		"Ports",
		"PublicAddress",
		"PrivateAddress",
	)
	todo := set.NewStrings(
		"StorageAttachmentCount",
	)

	s.AssertExportedFields(c, unitDoc{}, fields.Union(todo))
}

func (s *MigrationSuite) TestPortsDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID itself isn't migrated
		"DocID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		// MachineId is implicit in the migration structure through containment.
		"MachineID",
		"NetworkName",
		"Ports",
		// TxnRevno isn't migrated.
		"TxnRevno",
	)
	s.AssertExportedFields(c, portsDoc{}, fields)
}

func (s *MigrationSuite) TestMeterStatusDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID itself isn't migrated
		"DocID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		"Code",
		"Info",
	)
	s.AssertExportedFields(c, meterStatusDoc{}, fields)
}

func (s *MigrationSuite) TestRelationDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID itself isn't migrated
		"DocID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		"Key",
		"Id",
		"Endpoints",
		// Life isn't exported, only alive.
		"Life",
		// UnitCount isn't explicitly exported, but defined by the stored
		// unit settings data for the relation endpoint.
		"UnitCount",
	)
	s.AssertExportedFields(c, relationDoc{}, fields)
	// We also need to check the Endpoint and nested charm.Relation field.
	endpointFields := set.NewStrings("ServiceName", "Relation")
	s.AssertExportedFields(c, Endpoint{}, endpointFields)
	charmRelationFields := set.NewStrings(
		"Name",
		"Role",
		"Interface",
		"Optional",
		"Limit",
		"Scope",
	)
	s.AssertExportedFields(c, charm.Relation{}, charmRelationFields)
}

func (s *MigrationSuite) TestRelationScopeDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID itself isn't migrated
		"DocID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		"Key",
		// Departing isn't exported as we only deal with live, stable systems.
		"Departing",
	)
	s.AssertExportedFields(c, relationScopeDoc{}, fields)
}

func (s *MigrationSuite) TestAnnatatorDocFields(c *gc.C) {
	fields := set.NewStrings(
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		"GlobalKey",
		"Tag",
		"Annotations",
	)
	s.AssertExportedFields(c, annotatorDoc{}, fields)
}

func (s *MigrationSuite) AssertExportedFields(c *gc.C, doc interface{}, fields set.Strings) {
	expected := getExportedFields(doc)
	unknown := expected.Difference(fields)
	// If this test fails, it means that extra fields have been added to the
	// doc without thinking about the migration implications.
	c.Assert(unknown, gc.HasLen, 0)
}

func getExportedFields(arg interface{}) set.Strings {
	t := reflect.TypeOf(arg)
	result := set.NewStrings()

	count := t.NumField()
	for i := 0; i < count; i++ {
		f := t.Field(i)
		// empty PkgPath means exported field.
		// see https://golang.org/pkg/reflect/#StructField
		if f.PkgPath == "" {
			result.Add(f.Name)
		}
	}

	return result
}
<commit_msg>Record that we have done constraints.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state

import (
	"reflect"

	"github.com/juju/utils/set"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/charm.v6-unstable"
)

type MigrationSuite struct{}

var _ = gc.Suite(&MigrationSuite{})

func (s *MigrationSuite) TestKnownCollections(c *gc.C) {
	completedCollections := set.NewStrings(
		annotationsC,
		constraintsC,
		modelsC,
		modelUsersC,
		modelUserLastConnectionC,
		settingsC,
		statusesC,

		// machine
		instanceDataC,
		machinesC,
		openedPortsC,

		// service / unit
		servicesC,
		unitsC,
		meterStatusC, // red / green status for metrics of units

		// settings reference counts are only used for services
		settingsrefsC,

		// relation
		relationsC,
		relationScopesC,
	)

	ignoredCollections := set.NewStrings(
		// We don't export the controller model at this stage.
		controllersC,
		// Users aren't migrated.
		usersC,
		userLastLoginC,
		// userenvnameC is just to provide a unique key constraint.
		usermodelnameC,
		// Metrics aren't migrated.
		metricsC,
		// leaseC is deprecated in favour of leasesC.
		leaseC,
		// Backup and restore information is not migrated.
		restoreInfoC,
		// upgradeInfoC is used to coordinate upgrades and schema migrations,
		// and aren't needed for model migrations.
		upgradeInfoC,
		// Not exported, but the tools will possibly need to be either bundled
		// with the representation or sent separately.
		toolsmetadataC,
		// Transaction stuff.
		"txns",
		"txns.log",

		// We don't import any of the migration collections.
		modelMigrationsC,
		modelMigrationStatusC,
		modelMigrationsActiveC,

		// The container ref document is primarily there to keep track
		// of a particular machine's containers. The migration format
		// uses object containment for this purpose.
		containerRefsC,
		// The min units collection is only used to trigger a watcher
		// in order to have the service add or remove units if the minimum
		// number of units is changed. The Service doc has all we need
		// for migratino.
		minUnitsC,
		// This is a transitory collection of units that need to be assigned
		// to machines.
		assignUnitC,
	)

	// THIS SET WILL BE REMOVED WHEN MIGRATIONS ARE COMPLETE
	todoCollections := set.NewStrings(
		// model
		blocksC,
		cleanupsC,
		cloudimagemetadataC,
		sequenceC,

		// machine
		rebootC,

		// service / unit
		charmsC,
		leasesC,
		"payloads",
		"resources",
		endpointBindingsC,

		// storage
		blockDevicesC,
		filesystemsC,
		filesystemAttachmentsC,
		storageInstancesC,
		storageAttachmentsC,
		storageConstraintsC,
		volumesC,
		volumeAttachmentsC,

		// network
		ipaddressesC,
		networksC,
		networkInterfacesC,
		requestedNetworksC,
		subnetsC,
		spacesC,

		// actions
		actionsC,
		actionNotificationsC,
		actionresultsC,

		// done as part of machines/services/units
		statusesHistoryC,

		// uncategorised
		metricsManagerC, // should really be copied across
	)

	envCollections := set.NewStrings()
	for name := range allCollections() {
		envCollections.Add(name)
	}

	known := completedCollections.Union(ignoredCollections)

	remainder := envCollections.Difference(known)
	remainder = remainder.Difference(todoCollections)

	// If this test fails, it means that a new collection has been added
	// but migrations for it has not been done. This is a Bad Thing™.
	c.Assert(remainder, gc.HasLen, 0)
}

func (s *MigrationSuite) TestModelDocFields(c *gc.C) {
	fields := set.NewStrings(
		// UUID and Mame are constructed from the model config.
		"UUID",
		"Name",
		// Life will always be alive, or we won't be migrating.
		"Life",
		"Owner",
		"LatestAvailableTools",
		// ServerUUID is recreated when the new model is created in the
		// new controller (yay name changes).
		"ServerUUID",
		// Both of the times for dying and death are empty as the model
		// is alive.
		"TimeOfDying",
		"TimeOfDeath",
	)
	s.AssertExportedFields(c, modelDoc{}, fields)
}

func (s *MigrationSuite) TestEnvUserDocFields(c *gc.C) {
	fields := set.NewStrings(
		// ID is the same as UserName (but lowercased)
		"ID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		// Tracked fields:
		"UserName",
		"DisplayName",
		"CreatedBy",
		"DateCreated",
		"ReadOnly",
	)
	s.AssertExportedFields(c, modelUserDoc{}, fields)
}

func (s *MigrationSuite) TestEnvUserLastConnectionDocFields(c *gc.C) {
	fields := set.NewStrings(
		// ID is the same as UserName (but lowercased)
		"ID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		// UserName is captured in the migration.User.
		"UserName",
		"LastConnection",
	)
	s.AssertExportedFields(c, modelUserLastConnectionDoc{}, fields)
}

func (s *MigrationSuite) TestMachineDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID is the env + machine id
		"DocID",
		// ID is the machine id
		"Id",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		// Life is always alive, confirmed by export precheck.
		"Life",

		"Addresses",
		"ContainerType",
		"Jobs",
		"MachineAddresses",
		"Nonce",
		"PasswordHash",
		"Placement",
		"PreferredPrivateAddress",
		"PreferredPublicAddress",
		"Series",
		"SupportedContainers",
		"SupportedContainersKnown",
		"Tools",

		// Ignored at this stage, could be an issue if mongo 3.0 isn't
		// available.
		"StopMongoUntilVersion",
	)
	todo := set.NewStrings(
		"Principals",
		"Volumes",
		"NoVote",
		"Clean",
		"Filesystems",
		"HasVote",
	)
	s.AssertExportedFields(c, machineDoc{}, fields.Union(todo))
}

func (s *MigrationSuite) TestInstanceDataFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID is the env + machine id
		"DocID",
		"MachineId",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",

		"InstanceId",
		"Status",
		"Arch",
		"Mem",
		"RootDisk",
		"CpuCores",
		"CpuPower",
		"Tags",
		"AvailZone",
	)
	s.AssertExportedFields(c, instanceData{}, fields)
}

func (s *MigrationSuite) TestServiceDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID is the env + name
		"DocID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		// Always alive, not explicitly exported.
		"Life",
		// OwnerTag is deprecated and should be deleted.
		"OwnerTag",
		// TxnRevno is mgo internals and should not be migrated.
		"TxnRevno",

		"Name",
		"Series",
		"Subordinate",
		"CharmURL",
		"ForceCharm",
		"Exposed",
		"MinUnits",
		"MetricCredentials",
		// UnitCount is handled by the number of units for the exported service.
		"UnitCount",
	)
	todo := set.NewStrings(
		"RelationCount",
	)
	s.AssertExportedFields(c, serviceDoc{}, fields.Union(todo))
}

func (s *MigrationSuite) TestSettingsRefsDocFields(c *gc.C) {
	fields := set.NewStrings(
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",

		"RefCount",
	)
	s.AssertExportedFields(c, settingsRefsDoc{}, fields)
}

func (s *MigrationSuite) TestUnitDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID itself isn't migrated
		"DocID",
		"Name",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		// Service is implicit in the migration structure through containment.
		"Service",
		// Series and CharmURL also come from the service.
		"Series",
		"CharmURL",
		"Principal",
		"Subordinates",
		"MachineId",
		// Resolved is not migrated as we check that all is good before we start.
		"Resolved",
		"Tools",
		// Life isn't migrated as we only migrate live things.
		"Life",
		// TxnRevno isn't migrated.
		"TxnRevno",
		"PasswordHash",
		// Obsolete and not migrated.
		"Ports",
		"PublicAddress",
		"PrivateAddress",
	)
	todo := set.NewStrings(
		"StorageAttachmentCount",
	)

	s.AssertExportedFields(c, unitDoc{}, fields.Union(todo))
}

func (s *MigrationSuite) TestPortsDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID itself isn't migrated
		"DocID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		// MachineId is implicit in the migration structure through containment.
		"MachineID",
		"NetworkName",
		"Ports",
		// TxnRevno isn't migrated.
		"TxnRevno",
	)
	s.AssertExportedFields(c, portsDoc{}, fields)
}

func (s *MigrationSuite) TestMeterStatusDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID itself isn't migrated
		"DocID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		"Code",
		"Info",
	)
	s.AssertExportedFields(c, meterStatusDoc{}, fields)
}

func (s *MigrationSuite) TestRelationDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID itself isn't migrated
		"DocID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		"Key",
		"Id",
		"Endpoints",
		// Life isn't exported, only alive.
		"Life",
		// UnitCount isn't explicitly exported, but defined by the stored
		// unit settings data for the relation endpoint.
		"UnitCount",
	)
	s.AssertExportedFields(c, relationDoc{}, fields)
	// We also need to check the Endpoint and nested charm.Relation field.
	endpointFields := set.NewStrings("ServiceName", "Relation")
	s.AssertExportedFields(c, Endpoint{}, endpointFields)
	charmRelationFields := set.NewStrings(
		"Name",
		"Role",
		"Interface",
		"Optional",
		"Limit",
		"Scope",
	)
	s.AssertExportedFields(c, charm.Relation{}, charmRelationFields)
}

func (s *MigrationSuite) TestRelationScopeDocFields(c *gc.C) {
	fields := set.NewStrings(
		// DocID itself isn't migrated
		"DocID",
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		"Key",
		// Departing isn't exported as we only deal with live, stable systems.
		"Departing",
	)
	s.AssertExportedFields(c, relationScopeDoc{}, fields)
}

func (s *MigrationSuite) TestAnnatatorDocFields(c *gc.C) {
	fields := set.NewStrings(
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		"GlobalKey",
		"Tag",
		"Annotations",
	)
	s.AssertExportedFields(c, annotatorDoc{}, fields)
}

func (s *MigrationSuite) TestConstraintsDocFields(c *gc.C) {
	fields := set.NewStrings(
		// ModelUUID shouldn't be exported, and is inherited
		// from the model definition.
		"ModelUUID",
		"Arch",
		"CpuCores",
		"CpuPower",
		"Mem",
		"RootDisk",
		"InstanceType",
		"Container",
		"Tags",
		"Spaces",
		// Networks is a deprecated constraint and not exported.
		"Networks",
	)
	s.AssertExportedFields(c, constraintsDoc{}, fields)
}

func (s *MigrationSuite) AssertExportedFields(c *gc.C, doc interface{}, fields set.Strings) {
	expected := getExportedFields(doc)
	unknown := expected.Difference(fields)
	// If this test fails, it means that extra fields have been added to the
	// doc without thinking about the migration implications.
	c.Assert(unknown, gc.HasLen, 0)
}

func getExportedFields(arg interface{}) set.Strings {
	t := reflect.TypeOf(arg)
	result := set.NewStrings()

	count := t.NumField()
	for i := 0; i < count; i++ {
		f := t.Field(i)
		// empty PkgPath means exported field.
		// see https://golang.org/pkg/reflect/#StructField
		if f.PkgPath == "" {
			result.Add(f.Name)
		}
	}

	return result
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/arn"
	"github.com/aws/aws-sdk-go/service/athena"
	"github.com/hashicorp/terraform/helper/validation"

	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsAthenaWorkgroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsAthenaWorkgroupCreate,
		Read:   resourceAwsAthenaWorkgroupRead,
		Update: resourceAwsAthenaWorkgroupUpdate,
		Delete: resourceAwsAthenaWorkgroupDelete,

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"description": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"bytes_scanned_cutoff_per_query": {
				Type:         schema.TypeInt,
				Optional:     true,
				ValidateFunc: validation.IntAtLeast(10485760),
			},
			"enforce_workgroup_configuration": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"publish_cloudwatch_metrics_enabled": {
				Type:     schema.TypeBool,
				Optional: true,
			},
			"output_location": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"encryption_option": {
				Type:     schema.TypeString,
				Optional: true,
				ValidateFunc: validation.StringInSlice([]string{
					athena.EncryptionOptionCseKms,
					athena.EncryptionOptionSseKms,
					athena.EncryptionOptionSseS3,
				}, false),
			},
			"kms_key": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"tags": tagsSchema(),
		},
	}
}

func resourceAwsAthenaWorkgroupCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).athenaconn

	name := d.Get("name").(string)

	input := &athena.CreateWorkGroupInput{
		Name: aws.String(name),
	}

	basicConfig := false
	resultConfig := false
	encryptConfig := false

	if v, ok := d.GetOk("description"); ok {
		input.Description = aws.String(v.(string))
	}

	inputConfiguration := &athena.WorkGroupConfiguration{}

	if v, ok := d.GetOk("bytes_scanned_cutoff_per_query"); ok {
		basicConfig = true
		inputConfiguration.BytesScannedCutoffPerQuery = aws.Int64(int64(v.(int)))
	}

	if v, ok := d.GetOk("enforce_workgroup_configuration"); ok {
		basicConfig = true
		inputConfiguration.EnforceWorkGroupConfiguration = aws.Bool(v.(bool))
	}

	if v, ok := d.GetOk("publish_cloudwatch_metrics_enabled"); ok {
		basicConfig = true
		inputConfiguration.PublishCloudWatchMetricsEnabled = aws.Bool(v.(bool))
	}

	resultConfiguration := &athena.ResultConfiguration{}

	if v, ok := d.GetOk("output_location"); ok {
		resultConfig = true
		resultConfiguration.OutputLocation = aws.String(v.(string))
	}

	encryptionConfiguration := &athena.EncryptionConfiguration{}

	if v, ok := d.GetOk("encryption_option"); ok {
		resultConfig = true
		encryptConfig = true
		encryptionConfiguration.EncryptionOption = aws.String(v.(string))

		if v.(string) == athena.EncryptionOptionCseKms || v.(string) == athena.EncryptionOptionSseKms {
			if vkms, ok := d.GetOk("kms_key"); ok {
				encryptionConfiguration.KmsKey = aws.String(vkms.(string))
			} else {
				return fmt.Errorf("KMS Key required but not provided for encryption_option: %s", v.(string))
			}
		}
	}

	if basicConfig {
		input.Configuration = inputConfiguration
	}

	if resultConfig {
		input.Configuration.ResultConfiguration = resultConfiguration

		if encryptConfig {
			input.Configuration.ResultConfiguration.EncryptionConfiguration = encryptionConfiguration
		}
	}

	// Prevent the below error:
	// InvalidRequestException: Tags provided upon WorkGroup creation must not be empty
	if v := d.Get("tags").(map[string]interface{}); len(v) > 0 {
		input.Tags = tagsFromMapAthena(v)
	}

	_, err := conn.CreateWorkGroup(input)

	if err != nil {
		return fmt.Errorf("error creating Athena WorkGroup: %s", err)
	}

	d.SetId(name)

	return resourceAwsAthenaWorkgroupRead(d, meta)
}

func resourceAwsAthenaWorkgroupRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).athenaconn

	input := &athena.GetWorkGroupInput{
		WorkGroup: aws.String(d.Id()),
	}

	resp, err := conn.GetWorkGroup(input)

	if isAWSErr(err, athena.ErrCodeInvalidRequestException, "is not found") {
		log.Printf("[WARN] Athena WorkGroup (%s) not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}

	if err != nil {
		return fmt.Errorf("error reading Athena WorkGroup (%s): %s", d.Id(), err)
	}

	d.Set("name", *resp.WorkGroup.Name)

	if resp.WorkGroup.Description != nil {
		d.Set("description", *resp.WorkGroup.Description)
	}

	client := meta.(*AWSClient)

	arn := arn.ARN{
		Partition: client.partition,
		Region:    client.region,
		Service:   "athena",
		AccountID: client.accountid,
		Resource:  fmt.Sprintf("workgroup/%s", d.Id()),
	}

	d.Set("arn", arn.String())

	if resp.WorkGroup.Configuration != nil {
		if resp.WorkGroup.Configuration.BytesScannedCutoffPerQuery != nil {
			d.Set("bytes_scanned_cutoff_per_query", *resp.WorkGroup.Configuration.BytesScannedCutoffPerQuery)
		}

		if resp.WorkGroup.Configuration.EnforceWorkGroupConfiguration != nil {
			d.Set("enforce_workgroup_configuration", *resp.WorkGroup.Configuration.EnforceWorkGroupConfiguration)
		}

		if resp.WorkGroup.Configuration.PublishCloudWatchMetricsEnabled != nil {
			d.Set("publish_cloudwatch_metrics_enabled", *resp.WorkGroup.Configuration.PublishCloudWatchMetricsEnabled)
		}

		if resp.WorkGroup.Configuration.ResultConfiguration != nil {
			if resp.WorkGroup.Configuration.ResultConfiguration.OutputLocation != nil {
				d.Set("output_location", *resp.WorkGroup.Configuration.ResultConfiguration.OutputLocation)
			}

			if resp.WorkGroup.Configuration.ResultConfiguration.EncryptionConfiguration != nil {
				if resp.WorkGroup.Configuration.ResultConfiguration.EncryptionConfiguration.EncryptionOption != nil {
					d.Set("encryption_option", *resp.WorkGroup.Configuration.ResultConfiguration.EncryptionConfiguration.EncryptionOption)
				}

				if resp.WorkGroup.Configuration.ResultConfiguration.EncryptionConfiguration.KmsKey != nil {
					d.Set("kms_key", *resp.WorkGroup.Configuration.ResultConfiguration.EncryptionConfiguration.KmsKey)
				}
			}
		}
	}

	err = saveTagsAthena(conn, d, d.Get("arn").(string))

	if isAWSErr(err, athena.ErrCodeInvalidRequestException, "is not found") {
		log.Printf("[WARN] Athena WorkGroup (%s) not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}

	if err != nil {
		return fmt.Errorf("error setting tags: %s", err)
	}

	return nil
}

func resourceAwsAthenaWorkgroupDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).athenaconn

	input := &athena.DeleteWorkGroupInput{
		WorkGroup: aws.String(d.Id()),
	}

	_, err := conn.DeleteWorkGroup(input)

	if err != nil {
		return fmt.Errorf("error deleting Athena WorkGroup (%s): %s", d.Id(), err)
	}

	return nil
}

func resourceAwsAthenaWorkgroupUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).athenaconn

	workGroupUpdate := false
	resultConfigUpdate := false
	configUpdate := false
	encryptionUpdate := false
	removeEncryption := false

	input := &athena.UpdateWorkGroupInput{
		WorkGroup: aws.String(d.Get("name").(string)),
	}

	if d.HasChange("description") {
		workGroupUpdate = true
		input.Description = aws.String(d.Get("description").(string))
	}

	inputConfigurationUpdates := &athena.WorkGroupConfigurationUpdates{}

	if d.HasChange("bytes_scanned_cutoff_per_query") {
		workGroupUpdate = true
		configUpdate = true

		if v, ok := d.GetOk("bytes_scanned_cutoff_per_query"); ok {
			inputConfigurationUpdates.BytesScannedCutoffPerQuery = aws.Int64(int64(v.(int)))
		} else {
			inputConfigurationUpdates.RemoveBytesScannedCutoffPerQuery = aws.Bool(true)
		}
	}

	if d.HasChange("enforce_workgroup_configuration") {
		workGroupUpdate = true
		configUpdate = true

		v := d.Get("enforce_workgroup_configuration")
		inputConfigurationUpdates.EnforceWorkGroupConfiguration = aws.Bool(v.(bool))
	}

	if d.HasChange("publish_cloudwatch_metrics_enabled") {
		workGroupUpdate = true
		configUpdate = true

		v := d.Get("publish_cloudwatch_metrics_enabled")
		inputConfigurationUpdates.PublishCloudWatchMetricsEnabled = aws.Bool(v.(bool))
	}

	resultConfigurationUpdates := &athena.ResultConfigurationUpdates{}

	if d.HasChange("output_location") {
		workGroupUpdate = true
		configUpdate = true
		resultConfigUpdate = true

		if v, ok := d.GetOk("output_location"); ok {
			resultConfigurationUpdates.OutputLocation = aws.String(v.(string))
		} else {
			resultConfigurationUpdates.RemoveOutputLocation = aws.Bool(true)
		}
	}

	encryptionConfiguration := &athena.EncryptionConfiguration{}

	if d.HasChange("encryption_option") {
		workGroupUpdate = true
		configUpdate = true
		resultConfigUpdate = true
		encryptionUpdate = true

		if v, ok := d.GetOk("encryption_option"); ok {
			encryptionConfiguration.EncryptionOption = aws.String(v.(string))

			if v.(string) == athena.EncryptionOptionCseKms || v.(string) == athena.EncryptionOptionSseKms {
				if vkms, ok := d.GetOk("kms_key"); ok {
					encryptionConfiguration.KmsKey = aws.String(vkms.(string))
				} else {
					return fmt.Errorf("KMS Key required but not provided for encryption_option: %s", v.(string))
				}
			}
		} else {
			removeEncryption = true
			resultConfigurationUpdates.RemoveEncryptionConfiguration = aws.Bool(true)
		}
	}

	if workGroupUpdate {
		if configUpdate {
			input.ConfigurationUpdates = inputConfigurationUpdates
		}

		if resultConfigUpdate {
			input.ConfigurationUpdates.ResultConfigurationUpdates = resultConfigurationUpdates

			if encryptionUpdate && !removeEncryption {
				input.ConfigurationUpdates.ResultConfigurationUpdates.EncryptionConfiguration = encryptionConfiguration
			}
		}

		_, err := conn.UpdateWorkGroup(input)

		if err != nil {
			return fmt.Errorf("error updating Athena WorkGroup (%s): %s", d.Id(), err)
		}
	}

	if d.HasChange("tags") {
		err := setTagsAthena(conn, d, d.Get("arn").(string))

		if err != nil {
			return fmt.Errorf("error updating tags: %s", err)
		}
	}

	return resourceAwsAthenaWorkgroupRead(d, meta)
}
<commit_msg>resource/aws_athena_workgroup: Pass pointers directly to ResourceData.Set()<commit_after>package aws

import (
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/arn"
	"github.com/aws/aws-sdk-go/service/athena"
	"github.com/hashicorp/terraform/helper/validation"

	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsAthenaWorkgroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsAthenaWorkgroupCreate,
		Read:   resourceAwsAthenaWorkgroupRead,
		Update: resourceAwsAthenaWorkgroupUpdate,
		Delete: resourceAwsAthenaWorkgroupDelete,

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"description": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"bytes_scanned_cutoff_per_query": {
				Type:         schema.TypeInt,
				Optional:     true,
				ValidateFunc: validation.IntAtLeast(10485760),
			},
			"enforce_workgroup_configuration": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"publish_cloudwatch_metrics_enabled": {
				Type:     schema.TypeBool,
				Optional: true,
			},
			"output_location": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"encryption_option": {
				Type:     schema.TypeString,
				Optional: true,
				ValidateFunc: validation.StringInSlice([]string{
					athena.EncryptionOptionCseKms,
					athena.EncryptionOptionSseKms,
					athena.EncryptionOptionSseS3,
				}, false),
			},
			"kms_key": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"tags": tagsSchema(),
		},
	}
}

func resourceAwsAthenaWorkgroupCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).athenaconn

	name := d.Get("name").(string)

	input := &athena.CreateWorkGroupInput{
		Name: aws.String(name),
	}

	basicConfig := false
	resultConfig := false
	encryptConfig := false

	if v, ok := d.GetOk("description"); ok {
		input.Description = aws.String(v.(string))
	}

	inputConfiguration := &athena.WorkGroupConfiguration{}

	if v, ok := d.GetOk("bytes_scanned_cutoff_per_query"); ok {
		basicConfig = true
		inputConfiguration.BytesScannedCutoffPerQuery = aws.Int64(int64(v.(int)))
	}

	if v, ok := d.GetOk("enforce_workgroup_configuration"); ok {
		basicConfig = true
		inputConfiguration.EnforceWorkGroupConfiguration = aws.Bool(v.(bool))
	}

	if v, ok := d.GetOk("publish_cloudwatch_metrics_enabled"); ok {
		basicConfig = true
		inputConfiguration.PublishCloudWatchMetricsEnabled = aws.Bool(v.(bool))
	}

	resultConfiguration := &athena.ResultConfiguration{}

	if v, ok := d.GetOk("output_location"); ok {
		resultConfig = true
		resultConfiguration.OutputLocation = aws.String(v.(string))
	}

	encryptionConfiguration := &athena.EncryptionConfiguration{}

	if v, ok := d.GetOk("encryption_option"); ok {
		resultConfig = true
		encryptConfig = true
		encryptionConfiguration.EncryptionOption = aws.String(v.(string))

		if v.(string) == athena.EncryptionOptionCseKms || v.(string) == athena.EncryptionOptionSseKms {
			if vkms, ok := d.GetOk("kms_key"); ok {
				encryptionConfiguration.KmsKey = aws.String(vkms.(string))
			} else {
				return fmt.Errorf("KMS Key required but not provided for encryption_option: %s", v.(string))
			}
		}
	}

	if basicConfig {
		input.Configuration = inputConfiguration
	}

	if resultConfig {
		input.Configuration.ResultConfiguration = resultConfiguration

		if encryptConfig {
			input.Configuration.ResultConfiguration.EncryptionConfiguration = encryptionConfiguration
		}
	}

	// Prevent the below error:
	// InvalidRequestException: Tags provided upon WorkGroup creation must not be empty
	if v := d.Get("tags").(map[string]interface{}); len(v) > 0 {
		input.Tags = tagsFromMapAthena(v)
	}

	_, err := conn.CreateWorkGroup(input)

	if err != nil {
		return fmt.Errorf("error creating Athena WorkGroup: %s", err)
	}

	d.SetId(name)

	return resourceAwsAthenaWorkgroupRead(d, meta)
}

func resourceAwsAthenaWorkgroupRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).athenaconn

	input := &athena.GetWorkGroupInput{
		WorkGroup: aws.String(d.Id()),
	}

	resp, err := conn.GetWorkGroup(input)

	if isAWSErr(err, athena.ErrCodeInvalidRequestException, "is not found") {
		log.Printf("[WARN] Athena WorkGroup (%s) not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}

	if err != nil {
		return fmt.Errorf("error reading Athena WorkGroup (%s): %s", d.Id(), err)
	}

	arn := arn.ARN{
		Partition: meta.(*AWSClient).partition,
		Region:    meta.(*AWSClient).region,
		Service:   "athena",
		AccountID: meta.(*AWSClient).accountid,
		Resource:  fmt.Sprintf("workgroup/%s", d.Id()),
	}

	d.Set("arn", arn.String())
	d.Set("description", resp.WorkGroup.Description)
	d.Set("name", resp.WorkGroup.Name)

	if resp.WorkGroup.Configuration != nil {
		d.Set("bytes_scanned_cutoff_per_query", resp.WorkGroup.Configuration.BytesScannedCutoffPerQuery)
		d.Set("enforce_workgroup_configuration", resp.WorkGroup.Configuration.EnforceWorkGroupConfiguration)
		d.Set("publish_cloudwatch_metrics_enabled", resp.WorkGroup.Configuration.PublishCloudWatchMetricsEnabled)

		if resp.WorkGroup.Configuration.ResultConfiguration != nil {
			d.Set("output_location", resp.WorkGroup.Configuration.ResultConfiguration.OutputLocation)

			if resp.WorkGroup.Configuration.ResultConfiguration.EncryptionConfiguration != nil {
				d.Set("encryption_option", resp.WorkGroup.Configuration.ResultConfiguration.EncryptionConfiguration.EncryptionOption)
				d.Set("kms_key", resp.WorkGroup.Configuration.ResultConfiguration.EncryptionConfiguration.KmsKey)
			}
		}
	}

	err = saveTagsAthena(conn, d, d.Get("arn").(string))

	if isAWSErr(err, athena.ErrCodeInvalidRequestException, "is not found") {
		log.Printf("[WARN] Athena WorkGroup (%s) not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}

	if err != nil {
		return fmt.Errorf("error setting tags: %s", err)
	}

	return nil
}

func resourceAwsAthenaWorkgroupDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).athenaconn

	input := &athena.DeleteWorkGroupInput{
		WorkGroup: aws.String(d.Id()),
	}

	_, err := conn.DeleteWorkGroup(input)

	if err != nil {
		return fmt.Errorf("error deleting Athena WorkGroup (%s): %s", d.Id(), err)
	}

	return nil
}

func resourceAwsAthenaWorkgroupUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).athenaconn

	workGroupUpdate := false
	resultConfigUpdate := false
	configUpdate := false
	encryptionUpdate := false
	removeEncryption := false

	input := &athena.UpdateWorkGroupInput{
		WorkGroup: aws.String(d.Get("name").(string)),
	}

	if d.HasChange("description") {
		workGroupUpdate = true
		input.Description = aws.String(d.Get("description").(string))
	}

	inputConfigurationUpdates := &athena.WorkGroupConfigurationUpdates{}

	if d.HasChange("bytes_scanned_cutoff_per_query") {
		workGroupUpdate = true
		configUpdate = true

		if v, ok := d.GetOk("bytes_scanned_cutoff_per_query"); ok {
			inputConfigurationUpdates.BytesScannedCutoffPerQuery = aws.Int64(int64(v.(int)))
		} else {
			inputConfigurationUpdates.RemoveBytesScannedCutoffPerQuery = aws.Bool(true)
		}
	}

	if d.HasChange("enforce_workgroup_configuration") {
		workGroupUpdate = true
		configUpdate = true

		v := d.Get("enforce_workgroup_configuration")
		inputConfigurationUpdates.EnforceWorkGroupConfiguration = aws.Bool(v.(bool))
	}

	if d.HasChange("publish_cloudwatch_metrics_enabled") {
		workGroupUpdate = true
		configUpdate = true

		v := d.Get("publish_cloudwatch_metrics_enabled")
		inputConfigurationUpdates.PublishCloudWatchMetricsEnabled = aws.Bool(v.(bool))
	}

	resultConfigurationUpdates := &athena.ResultConfigurationUpdates{}

	if d.HasChange("output_location") {
		workGroupUpdate = true
		configUpdate = true
		resultConfigUpdate = true

		if v, ok := d.GetOk("output_location"); ok {
			resultConfigurationUpdates.OutputLocation = aws.String(v.(string))
		} else {
			resultConfigurationUpdates.RemoveOutputLocation = aws.Bool(true)
		}
	}

	encryptionConfiguration := &athena.EncryptionConfiguration{}

	if d.HasChange("encryption_option") {
		workGroupUpdate = true
		configUpdate = true
		resultConfigUpdate = true
		encryptionUpdate = true

		if v, ok := d.GetOk("encryption_option"); ok {
			encryptionConfiguration.EncryptionOption = aws.String(v.(string))

			if v.(string) == athena.EncryptionOptionCseKms || v.(string) == athena.EncryptionOptionSseKms {
				if vkms, ok := d.GetOk("kms_key"); ok {
					encryptionConfiguration.KmsKey = aws.String(vkms.(string))
				} else {
					return fmt.Errorf("KMS Key required but not provided for encryption_option: %s", v.(string))
				}
			}
		} else {
			removeEncryption = true
			resultConfigurationUpdates.RemoveEncryptionConfiguration = aws.Bool(true)
		}
	}

	if workGroupUpdate {
		if configUpdate {
			input.ConfigurationUpdates = inputConfigurationUpdates
		}

		if resultConfigUpdate {
			input.ConfigurationUpdates.ResultConfigurationUpdates = resultConfigurationUpdates

			if encryptionUpdate && !removeEncryption {
				input.ConfigurationUpdates.ResultConfigurationUpdates.EncryptionConfiguration = encryptionConfiguration
			}
		}

		_, err := conn.UpdateWorkGroup(input)

		if err != nil {
			return fmt.Errorf("error updating Athena WorkGroup (%s): %s", d.Id(), err)
		}
	}

	if d.HasChange("tags") {
		err := setTagsAthena(conn, d, d.Get("arn").(string))

		if err != nil {
			return fmt.Errorf("error updating tags: %s", err)
		}
	}

	return resourceAwsAthenaWorkgroupRead(d, meta)
}
<|endoftext|>