<commit_before>// Copyright 2016 Joel Scoble and The JoeFriday 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 cpuutil handles processing of CPU utilization information. This
// information is calculated using the differences of two CPU stats snapshots,
// /proc/stat and represented as a percentage.
package cpuutil

import (
	"io"
	"sync"
	"time"

	"github.com/SermoDigital/helpers"
	joe "github.com/mohae/joefriday"
	stats "github.com/mohae/joefriday/cpu/cpustats"
)

// Utilization holds information about cpu utilization.
type Utilization struct {
	Timestamp int64 `json:"timestamp"`
	// the time since the prior snapshot; the window that the utilization covers.
	TimeDelta int64 `json:"time_delta"`
	// time since last reboot, in seconds
	BTimeDelta int32 `json:"btime_delta"`
	// context switches since last snapshot
	CtxtDelta int64 `json:"ctxt_delta"`
	// current number of Processes
	Processes int32 `json:"processes"`
	// cpu specific utilization information
	CPU []Util `json:"cpu"`
}

// Util holds utilization information, as percentages, for a CPU.
type Util struct {
	ID     string  `json:"id"`
	Usage  float32 `json:"usage"`
	User   float32 `json:"user"`
	Nice   float32 `json:"nice"`
	System float32 `json:"system"`
	Idle   float32 `json:"idle"`
	IOWait float32 `json:"io_wait"`
}

// Profiler is used to process the /proc/stats file and calculate Utilization
// information.
type Profiler struct {
	*stats.Profiler
	prior stats.Stats
}

// Returns an initialized Profiler; ready to use.
func NewProfiler() (prof *Profiler, err error) {
	p, err := stats.NewProfiler()
	if err != nil {
		return nil, err
	}
	s, err := p.Get()
	if err != nil {
		return nil, err
	}
	return &Profiler{Profiler: p, prior: *s}, nil
}

// Get returns the cpu utilization.  Utilization calculations requires two
// pieces of data.  This func gets a snapshot of /proc/stat, sleeps for a
// second, takes another snapshot and calcualtes the utilization from the
// two snapshots.  If ongoing utilitzation information is desired, the
// Ticker should be used; it's better suited for ongoing utilization
// information.
func (prof *Profiler) Get() (u *Utilization, err error) {
	stat, err := prof.Profiler.Get()
	if err != nil {
		return nil, err
	}
	u = prof.calculateUtilization(stat)
	prof.prior = *stat
	return u, nil
}

var std *Profiler
var stdMu sync.Mutex

// Get returns the current cpu utilization using the package's global
// Profiler.  The Profiler instantiated lazily; if it doesn't already exist,
// the first Utilization received may be inaccurate.
func Get() (*Utilization, error) {
	stdMu.Lock()
	defer stdMu.Unlock()
	if std == nil {
		var err error
		std, err = NewProfiler()
		if err != nil {
			return nil, err
		}
	}
	return std.Get()
}

// utilizaton =
//   ()(Δuser + Δnice + Δsystem)/(Δuser+Δnice+Δsystem+Δidle)) * CLK_TCK
func (prof *Profiler) calculateUtilization(cur *stats.Stats) *Utilization {
	u := &Utilization{
		Timestamp:  cur.Timestamp,
		TimeDelta:  cur.Timestamp - prof.prior.Timestamp,
		BTimeDelta: int32(cur.Timestamp/1000000000 - cur.BTime),
		CtxtDelta:  cur.Ctxt - prof.prior.Ctxt,
		Processes:  int32(cur.Processes),
		CPU:        make([]Util, len(cur.CPU)),
	}
	var dUser, dNice, dSys, dIdle, tot float32
	// Rest of the calculations are per core
	for i := 0; i < len(cur.CPU); i++ {
		v := Util{ID: cur.CPU[i].ID}
		dUser = float32(cur.CPU[i].User - prof.prior.CPU[i].User)
		dNice = float32(cur.CPU[i].Nice - prof.prior.CPU[i].Nice)
		dSys = float32(cur.CPU[i].System - prof.prior.CPU[i].System)
		dIdle = float32(cur.CPU[i].Idle - prof.prior.CPU[i].Idle)
		tot = dUser + dNice + dSys + dIdle
		v.Usage = (dUser + dNice + dSys) / tot * float32(cur.ClkTck)
		v.User = dUser / tot * float32(cur.ClkTck)
		v.Nice = dNice / tot * float32(cur.ClkTck)
		v.System = dSys / tot * float32(cur.ClkTck)
		v.Idle = dIdle / tot * float32(cur.ClkTck)
		v.IOWait = float32(cur.CPU[i].IOWait-prof.prior.CPU[i].IOWait) / tot * float32(cur.ClkTck)
		u.CPU[i] = v
	}
	return u
}

// Ticker delivers the system's memory information at intervals.
type Ticker struct {
	*joe.Ticker
	Data chan *Utilization
	*Profiler
}

// NewTicker returns a new Ticker continaing a Data channel that delivers
// the data at intervals and an error channel that delivers any errors
// encountered.  Stop the ticker to signal the ticker to stop running; it
// does not close the Data channel.  Close the ticker to close all ticker
// channels.
func NewTicker(d time.Duration) (joe.Tocker, error) {
	p, err := NewProfiler()
	if err != nil {
		return nil, err
	}
	t := Ticker{Ticker: joe.NewTicker(d), Data: make(chan *Utilization), Profiler: p}
	go t.Run()
	return &t, nil
}

// Run runs the ticker.
func (t *Ticker) Run() {
	// predeclare some vars
	var (
		i, j, pos, fieldNum int
		n                   uint64
		v                   byte
		stop                bool
		err                 error
		cur                 stats.Stats
		stat                stats.Stat
	)
	for {
		select {
		case <-t.Done:
			return
		case <-t.C:
			cur.Timestamp = time.Now().UTC().UnixNano()
			err = t.Reset()
			if err != nil {
				t.Errs <- err
				break
			}
			cur.CPU = cur.CPU[:0]
			// read each line until eof
			for {
				t.Line, err = t.Buf.ReadSlice('\n')
				if err != nil {
					if err == io.EOF {
						break
					}
					t.Errs <- &joe.ReadError{Err: err}
					break
				}
				// Get everything up to the first space, this is the key.  Not all keys are processed.
				for i, v = range t.Line {
					if v == 0x20 {
						t.Val = t.Line[:i]
						pos = i + 1
						break
					}
				}
				// skip the intr line
				if t.Val[0] == 'i' {
					continue
				}
				if t.Val[0] == 'c' {
					if t.Val[1] == 'p' { // process CPU
						stat.ID = string(t.Val[:])
						j = 0
						// skip over any remaining spaces
						for i, v = range t.Line[pos:] {
							if v != 0x20 {
								break
							}
							j++
						}
						fieldNum = 0
						pos, j = j+pos, j+pos
						// space is the field separator
						for i, v = range t.Line[pos:] {
							if v == '\n' {
								stop = true
							}
							if v == 0x20 || stop {
								fieldNum++
								n, err = helpers.ParseUint(t.Line[j : pos+i])
								if err != nil {
									t.Errs <- &joe.ParseError{Info: string(t.Val[:]), Err: err}
									continue
								}
								j = pos + i + 1
								if fieldNum < 6 {
									if fieldNum < 4 {
										if fieldNum == 1 {
											stat.User = int64(n)
											continue
										}
										if fieldNum == 2 {
											stat.Nice = int64(n)
											continue
										}
										stat.System = int64(n) // 3
										continue
									}
									if fieldNum == 4 {
										stat.Idle = int64(n)
										continue
									}
									stat.IOWait = int64(n) // 5
									continue
								}
								if fieldNum < 8 {
									if fieldNum == 6 {
										stat.IRQ = int64(n)
										continue
									}
									stat.SoftIRQ = int64(n) // 7
									continue
								}
								if fieldNum == 8 {
									stat.Steal = int64(n)
									continue
								}
								if fieldNum == 9 {
									stat.Quest = int64(n)
									continue
								}
								stat.QuestNice = int64(n) // 10
							}
						}
						cur.CPU = append(cur.CPU, stat)
						stop = false
						continue
					}
					// Otherwise it's ctxt info; rest of the line is the data.
					n, err = helpers.ParseUint(t.Line[pos : len(t.Line)-1])
					if err != nil {
						t.Errs <- &joe.ParseError{Info: string(t.Val[:]), Err: err}
						continue
					}
					cur.Ctxt = int64(n)
					continue
				}
				if t.Val[0] == 'b' {
					// rest of the line is the data
					n, err = helpers.ParseUint(t.Line[pos : len(t.Line)-1])
					if err != nil {
						t.Errs <- &joe.ParseError{Info: string(t.Val[:]), Err: err}
						continue
					}
					cur.BTime = int64(n)
					continue
				}
				if t.Val[0] == 'p' && t.Val[4] == 'e' { // processes info
					// rest of the line is the data
					n, err = helpers.ParseUint(t.Line[pos : len(t.Line)-1])
					if err != nil {
						t.Errs <- &joe.ParseError{Info: string(t.Val[:]), Err: err}
						continue
					}
					cur.Processes = int64(n)
					continue
				}
			}
			t.Data <- t.Profiler.calculateUtilization(&cur)
			t.Profiler.prior.Ctxt = cur.Ctxt
			t.Profiler.prior.BTime = cur.BTime
			t.Profiler.prior.Processes = cur.Processes
			if len(t.Profiler.prior.CPU) != len(cur.CPU) {
				t.Profiler.prior.CPU = make([]stats.Stat, len(cur.CPU))
			}
			copy(t.Profiler.prior.CPU, cur.CPU)
		}
	}
}

// Close closes the ticker resources.
func (t *Ticker) Close() {
	t.Ticker.Close()
	close(t.Data)
}
<commit_msg>fix cpuutil: use CPU instead of obsolete/non-existent Stat<commit_after>// Copyright 2016 Joel Scoble and The JoeFriday 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 cpuutil handles processing of CPU utilization information. This
// information is calculated using the differences of two CPU stats snapshots,
// /proc/stat and represented as a percentage.
package cpuutil

import (
	"io"
	"sync"
	"time"

	"github.com/SermoDigital/helpers"
	joe "github.com/mohae/joefriday"
	stats "github.com/mohae/joefriday/cpu/cpustats"
)

// Utilization holds information about cpu utilization.
type Utilization struct {
	Timestamp int64 `json:"timestamp"`
	// the time since the prior snapshot; the window that the utilization covers.
	TimeDelta int64 `json:"time_delta"`
	// time since last reboot, in seconds
	BTimeDelta int32 `json:"btime_delta"`
	// context switches since last snapshot
	CtxtDelta int64 `json:"ctxt_delta"`
	// current number of Processes
	Processes int32 `json:"processes"`
	// cpu specific utilization information
	CPU []Util `json:"cpu"`
}

// Util holds utilization information, as percentages, for a CPU.
type Util struct {
	ID     string  `json:"id"`
	Usage  float32 `json:"usage"`
	User   float32 `json:"user"`
	Nice   float32 `json:"nice"`
	System float32 `json:"system"`
	Idle   float32 `json:"idle"`
	IOWait float32 `json:"io_wait"`
}

// Profiler is used to process the /proc/stats file and calculate Utilization
// information.
type Profiler struct {
	*stats.Profiler
	prior stats.Stats
}

// Returns an initialized Profiler; ready to use.
func NewProfiler() (prof *Profiler, err error) {
	p, err := stats.NewProfiler()
	if err != nil {
		return nil, err
	}
	s, err := p.Get()
	if err != nil {
		return nil, err
	}
	return &Profiler{Profiler: p, prior: *s}, nil
}

// Get returns the cpu utilization.  Utilization calculations requires two
// pieces of data.  This func gets a snapshot of /proc/stat, sleeps for a
// second, takes another snapshot and calcualtes the utilization from the
// two snapshots.  If ongoing utilitzation information is desired, the
// Ticker should be used; it's better suited for ongoing utilization
// information.
func (prof *Profiler) Get() (u *Utilization, err error) {
	stat, err := prof.Profiler.Get()
	if err != nil {
		return nil, err
	}
	u = prof.calculateUtilization(stat)
	prof.prior = *stat
	return u, nil
}

var std *Profiler
var stdMu sync.Mutex

// Get returns the current cpu utilization using the package's global
// Profiler.  The Profiler instantiated lazily; if it doesn't already exist,
// the first Utilization received may be inaccurate.
func Get() (*Utilization, error) {
	stdMu.Lock()
	defer stdMu.Unlock()
	if std == nil {
		var err error
		std, err = NewProfiler()
		if err != nil {
			return nil, err
		}
	}
	return std.Get()
}

// utilizaton =
//   ()(Δuser + Δnice + Δsystem)/(Δuser+Δnice+Δsystem+Δidle)) * CLK_TCK
func (prof *Profiler) calculateUtilization(cur *stats.Stats) *Utilization {
	u := &Utilization{
		Timestamp:  cur.Timestamp,
		TimeDelta:  cur.Timestamp - prof.prior.Timestamp,
		BTimeDelta: int32(cur.Timestamp/1000000000 - cur.BTime),
		CtxtDelta:  cur.Ctxt - prof.prior.Ctxt,
		Processes:  int32(cur.Processes),
		CPU:        make([]Util, len(cur.CPU)),
	}
	var dUser, dNice, dSys, dIdle, tot float32
	// Rest of the calculations are per core
	for i := 0; i < len(cur.CPU); i++ {
		v := Util{ID: cur.CPU[i].ID}
		dUser = float32(cur.CPU[i].User - prof.prior.CPU[i].User)
		dNice = float32(cur.CPU[i].Nice - prof.prior.CPU[i].Nice)
		dSys = float32(cur.CPU[i].System - prof.prior.CPU[i].System)
		dIdle = float32(cur.CPU[i].Idle - prof.prior.CPU[i].Idle)
		tot = dUser + dNice + dSys + dIdle
		v.Usage = (dUser + dNice + dSys) / tot * float32(cur.ClkTck)
		v.User = dUser / tot * float32(cur.ClkTck)
		v.Nice = dNice / tot * float32(cur.ClkTck)
		v.System = dSys / tot * float32(cur.ClkTck)
		v.Idle = dIdle / tot * float32(cur.ClkTck)
		v.IOWait = float32(cur.CPU[i].IOWait-prof.prior.CPU[i].IOWait) / tot * float32(cur.ClkTck)
		u.CPU[i] = v
	}
	return u
}

// Ticker delivers the system's memory information at intervals.
type Ticker struct {
	*joe.Ticker
	Data chan *Utilization
	*Profiler
}

// NewTicker returns a new Ticker continaing a Data channel that delivers
// the data at intervals and an error channel that delivers any errors
// encountered.  Stop the ticker to signal the ticker to stop running; it
// does not close the Data channel.  Close the ticker to close all ticker
// channels.
func NewTicker(d time.Duration) (joe.Tocker, error) {
	p, err := NewProfiler()
	if err != nil {
		return nil, err
	}
	t := Ticker{Ticker: joe.NewTicker(d), Data: make(chan *Utilization), Profiler: p}
	go t.Run()
	return &t, nil
}

// Run runs the ticker.
func (t *Ticker) Run() {
	// predeclare some vars
	var (
		i, j, pos, fieldNum int
		n                   uint64
		v                   byte
		stop                bool
		err                 error
		cur                 stats.Stats
		cpu                stats.CPU
	)
	for {
		select {
		case <-t.Done:
			return
		case <-t.C:
			cur.Timestamp = time.Now().UTC().UnixNano()
			err = t.Reset()
			if err != nil {
				t.Errs <- err
				break
			}
			cur.CPU = cur.CPU[:0]
			// read each line until eof
			for {
				t.Line, err = t.Buf.ReadSlice('\n')
				if err != nil {
					if err == io.EOF {
						break
					}
					t.Errs <- &joe.ReadError{Err: err}
					break
				}
				// Get everything up to the first space, this is the key.  Not all keys are processed.
				for i, v = range t.Line {
					if v == 0x20 {
						t.Val = t.Line[:i]
						pos = i + 1
						break
					}
				}
				// skip the intr line
				if t.Val[0] == 'i' {
					continue
				}
				if t.Val[0] == 'c' {
					if t.Val[1] == 'p' { // process CPU
						cpu.ID = string(t.Val[:])
						j = 0
						// skip over any remaining spaces
						for i, v = range t.Line[pos:] {
							if v != 0x20 {
								break
							}
							j++
						}
						fieldNum = 0
						pos, j = j+pos, j+pos
						// space is the field separator
						for i, v = range t.Line[pos:] {
							if v == '\n' {
								stop = true
							}
							if v == 0x20 || stop {
								fieldNum++
								n, err = helpers.ParseUint(t.Line[j : pos+i])
								if err != nil {
									t.Errs <- &joe.ParseError{Info: string(t.Val[:]), Err: err}
									continue
								}
								j = pos + i + 1
								if fieldNum < 6 {
									if fieldNum < 4 {
										if fieldNum == 1 {
											cpu.User = int64(n)
											continue
										}
										if fieldNum == 2 {
											cpu.Nice = int64(n)
											continue
										}
										cpu.System = int64(n) // 3
										continue
									}
									if fieldNum == 4 {
										cpu.Idle = int64(n)
										continue
									}
									cpu.IOWait = int64(n) // 5
									continue
								}
								if fieldNum < 8 {
									if fieldNum == 6 {
										cpu.IRQ = int64(n)
										continue
									}
									cpu.SoftIRQ = int64(n) // 7
									continue
								}
								if fieldNum == 8 {
									cpu.Steal = int64(n)
									continue
								}
								if fieldNum == 9 {
									cpu.Quest = int64(n)
									continue
								}
								cpu.QuestNice = int64(n) // 10
							}
						}
						cur.CPU = append(cur.CPU, cpu)
						stop = false
						continue
					}
					// Otherwise it's ctxt info; rest of the line is the data.
					n, err = helpers.ParseUint(t.Line[pos : len(t.Line)-1])
					if err != nil {
						t.Errs <- &joe.ParseError{Info: string(t.Val[:]), Err: err}
						continue
					}
					cur.Ctxt = int64(n)
					continue
				}
				if t.Val[0] == 'b' {
					// rest of the line is the data
					n, err = helpers.ParseUint(t.Line[pos : len(t.Line)-1])
					if err != nil {
						t.Errs <- &joe.ParseError{Info: string(t.Val[:]), Err: err}
						continue
					}
					cur.BTime = int64(n)
					continue
				}
				if t.Val[0] == 'p' && t.Val[4] == 'e' { // processes info
					// rest of the line is the data
					n, err = helpers.ParseUint(t.Line[pos : len(t.Line)-1])
					if err != nil {
						t.Errs <- &joe.ParseError{Info: string(t.Val[:]), Err: err}
						continue
					}
					cur.Processes = int64(n)
					continue
				}
			}
			t.Data <- t.Profiler.calculateUtilization(&cur)
			t.Profiler.prior.Ctxt = cur.Ctxt
			t.Profiler.prior.BTime = cur.BTime
			t.Profiler.prior.Processes = cur.Processes
			if len(t.Profiler.prior.CPU) != len(cur.CPU) {
				t.Profiler.prior.CPU = make([]stats.CPU, len(cur.CPU))
			}
			copy(t.Profiler.prior.CPU, cur.CPU)
		}
	}
}

// Close closes the ticker resources.
func (t *Ticker) Close() {
	t.Ticker.Close()
	close(t.Data)
}
<|endoftext|>


<commit_before>package exec

import (
	"database/sql/driver"
	"fmt"
	"strings"

	u "github.com/araddon/gou"

	"github.com/araddon/qlbridge/datasource"
	"github.com/araddon/qlbridge/datasource/membtree"
	"github.com/araddon/qlbridge/expr"
	"github.com/araddon/qlbridge/plan"
	"github.com/araddon/qlbridge/schema"
	"github.com/araddon/qlbridge/value"
)

var (
	_ = u.EMPTY
)

func FieldsAsMessages(tbl *schema.Table) []datasource.Message {
	msgs := make([]datasource.Message, len(tbl.Fields))
	for i, f := range tbl.Fields {
		msgs[i] = f
	}
	return msgs
}

// Describe A table
//
func DescribeTable(tbl *schema.Table, full bool) (datasource.Scanner, *expr.Projection) {
	if len(tbl.Fields) == 0 {
		u.Warnf("NO Fields!!!!! for %s p=%p", tbl.Name, tbl)
	}
	proj := expr.NewProjection()
	if full {
		for _, f := range schema.DescribeFullHeaders {
			proj.AddColumnShort(string(f.Name), f.Type)
		}
	} else {
		for _, f := range schema.DescribeHeaders {
			proj.AddColumnShort(string(f.Name), f.Type)
		}
	}
	//u.Warnf("columns?  %#v   \nvals?%v", proj, tbl.FieldMap)
	//tbl.FieldsAsMessages()
	ss := datasource.NewStaticSource(tbl.Name, tbl.Columns(), FieldsAsMessages(tbl))
	//tableVals := membtree.NewStaticDataSource("describetable", 0, tbl.DescribeValues, schema.DescribeFullCols)
	return ss, proj
}

func ShowTables(ctx *plan.Context) (*membtree.StaticDataSource, *expr.Projection) {

	/*
		mysql> show full tables from temp like '%';
		+--------------------+------------+
		| Tables_in_temp (%) | Table_type |
		+--------------------+------------+
		| emails             | BASE TABLE |
		| events             | BASE TABLE |
		| evtnames           | BASE TABLE |
		| username           | BASE TABLE |
		+--------------------+------------+
		5 rows in set (0.00 sec)

		mysql> show tables;
		+----------------+
		| Tables_in_temp |
		+----------------+
		| emails         |
		| events         |
		| evtnames       |
		| username       |
		+----------------+
		5 rows in set (0.00 sec)

		mysql> show tables from temp like '%';
		+--------------------+
		| Tables_in_temp (%) |
		+--------------------+
		| emails             |
		| events             |
		| evtnames           |
		| username           |
		+--------------------+
		5 rows in set (0.00 sec)

	*/
	tables := ctx.Schema.Tables()
	vals := make([][]driver.Value, len(tables))
	idx := 0
	if len(tables) == 0 {
		u.Warnf("NO TABLES!!!!! for %+v", ctx.Schema)
	}
	for _, tbl := range tables {
		vals[idx] = []driver.Value{tbl}
		//u.Infof("found table: %v   vals=%v", tbl, vals[idx])
		idx++
	}
	showTableVals := membtree.NewStaticDataSource("schematables", 0, vals, []string{"Table"})
	proj := expr.NewProjection()
	proj.AddColumnShort("Table", value.StringType)
	//u.Infof("showtables:  %v", m.showTableVals)
	return showTableVals, proj
}

func ShowVariables(name string, val driver.Value) (*membtree.StaticDataSource, *expr.Projection) {
	/*
	   MariaDB [(none)]> SHOW SESSION VARIABLES LIKE 'lower_case_table_names';
	   +------------------------+-------+
	   | Variable_name          | Value |
	   +------------------------+-------+
	   | lower_case_table_names | 0     |
	   +------------------------+-------+
	*/
	vals := make([][]driver.Value, 1)
	vals[0] = []driver.Value{name, val}
	dataSource := membtree.NewStaticDataSource("schematables", 0, vals, []string{"Variable_name", "Value"})
	p := expr.NewProjection()
	p.AddColumnShort("Variable_name", value.StringType)
	p.AddColumnShort("Value", value.StringType)
	return dataSource, p
}

func (m *JobBuilder) emptyTask(name string) (TaskRunner, expr.VisitStatus, error) {
	source := membtree.NewStaticDataSource(name, 0, nil, []string{name})
	proj := expr.NewProjection()
	proj.AddColumnShort(name, value.StringType)
	m.Ctx.Projection = plan.NewProjectionStatic(proj)
	tasks := make(Tasks, 0)
	sourceTask := NewSource(m.Ctx, nil, source)
	tasks.Add(sourceTask)
	return NewSequential(m.Ctx, name, tasks), expr.VisitContinue, nil
}

func (m *JobBuilder) VisitShow(stmt *expr.SqlShow) (expr.Task, expr.VisitStatus, error) {
	//u.Debugf("exec.VisitShow create?%v  identity=%q  raw=%s", stmt.Create, stmt.Identity, stmt.Raw)

	raw := strings.ToLower(stmt.Raw)
	switch {
	case stmt.Create && strings.ToLower(stmt.CreateWhat) == "table":
		// SHOW CREATE TABLE
		tbl, _ := m.Ctx.Schema.Table(stmt.Identity)
		if tbl == nil {
			u.Warnf("no table? %q", stmt.Identity)
			return nil, expr.VisitError, fmt.Errorf("No table found for %q", stmt.Identity)
		}
		rows := make([][]driver.Value, 1)
		rows[0] = []driver.Value{stmt.Identity, tbl}
		source := membtree.NewStaticDataSource("tables", 0, rows, []string{"Table", "Create Table"})
		proj := expr.NewProjection()
		proj.AddColumnShort("Table", value.StringType)
		proj.AddColumnShort("Create Table", value.StringType)
		m.Ctx.Projection = plan.NewProjectionStatic(proj)
		tasks := make(Tasks, 0)
		sourceTask := NewSource(m.Ctx, nil, source)
		u.Infof("source rowct=%d  %#v", rows, source)
		tasks.Add(sourceTask)
		return NewSequential(m.Ctx, "show-tables", tasks), expr.VisitContinue, nil

	case stmt.ShowType == "columns":
		// SHOW FULL COLUMNS FROM `user` FROM `mysql` LIKE '%';
		// SHOW COLUMNS FROM `user` FROM `mysql` LIKE '%';
		tbl, _ := m.Ctx.Schema.Table(stmt.Identity)
		if tbl == nil {
			u.Warnf("no table? %q", stmt.Identity)
			return nil, expr.VisitError, fmt.Errorf("No table found for %q", stmt.Identity)
		}
		source, proj := DescribeTable(tbl, stmt.Full)
		m.Ctx.Projection = plan.NewProjectionStatic(proj)
		tasks := make(Tasks, 0)
		sourceTask := NewSource(m.Ctx, nil, source)
		tasks.Add(sourceTask)
		return NewSequential(m.Ctx, "show columns", tasks), expr.VisitContinue, nil
	case strings.ToLower(stmt.Identity) == "variables":
		// SHOW variables;
		vals := make([][]driver.Value, 2)
		vals[0] = []driver.Value{"auto_increment_increment", "1"}
		vals[1] = []driver.Value{"collation", "utf8"}
		source := membtree.NewStaticDataSource("variables", 0, vals, []string{"Variable_name", "Value"})
		proj := expr.NewProjection()
		proj.AddColumnShort("Variable_name", value.StringType)
		proj.AddColumnShort("Value", value.StringType)
		m.Ctx.Projection = plan.NewProjectionStatic(proj)
		tasks := make(Tasks, 0)
		sourceTask := NewSource(m.Ctx, nil, source)
		u.Infof("source:  %#v", source)
		tasks.Add(sourceTask)
		return NewSequential(m.Ctx, "variables", tasks), expr.VisitContinue, nil
	case strings.ToLower(stmt.Identity) == "databases":
		// SHOW databases;
		vals := make([][]driver.Value, 1)
		vals[0] = []driver.Value{m.Ctx.Schema.Name}
		source := membtree.NewStaticDataSource("databases", 0, vals, []string{"Database"})
		proj := expr.NewProjection()
		proj.AddColumnShort("Database", value.StringType)
		m.Ctx.Projection = plan.NewProjectionStatic(proj)
		tasks := make(Tasks, 0)
		sourceTask := NewSource(m.Ctx, nil, source)
		u.Infof("source:  %#v", source)
		tasks.Add(sourceTask)
		return NewSequential(m.Ctx, "databases", tasks), expr.VisitContinue, nil
	case strings.ToLower(stmt.Identity) == "collation":
		// SHOW collation;
		vals := make([][]driver.Value, 1)
		// utf8_general_ci          | utf8     |  33 | Yes     | Yes      |       1 |
		vals[0] = []driver.Value{"utf8_general_ci", "utf8", 33, "Yes", "Yes", 1}
		cols := []string{"Collation", "Charset", "Id", "Default", "Compiled", "Sortlen"}
		source := membtree.NewStaticDataSource("collation", 0, vals, cols)
		proj := expr.NewProjection()
		proj.AddColumnShort("Collation", value.StringType)
		proj.AddColumnShort("Charset", value.StringType)
		proj.AddColumnShort("Id", value.IntType)
		proj.AddColumnShort("Default", value.StringType)
		proj.AddColumnShort("Compiled", value.StringType)
		proj.AddColumnShort("Sortlen", value.IntType)
		m.Ctx.Projection = plan.NewProjectionStatic(proj)
		tasks := make(Tasks, 0)
		sourceTask := NewSource(m.Ctx, nil, source)
		u.Infof("source:  %#v", source)
		tasks.Add(sourceTask)
		return NewSequential(m.Ctx, "collation", tasks), expr.VisitContinue, nil
	case strings.HasPrefix(raw, "show session"):
		//SHOW SESSION VARIABLES LIKE 'lower_case_table_names';
		source, proj := ShowVariables("lower_case_table_names", 0)
		m.Ctx.Projection = plan.NewProjectionStatic(proj)
		tasks := make(Tasks, 0)
		sourceTask := NewSource(m.Ctx, nil, source)
		u.Infof("source:  %#v", source)
		tasks.Add(sourceTask)
		return NewSequential(m.Ctx, "session", tasks), expr.VisitContinue, nil
	case strings.ToLower(stmt.ShowType) == "tables" || strings.ToLower(stmt.Identity) == m.Ctx.Schema.Name:
		if stmt.Full {
			// SHOW FULL TABLES;
			u.Debugf("show tables: %+v", m.Ctx)
			tables := m.Ctx.Schema.Tables()
			vals := make([][]driver.Value, len(tables))
			row := 0
			for _, tbl := range tables {
				vals[row] = []driver.Value{tbl, "BASE TABLE"}
				row++
			}
			source := membtree.NewStaticDataSource("tables", 0, vals, []string{"Tables", "Table_type"})
			proj := expr.NewProjection()
			proj.AddColumnShort("Tables", value.StringType)
			proj.AddColumnShort("Table_type", value.StringType)
			m.Ctx.Projection = plan.NewProjectionStatic(proj)
			tasks := make(Tasks, 0)
			sourceTask := NewSource(m.Ctx, nil, source)
			u.Infof("source rowct=%d  %#v", row, source)
			tasks.Add(sourceTask)
			return NewSequential(m.Ctx, "show-tables", tasks), expr.VisitContinue, nil
		}
		// SHOW TABLES;
		//u.Debugf("show tables: %+v", m.Conf)
		source, proj := ShowTables(m.Ctx)
		m.Ctx.Projection = plan.NewProjectionStatic(proj)
		tasks := make(Tasks, 0)
		sourceTask := NewSource(m.Ctx, nil, source)
		//u.Infof("source:  %#v", source)
		tasks.Add(sourceTask)
		return NewSequential(m.Ctx, "describe", tasks), expr.VisitContinue, nil
	case strings.ToLower(stmt.Identity) == "procedure":
		// SHOW PROCEDURE STATUS WHERE Db='mydb'
		return m.emptyTask("Procedures")
	case strings.ToLower(stmt.Identity) == "function":
		// SHOW FUNCTION STATUS WHERE Db='mydb'
		return m.emptyTask("Function")
	default:
		// SHOW FULL TABLES FROM `auths`
		desc := expr.SqlDescribe{}
		desc.Identity = stmt.Identity
		return m.VisitDescribe(&desc)
	}
	return nil, expr.VisitError, fmt.Errorf("No handler found")
}

// DESCRIBE statements
//
//    - DESCRIBE table
//
func (m *JobBuilder) VisitDescribe(stmt *expr.SqlDescribe) (expr.Task, expr.VisitStatus, error) {
	u.Debugf("VisitDescribe %+v", stmt)

	if m.Ctx == nil || m.Ctx.Schema == nil {
		return nil, expr.VisitError, ErrNoSchemaSelected
	}
	tbl, err := m.Ctx.Schema.Table(strings.ToLower(stmt.Identity))
	if err != nil {
		u.Errorf("could not get table: %v", err)
		return nil, expr.VisitError, err
	}
	source, proj := DescribeTable(tbl, false)
	m.Ctx.Projection = plan.NewProjectionStatic(proj)

	tasks := make(Tasks, 0)
	sourceTask := NewSource(m.Ctx, nil, source)
	//u.Infof("source:  %#v", source)
	tasks.Add(sourceTask)

	return NewSequential(m.Ctx, "describe", tasks), expr.VisitContinue, nil
}
<commit_msg>simplify Show statement<commit_after>package exec

import (
	"database/sql/driver"
	"fmt"
	"strings"

	u "github.com/araddon/gou"

	"github.com/araddon/qlbridge/datasource"
	"github.com/araddon/qlbridge/datasource/membtree"
	"github.com/araddon/qlbridge/expr"
	"github.com/araddon/qlbridge/plan"
	"github.com/araddon/qlbridge/schema"
	"github.com/araddon/qlbridge/value"
)

var (
	_ = u.EMPTY
)

func FieldsAsMessages(tbl *schema.Table) []datasource.Message {
	msgs := make([]datasource.Message, len(tbl.Fields))
	for i, f := range tbl.Fields {
		msgs[i] = f
	}
	return msgs
}

// Describe A table
//
func DescribeTable(tbl *schema.Table, full bool) (datasource.Scanner, *expr.Projection) {
	if len(tbl.Fields) == 0 {
		u.Warnf("NO Fields!!!!! for %s p=%p", tbl.Name, tbl)
	}
	proj := expr.NewProjection()
	if full {
		for _, f := range schema.DescribeFullHeaders {
			proj.AddColumnShort(string(f.Name), f.Type)
		}
	} else {
		for _, f := range schema.DescribeHeaders {
			proj.AddColumnShort(string(f.Name), f.Type)
		}
	}
	//u.Warnf("columns?  %#v   \nvals?%v", proj, tbl.FieldMap)
	//tbl.FieldsAsMessages()
	ss := datasource.NewStaticSource(tbl.Name, tbl.Columns(), FieldsAsMessages(tbl))
	//tableVals := membtree.NewStaticDataSource("describetable", 0, tbl.DescribeValues, schema.DescribeFullCols)
	return ss, proj
}

func ShowTables(ctx *plan.Context) (*membtree.StaticDataSource, *expr.Projection) {

	/*
		mysql> show full tables from temp like '%';
		+--------------------+------------+
		| Tables_in_temp (%) | Table_type |
		+--------------------+------------+
		| emails             | BASE TABLE |
		| events             | BASE TABLE |
		| evtnames           | BASE TABLE |
		| username           | BASE TABLE |
		+--------------------+------------+
		5 rows in set (0.00 sec)

		mysql> show tables;
		+----------------+
		| Tables_in_temp |
		+----------------+
		| emails         |
		| events         |
		| evtnames       |
		| username       |
		+----------------+
		5 rows in set (0.00 sec)

		mysql> show tables from temp like '%';
		+--------------------+
		| Tables_in_temp (%) |
		+--------------------+
		| emails             |
		| events             |
		| evtnames           |
		| username           |
		+--------------------+
		5 rows in set (0.00 sec)

	*/
	tables := ctx.Schema.Tables()
	vals := make([][]driver.Value, len(tables))
	idx := 0
	if len(tables) == 0 {
		u.Warnf("NO TABLES!!!!! for %+v", ctx.Schema)
	}
	for _, tbl := range tables {
		vals[idx] = []driver.Value{tbl}
		//u.Infof("found table: %v   vals=%v", tbl, vals[idx])
		idx++
	}
	showTableVals := membtree.NewStaticDataSource("schematables", 0, vals, []string{"Table"})
	proj := expr.NewProjection()
	proj.AddColumnShort("Table", value.StringType)
	//u.Infof("showtables:  %v", m.showTableVals)
	return showTableVals, proj
}

func ShowVariables(name string, val driver.Value) (*membtree.StaticDataSource, *expr.Projection) {
	/*
	   MariaDB [(none)]> SHOW SESSION VARIABLES LIKE 'lower_case_table_names';
	   +------------------------+-------+
	   | Variable_name          | Value |
	   +------------------------+-------+
	   | lower_case_table_names | 0     |
	   +------------------------+-------+
	*/
	vals := make([][]driver.Value, 1)
	vals[0] = []driver.Value{name, val}
	dataSource := membtree.NewStaticDataSource("schematables", 0, vals, []string{"Variable_name", "Value"})
	p := expr.NewProjection()
	p.AddColumnShort("Variable_name", value.StringType)
	p.AddColumnShort("Value", value.StringType)
	return dataSource, p
}

func (m *JobBuilder) emptyTask(name string) (TaskRunner, expr.VisitStatus, error) {
	source := membtree.NewStaticDataSource(name, 0, nil, []string{name})
	proj := expr.NewProjection()
	proj.AddColumnShort(name, value.StringType)
	m.Ctx.Projection = plan.NewProjectionStatic(proj)
	tasks := make(Tasks, 0)
	sourceTask := NewSource(m.Ctx, nil, source)
	tasks.Add(sourceTask)
	return NewSequential(m.Ctx, name, tasks), expr.VisitContinue, nil
}

func (m *JobBuilder) VisitShow(stmt *expr.SqlShow) (expr.Task, expr.VisitStatus, error) {

	//u.Debugf("exec.VisitShow create?%v  identity=%q  raw=%s", stmt.Create, stmt.Identity, stmt.Raw)

	/*
		TODO:  #
		  Rewrite all of this to rewrite queries as select statements with
		  - WHERE (rewritten like, with column name inserted)
		  - select schema
	*/

	tasks := make(Tasks, 0)
	taskName := "show"
	var source datasource.Scanner
	proj := expr.NewProjection()

	raw := strings.ToLower(stmt.Raw)
	switch {
	case stmt.Create && strings.ToLower(stmt.CreateWhat) == "table":
		// SHOW CREATE TABLE
		tbl, _ := m.Ctx.Schema.Table(stmt.Identity)
		if tbl == nil {
			u.Warnf("no table? %q", stmt.Identity)
			return nil, expr.VisitError, fmt.Errorf("No table found for %q", stmt.Identity)
		}
		source = membtree.NewStaticDataSource("tables", 0, [][]driver.Value{{stmt.Identity, tbl}}, []string{"Table", "Create Table"})
		proj.AddColumnShort("Table", value.StringType)
		proj.AddColumnShort("Create Table", value.StringType)
		taskName = "show-create-table"

	case stmt.ShowType == "columns":
		// SHOW FULL COLUMNS FROM `user` FROM `mysql` LIKE '%';
		// SHOW COLUMNS FROM `user` FROM `mysql` LIKE '%';
		tbl, _ := m.Ctx.Schema.Table(stmt.Identity)
		if tbl == nil {
			u.Warnf("no table? %q", stmt.Identity)
			return nil, expr.VisitError, fmt.Errorf("No table found for %q", stmt.Identity)
		}
		source, proj = DescribeTable(tbl, stmt.Full)
		taskName = "show columns"
	case strings.ToLower(stmt.Identity) == "variables":
		// SHOW variables;
		vals := make([][]driver.Value, 2)
		vals[0] = []driver.Value{"auto_increment_increment", "1"}
		vals[1] = []driver.Value{"collation", "utf8"}
		source = membtree.NewStaticDataSource("variables", 0, vals, []string{"Variable_name", "Value"})
		proj.AddColumnShort("Variable_name", value.StringType)
		proj.AddColumnShort("Value", value.StringType)
		return NewSequential(m.Ctx, "variables", tasks), expr.VisitContinue, nil
	case strings.ToLower(stmt.Identity) == "databases":
		// SHOW databases;
		source = membtree.NewStaticDataSource("databases", 0, [][]driver.Value{{m.Ctx.Schema.Name}}, []string{"Database"})
		proj.AddColumnShort("Database", value.StringType)
		taskName = "show-databases"
	case strings.ToLower(stmt.Identity) == "collation":
		// SHOW collation;
		vals := make([][]driver.Value, 1)
		// utf8_general_ci          | utf8     |  33 | Yes     | Yes      |       1 |
		vals[0] = []driver.Value{"utf8_general_ci", "utf8", 33, "Yes", "Yes", 1}
		cols := []string{"Collation", "Charset", "Id", "Default", "Compiled", "Sortlen"}
		source = membtree.NewStaticDataSource("collation", 0, vals, cols)
		proj.AddColumnShort("Collation", value.StringType)
		proj.AddColumnShort("Charset", value.StringType)
		proj.AddColumnShort("Id", value.IntType)
		proj.AddColumnShort("Default", value.StringType)
		proj.AddColumnShort("Compiled", value.StringType)
		proj.AddColumnShort("Sortlen", value.IntType)
		taskName = "show-collation"
	case strings.HasPrefix(raw, "show session"):
		//SHOW SESSION VARIABLES LIKE 'lower_case_table_names';
		source, proj = ShowVariables("lower_case_table_names", 0)
		taskName = "show-session-vars"
	case strings.ToLower(stmt.ShowType) == "tables" || strings.ToLower(stmt.Identity) == m.Ctx.Schema.Name:
		if stmt.Full {
			// SHOW FULL TABLES;
			u.Debugf("show tables: %+v", m.Ctx)
			tables := m.Ctx.Schema.Tables()
			vals := make([][]driver.Value, len(tables))
			row := 0
			for _, tbl := range tables {
				vals[row] = []driver.Value{tbl, "BASE TABLE"}
				row++
			}
			source = membtree.NewStaticDataSource("tables", 0, vals, []string{"Tables", "Table_type"})
			proj.AddColumnShort("Tables", value.StringType)
			proj.AddColumnShort("Table_type", value.StringType)
			taskName = "show-full-tables"
		} else {
			// SHOW TABLES;
			source, proj = ShowTables(m.Ctx)
			taskName = "show tables"

		}
	case strings.ToLower(stmt.Identity) == "procedure":
		// SHOW PROCEDURE STATUS WHERE Db='mydb'
		return m.emptyTask("Procedures")
	case strings.ToLower(stmt.Identity) == "function":
		// SHOW FUNCTION STATUS WHERE Db='mydb'
		return m.emptyTask("Function")
	default:
		// SHOW FULL TABLES FROM `auths`
		desc := expr.SqlDescribe{}
		desc.Identity = stmt.Identity
		return m.VisitDescribe(&desc)
	}
	sourceTask := NewSource(m.Ctx, nil, source)
	tasks.Add(sourceTask)
	m.Ctx.Projection = plan.NewProjectionStatic(proj)

	if stmt.Like != nil {
		// TODO:  this Like needs to be written to support which column we are filtering on
		// where := NewWhereFinal(m.Ctx, stmt)
		// tasks.Add(where)
	}

	return NewSequential(m.Ctx, taskName, tasks), expr.VisitContinue, nil
}

// DESCRIBE statements
//
//    - DESCRIBE table
//
func (m *JobBuilder) VisitDescribe(stmt *expr.SqlDescribe) (expr.Task, expr.VisitStatus, error) {
	u.Debugf("VisitDescribe %+v", stmt)

	if m.Ctx == nil || m.Ctx.Schema == nil {
		return nil, expr.VisitError, ErrNoSchemaSelected
	}
	tbl, err := m.Ctx.Schema.Table(strings.ToLower(stmt.Identity))
	if err != nil {
		u.Errorf("could not get table: %v", err)
		return nil, expr.VisitError, err
	}
	source, proj := DescribeTable(tbl, false)
	m.Ctx.Projection = plan.NewProjectionStatic(proj)

	tasks := make(Tasks, 0)
	sourceTask := NewSource(m.Ctx, nil, source)
	//u.Infof("source:  %#v", source)
	tasks.Add(sourceTask)

	return NewSequential(m.Ctx, "describe", tasks), expr.VisitContinue, nil
}
<|endoftext|>


<commit_before>// Copyright 2019 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 math_test

import (
	"reflect"
	"testing"

	. "github.com/hajimehoshi/ebiten/vector/internal/math"
)

func TestIsInTriangle(t *testing.T) {
	tests := []struct {
		Tri []Point
		Pt  Point
		Out bool
	}{
		{
			Tri: []Point{
				{0, 0},
				{0, 10},
				{10, 10},
			},
			Pt:  Point{1, 9},
			Out: true,
		},
		{
			Tri: []Point{
				{0, 0},
				{0, 10},
				{10, 10},
			},
			Pt:  Point{8, 9},
			Out: true,
		},
		{
			Tri: []Point{
				{0, 0},
				{0, 10},
				{10, 10},
			},
			Pt:  Point{10, 9},
			Out: false,
		},
		{
			Tri: []Point{
				{3, 5},
				{2, 7},
				{7, 7},
			},
			Pt:  Point{3, 6},
			Out: true,
		},
		{
			Tri: []Point{
				{3, 5},
				{2, 7},
				{7, 7},
			},
			Pt:  Point{7, 6},
			Out: false,
		},
	}
	for _, tc := range tests {
		got := InTriangle(tc.Pt, tc.Tri[0], tc.Tri[1], tc.Tri[2])
		want := tc.Out
		if got != want {
			t.Errorf("InTriangle(%v, %v, %v, %v): got: %t, want: %t", tc.Pt, tc.Tri[0], tc.Tri[1], tc.Tri[2], got, want)
		}
	}
}

func TestTriangulate(t *testing.T) {
	tests := []struct {
		In  []Point
		Out []uint16
	}{
		{
			In:  []Point{},
			Out: nil,
		},
		{
			In: []Point{
				{0, 0},
			},
			Out: nil,
		},
		{
			In: []Point{
				{0, 0},
				{0, 1},
			},
			Out: nil,
		},
		{
			In: []Point{
				{0, 0},
				{0, 0},
				{1, 1},
			},
			Out: nil,
		},
		{
			In: []Point{
				{0, 0},
				{0.5, 0.5},
				{1, 1},
			},
			Out: nil,
		},
		{
			In: []Point{
				{0, 0},
				{0, 1},
				{1, 1},
			},
			Out: []uint16{2, 0, 1},
		},
		{
			In: []Point{
				{0, 0},
				{1, 1},
				{0, 1},
			},
			Out: []uint16{2, 0, 1},
		},
		{
			In: []Point{
				{0, 0},
				{0, 1},
				{1, 1},
				{1, 0},
			},
			Out: []uint16{3, 0, 1, 3, 1, 2},
		},
		{
			In: []Point{
				{2, 2},
				{2, 7},
				{7, 7},
				{7, 6},
				{3, 6},
				{3, 5},
			},
			Out: []uint16{5, 0, 1, 1, 2, 3, 1, 3, 4, 5, 1, 4},
		},
		{
			In: []Point{
				{2, 2},
				{2, 7},
				{7, 7},
				{7, 6},
				{3, 6},
				{3, 5},
				{7, 5},
				{7, 4},
				{3, 4},
				{3, 3},
			},
			Out: []uint16{9, 0, 1, 1, 2, 3, 1, 3, 4, 9, 1, 4, 8, 5, 6, 8, 6, 7},
		},
	}
	for _, tc := range tests {
		got := Triangulate(tc.In)
		want := tc.Out
		if !reflect.DeepEqual(got, want) {
			t.Errorf("Triangulate(%v): got: %v, want: %v", tc.In, got, want)
		}
	}
}
<commit_msg>vector/internal/math: Add tests<commit_after>// Copyright 2019 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 math_test

import (
	"reflect"
	"testing"

	. "github.com/hajimehoshi/ebiten/vector/internal/math"
)

func TestIsInTriangle(t *testing.T) {
	tests := []struct {
		Tri []Point
		Pt  Point
		Out bool
	}{
		{
			Tri: []Point{
				{0, 0},
				{0, 10},
				{10, 10},
			},
			Pt:  Point{1, 9},
			Out: true,
		},
		{
			Tri: []Point{
				{0, 0},
				{0, 10},
				{10, 10},
			},
			Pt:  Point{8, 9},
			Out: true,
		},
		{
			Tri: []Point{
				{0, 0},
				{0, 10},
				{10, 10},
			},
			Pt:  Point{10, 9},
			Out: false,
		},
		{
			Tri: []Point{
				{3, 5},
				{2, 7},
				{7, 7},
			},
			Pt:  Point{3, 6},
			Out: true,
		},
		{
			Tri: []Point{
				{3, 5},
				{2, 7},
				{7, 7},
			},
			Pt:  Point{7, 6},
			Out: false,
		},
	}
	for _, tc := range tests {
		got := InTriangle(tc.Pt, tc.Tri[0], tc.Tri[1], tc.Tri[2])
		want := tc.Out
		if got != want {
			t.Errorf("InTriangle(%v, %v, %v, %v): got: %t, want: %t", tc.Pt, tc.Tri[0], tc.Tri[1], tc.Tri[2], got, want)
		}
	}
}

func TestTriangulate(t *testing.T) {
	tests := []struct {
		In  []Point
		Out []uint16
	}{
		{
			In:  []Point{},
			Out: nil,
		},
		{
			In: []Point{
				{0, 0},
			},
			Out: nil,
		},
		{
			In: []Point{
				{0, 0},
				{0, 1},
			},
			Out: nil,
		},
		{
			In: []Point{
				{0, 0},
				{0, 0},
				{1, 1},
			},
			Out: nil,
		},
		{
			In: []Point{
				{0, 0},
				{0.5, 0.5},
				{1, 1},
			},
			Out: nil,
		},
		{
			In: []Point{
				{0, 0},
				{0.5, 0.5},
				{1.5, 1.5},
				{1, 1},
			},
			Out: nil,
		},
		{
			In: []Point{
				{0, 0},
				{0, 1},
				{1, 1},
			},
			Out: []uint16{2, 0, 1},
		},
		{
			In: []Point{
				{0, 0},
				{1, 1},
				{0, 1},
			},
			Out: []uint16{2, 0, 1},
		},
		{
			In: []Point{
				{0, 0},
				{0, 1},
				{1, 1},
				{1, 0},
			},
			Out: []uint16{3, 0, 1, 3, 1, 2},
		},
		{
			In: []Point{
				{2, 2},
				{2, 7},
				{7, 7},
				{7, 6},
				{3, 6},
				{3, 5},
			},
			Out: []uint16{5, 0, 1, 1, 2, 3, 1, 3, 4, 5, 1, 4},
		},
		{
			In: []Point{
				{2, 2},
				{2, 7},
				{7, 7},
				{7, 6},
				{3, 6},
				{3, 5},
				{7, 5},
				{7, 4},
				{3, 4},
				{3, 3},
			},
			Out: []uint16{9, 0, 1, 1, 2, 3, 1, 3, 4, 9, 1, 4, 8, 5, 6, 8, 6, 7},
		},
	}
	for _, tc := range tests {
		got := Triangulate(tc.In)
		want := tc.Out
		if !reflect.DeepEqual(got, want) {
			t.Errorf("Triangulate(%v): got: %v, want: %v", tc.In, got, want)
		}
	}
}
<|endoftext|>


<commit_before>package aiff

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"log"
	"sync"
	"time"

	"github.com/mattetti/audio"
	"github.com/mattetti/audio/misc"
)

var (
	defaultChunkDecoderTimeout = 2 * time.Second
)

// Decoder is the wrapper structure for the AIFF container
type Decoder struct {
	r io.ReadSeeker
	// Chan is an Optional channel of chunks that is used to parse chunks
	Chan chan *Chunk
	// ChunkDecoderTimeout is the duration after which the main parser keeps going
	// if the dev hasn't reported the chunk parsing to be done.
	// By default: 2s
	//ChunkDecoderTimeout time.Duration
	// The waitgroup is used to let the parser that it's ok to continue
	// after a chunk was passed to the optional parser channel.
	Wg sync.WaitGroup

	// ID is always 'FORM'. This indicates that this is a FORM chunk
	ID [4]byte
	// Size contains the size of data portion of the 'FORM' chunk.
	// Note that the data portion has been
	// broken into two parts, formType and chunks
	Size uint32
	// Format describes what's in the 'FORM' chunk. For Audio IFF files,
	// formType (aka Format) is always 'AIFF'.
	// This indicates that the chunks within the FORM pertain to sampled sound.
	Format [4]byte

	// Data coming from the COMM chunk
	commSize        uint32
	numChans        uint16
	numSampleFrames uint32
	sampleSize      uint16
	sampleRate      int

	// AIFC data
	Encoding     [4]byte
	EncodingName string

	err      error
	dataClip *audio.Clip
}

// NewDecoder creates a new reader reading the given reader and pushing audio data to the given channel.
// It is the caller's responsibility to call Close on the Decoder when done.
func NewDecoder(r io.ReadSeeker, c chan *Chunk) *Decoder {
	return &Decoder{r: r, Chan: c}
}

// Err returns the first non-EOF error that was encountered by the Decoder.
func (d *Decoder) Err() error {
	if d.err == io.EOF {
		return nil
	}
	return d.err
}

// Clip returns the audio Clip information including a reader to reads its content.
// This method is safe to be called multiple times but the reader might need to be rewinded
// if previously read.
// This is the recommended, default way to consume an AIFF file.
// Note that non audio chunks are skipped and the chunk channels doesn't get dispatched.
func (d *Decoder) Clip() *audio.Clip {
	if d.dataClip != nil {
		return d.dataClip
	}
	if d.err = d.readHeaders(); d.err != nil {
		d.err = fmt.Errorf("failed to read header - %v", d.err)
		return nil
	}

	d.dataClip = &audio.Clip{}

	// read the file information to setup the audio clip
	// find the beginning of the SSND chunk and set the clip reader to it.
	var (
		id          [4]byte
		size        uint32
		rewindBytes int64
	)
	for d.err != io.EOF {
		id, size, d.err = d.IDnSize()
		if d.err != nil {
			d.err = fmt.Errorf("error reading chunk header - %v", d.err)
			break
		}
		switch id {
		case COMMID:
			d.parseCommChunk(size)
			d.dataClip.Channels = int(d.numChans)
			d.dataClip.BitDepth = int(d.sampleSize)
			d.dataClip.SampleRate = int64(d.sampleRate)
			// if we found the sound data before the COMM,
			// we need to rewind the reader so we can properly
			// set the clip reader.
			if rewindBytes > 0 {
				d.r.Seek(-rewindBytes, 1)
				break
			}
		case SSNDID:
			d.dataClip.DataSize = int64(size)
			// if we didn't read the COMM, we are going to need to come back
			if d.dataClip.SampleRate == 0 {
				rewindBytes += int64(size)
				if d.err = d.jumpTo(int(size)); d.err != nil {
					return nil
				}
			}
			d.dataClip.R = d.r
			return d.dataClip

		default:
			// if we read SSN but didn't read the COMM, we need to track location
			if d.dataClip.DataSize == 0 {
				rewindBytes += int64(size)
			}
			if d.err = d.jumpTo(int(size)); d.err != nil {
				return nil
			}
		}
	}

	return d.dataClip
}

// Parse reads the aiff reader and populates the container structure with found information.
// The sound data or unknown chunks are passed to the optional channel if available.
// Instead of checking the returned error, it's recommended to read d.Err()
func (d *Decoder) Parse() error {
	if d.err = d.readHeaders(); d.err != nil {
		return d.err
	}

	var id [4]byte
	var size uint32
	for d.err != io.EOF {
		id, size, d.err = d.IDnSize()
		if d.err != nil {
			break
		}
		switch id {
		case COMMID:
			d.parseCommChunk(size)
		default:
			d.dispatchToChan(id, size)
		}
	}

	if d.Chan != nil {
		close(d.Chan)
	}

	return d.Err()
}

// Frames processes the reader and returns the basic data and LPCM audio frames.
// Very naive and inneficient approach loading the entire data set in memory.
// Consider using Clip() instead
func (d *Decoder) Frames() (info *Info, frames [][]int, err error) {
	ch := make(chan *Chunk)
	d.Chan = ch
	var sndDataFrames [][]int
	go func() {
		if err := d.Parse(); err != nil {
			panic(err)
		}
	}()

	for chunk := range ch {
		if sndDataFrames == nil {
			sndDataFrames = make([][]int, d.numSampleFrames, d.numSampleFrames)
		}
		id := string(chunk.ID[:])
		if id == "SSND" {
			var offset uint32
			var blockSize uint32
			// TODO: BE might depend on the encoding used to generate the aiff data.
			// check encSowt or encTwos
			chunk.ReadBE(&offset)
			chunk.ReadBE(&blockSize)

			// TODO: might want to use io.NewSectionDecoder
			bufData := make([]byte, chunk.Size-8)
			chunk.ReadBE(bufData)
			buf := bytes.NewReader(bufData)

			bytesPerSample := (d.sampleSize-1)/8 + 1
			frameCount := int(d.numSampleFrames)

			if d.numSampleFrames == 0 {
				chunk.Done()
				continue
			}

			for i := 0; i < frameCount; i++ {
				sampleBufData := make([]byte, bytesPerSample)
				frame := make([]int, d.numChans)

				for j := uint16(0); j < d.numChans; j++ {
					_, err := buf.Read(sampleBufData)
					if err != nil {
						if err == io.EOF {
							break
						}
						log.Println("error reading the buffer")
						log.Fatal(err)
					}

					sampleBuf := bytes.NewBuffer(sampleBufData)
					switch d.sampleSize {
					case 8:
						var v uint8
						binary.Read(sampleBuf, binary.BigEndian, &v)
						frame[j] = int(v)
					case 16:
						var v int16
						binary.Read(sampleBuf, binary.BigEndian, &v)
						frame[j] = int(v)
					case 24:
						// TODO: check if the conversion might not be inversed depending on
						// the encoding (BE vs LE)
						var output int32
						output |= int32(sampleBufData[2]) << 0
						output |= int32(sampleBufData[1]) << 8
						output |= int32(sampleBufData[0]) << 16
						frame[j] = int(output)
					case 32:
						var v int32
						binary.Read(sampleBuf, binary.BigEndian, &v)
						frame[j] = int(v)
					default:
						// TODO: nicer error instead of crashing
						log.Fatalf("%v bitrate not supported", d.sampleSize)
					}
				}
				sndDataFrames[i] = frame

			}
		}

		chunk.Done()
	}

	duration, err := d.Duration()
	if err != nil {
		return nil, sndDataFrames, err
	}

	info = &Info{
		NumChannels: int(d.numChans),
		SampleRate:  d.sampleRate,
		BitDepth:    int(d.sampleSize),
		Duration:    duration,
	}

	return info, sndDataFrames, err
}

func (d *Decoder) readHeaders() error {
	// prevent the headers to be re-read
	if d.Size > 0 {
		return nil
	}
	if d.err = binary.Read(d.r, binary.BigEndian, &d.ID); d.err != nil {
		return d.err
	}
	// Must start by a FORM header/ID
	if d.ID != formID {
		d.err = fmt.Errorf("%s - %s", ErrFmtNotSupported, d.ID)
		return d.err
	}

	if d.err = binary.Read(d.r, binary.BigEndian, &d.Size); d.err != nil {
		return d.err
	}
	if d.err = binary.Read(d.r, binary.BigEndian, &d.Format); d.err != nil {
		return d.err
	}

	// Must be a AIFF or AIFC form type
	if d.Format != aiffID && d.Format != aifcID {
		d.err = fmt.Errorf("%s - %s", ErrFmtNotSupported, d.Format)
		return d.err
	}

	return nil
}

func (d *Decoder) parseCommChunk(size uint32) error {
	d.commSize = size
	// don't re-parse the comm chunk
	if d.numChans > 0 {
		return nil
	}

	if d.err = binary.Read(d.r, binary.BigEndian, &d.numChans); d.err != nil {
		d.err = fmt.Errorf("num of channels failed to parse - %s", d.err)
		return d.err
	}
	if d.err = binary.Read(d.r, binary.BigEndian, &d.numSampleFrames); d.err != nil {
		d.err = fmt.Errorf("num of sample frames failed to parse - %s", d.err)
		return d.err
	}
	if d.err = binary.Read(d.r, binary.BigEndian, &d.sampleSize); d.err != nil {
		d.err = fmt.Errorf("sample size failed to parse - %s", d.err)
		return d.err
	}
	var srBytes [10]byte
	if d.err = binary.Read(d.r, binary.BigEndian, &srBytes); d.err != nil {
		d.err = fmt.Errorf("sample rate failed to parse - %s", d.err)
		return d.err
	}
	d.sampleRate = misc.IeeeFloatToInt(srBytes)

	if d.Format == aifcID {
		if d.err = binary.Read(d.r, binary.BigEndian, &d.Encoding); d.err != nil {
			d.err = fmt.Errorf("AIFC encoding failed to parse - %s", d.err)
			return d.err
		}
		// pascal style string with the description of the encoding
		var size uint8
		if d.err = binary.Read(d.r, binary.BigEndian, &size); d.err != nil {
			d.err = fmt.Errorf("AIFC encoding failed to parse - %s", d.err)
			return d.err
		}

		desc := make([]byte, size)
		if d.err = binary.Read(d.r, binary.BigEndian, &desc); d.err != nil {
			d.err = fmt.Errorf("AIFC encoding failed to parse - %s", d.err)
			return d.err
		}
		d.EncodingName = string(desc)
	}

	return nil

}

func (d *Decoder) dispatchToChan(id [4]byte, size uint32) error {
	if d.Chan == nil {
		if d.err = d.jumpTo(int(size)); d.err != nil {
			return d.err
		}
		return nil
	}
	okC := make(chan bool)
	bSize := int(size)
	d.Wg.Add(1)
	d.Chan <- &Chunk{
		ID:     id,
		Size:   bSize,
		R:      io.LimitReader(d.r, int64(bSize)),
		okChan: okC,
		Wg:     &d.Wg,
	}
	d.Wg.Wait()
	// TODO: timeout
	return nil
}

// Duration returns the time duration for the current AIFF container
func (d *Decoder) Duration() (time.Duration, error) {
	if d == nil {
		return 0, errors.New("can't calculate the duration of a nil pointer")
	}
	duration := time.Duration(float64(d.numSampleFrames) / float64(d.sampleRate) * float64(time.Second))
	return duration, nil
}

// String implements the Stringer interface.
func (d *Decoder) String() string {
	out := fmt.Sprintf("Format: %s - ", d.Format)
	if d.Format == aifcID {
		out += fmt.Sprintf("%s - ", d.EncodingName)
	}
	if d.sampleRate != 0 {
		out += fmt.Sprintf("%d channels @ %d / %d bits - ", d.numChans, d.sampleRate, d.sampleSize)
		dur, _ := d.Duration()
		out += fmt.Sprintf("Duration: %f seconds\n", dur.Seconds())
	}
	return out
}

// IDnSize returns the next ID + block size
func (d *Decoder) IDnSize() ([4]byte, uint32, error) {
	var ID [4]byte
	var blockSize uint32
	if d.err = binary.Read(d.r, binary.BigEndian, &ID); d.err != nil {
		return ID, blockSize, d.err
	}
	if d.err = binary.Read(d.r, binary.BigEndian, &blockSize); d.err != nil {
		return ID, blockSize, d.err
	}
	return ID, blockSize, nil
}

// jumpTo advances the reader to the amount of bytes provided
func (d *Decoder) jumpTo(bytesAhead int) error {
	for bytesAhead > 0 {
		readSize := bytesAhead
		if readSize > 4000 {
			readSize = 4000
		}

		buf := make([]byte, readSize)
		d.err = binary.Read(d.r, binary.LittleEndian, &buf)
		if d.err != nil {
			return d.err
		}
		bytesAhead -= readSize
	}
	return nil
}
<commit_msg>aiff: more refactoring<commit_after>package aiff

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"sync"
	"time"

	"github.com/mattetti/audio"
	"github.com/mattetti/audio/misc"
)

var (
	defaultChunkDecoderTimeout = 2 * time.Second
)

// Decoder is the wrapper structure for the AIFF container
type Decoder struct {
	r io.ReadSeeker
	// Chan is an Optional channel of chunks that is used to parse chunks
	Chan chan *Chunk
	// ChunkDecoderTimeout is the duration after which the main parser keeps going
	// if the dev hasn't reported the chunk parsing to be done.
	// By default: 2s
	//ChunkDecoderTimeout time.Duration
	// The waitgroup is used to let the parser that it's ok to continue
	// after a chunk was passed to the optional parser channel.
	Wg sync.WaitGroup

	// ID is always 'FORM'. This indicates that this is a FORM chunk
	ID [4]byte
	// Size contains the size of data portion of the 'FORM' chunk.
	// Note that the data portion has been
	// broken into two parts, formType and chunks
	Size uint32
	// Format describes what's in the 'FORM' chunk. For Audio IFF files,
	// formType (aka Format) is always 'AIFF'.
	// This indicates that the chunks within the FORM pertain to sampled sound.
	Format [4]byte

	// Data coming from the COMM chunk
	commSize        uint32
	numChans        uint16
	numSampleFrames uint32
	sampleSize      uint16
	sampleRate      int

	// AIFC data
	Encoding     [4]byte
	EncodingName string

	err      error
	dataClip *audio.Clip
}

// NewDecoder creates a new reader reading the given reader and pushing audio data to the given channel.
// It is the caller's responsibility to call Close on the Decoder when done.
func NewDecoder(r io.ReadSeeker, c chan *Chunk) *Decoder {
	return &Decoder{r: r, Chan: c}
}

// Err returns the first non-EOF error that was encountered by the Decoder.
func (d *Decoder) Err() error {
	if d.err == io.EOF {
		return nil
	}
	return d.err
}

// Clip returns the audio Clip information including a reader to reads its content.
// This method is safe to be called multiple times but the reader might need to be rewinded
// if previously read.
// This is the recommended, default way to consume an AIFF file.
// Note that non audio chunks are skipped and the chunk channels doesn't get dispatched.
func (d *Decoder) Clip() *audio.Clip {
	if d.dataClip != nil {
		return d.dataClip
	}
	if d.err = d.readHeaders(); d.err != nil {
		d.err = fmt.Errorf("failed to read header - %v", d.err)
		return nil
	}

	d.dataClip = &audio.Clip{}

	// read the file information to setup the audio clip
	// find the beginning of the SSND chunk and set the clip reader to it.
	var (
		id          [4]byte
		size        uint32
		rewindBytes int64
	)
	for d.err != io.EOF {
		id, size, d.err = d.iDnSize()
		if d.err != nil {
			d.err = fmt.Errorf("error reading chunk header - %v", d.err)
			break
		}
		switch id {
		case COMMID:
			d.parseCommChunk(size)
			d.dataClip.Channels = int(d.numChans)
			d.dataClip.BitDepth = int(d.sampleSize)
			d.dataClip.SampleRate = int64(d.sampleRate)
			// if we found the sound data before the COMM,
			// we need to rewind the reader so we can properly
			// set the clip reader.
			if rewindBytes > 0 {
				d.r.Seek(-rewindBytes, 1)
				break
			}
		case SSNDID:
			d.dataClip.DataSize = int64(size)
			// if we didn't read the COMM, we are going to need to come back
			if d.dataClip.SampleRate == 0 {
				rewindBytes += int64(size)
				if d.err = d.jumpTo(int(size)); d.err != nil {
					return nil
				}
			}
			d.dataClip.R = d.r
			return d.dataClip

		default:
			// if we read SSN but didn't read the COMM, we need to track location
			if d.dataClip.DataSize == 0 {
				rewindBytes += int64(size)
			}
			if d.err = d.jumpTo(int(size)); d.err != nil {
				return nil
			}
		}
	}

	return d.dataClip
}

// Parse reads the aiff reader and populates the container structure with found information.
// The sound data or unknown chunks are passed to the optional channel if available.
// Instead of checking the returned error, it's recommended to read d.Err()
func (d *Decoder) Parse() error {
	if d.err = d.readHeaders(); d.err != nil {
		return d.err
	}

	var id [4]byte
	var size uint32
	for d.err != io.EOF {
		id, size, d.err = d.iDnSize()
		if d.err != nil {
			break
		}
		switch id {
		case COMMID:
			d.parseCommChunk(size)
		default:
			d.dispatchToChan(id, size)
		}
	}

	if d.Chan != nil {
		close(d.Chan)
	}

	return d.Err()
}

// Frames processes the reader and returns the basic data and LPCM audio frames.
// Very naive and inneficient approach loading the entire data set in memory.
// Consider using Clip() instead
func (d *Decoder) Frames() (info *Info, frames [][]int, err error) {
	ch := make(chan *Chunk)
	d.Chan = ch
	var sndDataFrames [][]int
	go func() {
		if err := d.Parse(); err != nil {
			panic(err)
		}
	}()

	for chunk := range ch {
		if sndDataFrames == nil {
			sndDataFrames = make([][]int, d.numSampleFrames, d.numSampleFrames)
		}
		id := string(chunk.ID[:])
		if id == "SSND" {
			var offset uint32
			var blockSize uint32
			// TODO: BE might depend on the encoding used to generate the aiff data.
			// check encSowt or encTwos
			chunk.ReadBE(&offset)
			chunk.ReadBE(&blockSize)

			// TODO: might want to use io.NewSectionDecoder
			bufData := make([]byte, chunk.Size-8)
			chunk.ReadBE(bufData)
			buf := bytes.NewReader(bufData)

			bytesPerSample := (d.sampleSize-1)/8 + 1
			frameCount := int(d.numSampleFrames)

			if d.numSampleFrames == 0 {
				chunk.Done()
				continue
			}

			for i := 0; i < frameCount; i++ {
				sampleBufData := make([]byte, bytesPerSample)
				frame := make([]int, d.numChans)

				for j := uint16(0); j < d.numChans; j++ {
					_, err := buf.Read(sampleBufData)
					if err != nil {
						if err == io.EOF {
							break
						}
						log.Println("error reading the buffer")
						log.Fatal(err)
					}

					sampleBuf := bytes.NewBuffer(sampleBufData)
					switch d.sampleSize {
					case 8:
						var v uint8
						binary.Read(sampleBuf, binary.BigEndian, &v)
						frame[j] = int(v)
					case 16:
						var v int16
						binary.Read(sampleBuf, binary.BigEndian, &v)
						frame[j] = int(v)
					case 24:
						// TODO: check if the conversion might not be inversed depending on
						// the encoding (BE vs LE)
						var output int32
						output |= int32(sampleBufData[2]) << 0
						output |= int32(sampleBufData[1]) << 8
						output |= int32(sampleBufData[0]) << 16
						frame[j] = int(output)
					case 32:
						var v int32
						binary.Read(sampleBuf, binary.BigEndian, &v)
						frame[j] = int(v)
					default:
						// TODO: nicer error instead of crashing
						log.Fatalf("%v bitrate not supported", d.sampleSize)
					}
				}
				sndDataFrames[i] = frame

			}
		}

		chunk.Done()
	}

	duration, err := d.Duration()
	if err != nil {
		return nil, sndDataFrames, err
	}

	info = &Info{
		NumChannels: int(d.numChans),
		SampleRate:  d.sampleRate,
		BitDepth:    int(d.sampleSize),
		Duration:    duration,
	}

	return info, sndDataFrames, err
}

func (d *Decoder) readHeaders() error {
	// prevent the headers to be re-read
	if d.Size > 0 {
		return nil
	}
	if d.err = binary.Read(d.r, binary.BigEndian, &d.ID); d.err != nil {
		return d.err
	}
	// Must start by a FORM header/ID
	if d.ID != formID {
		d.err = fmt.Errorf("%s - %s", ErrFmtNotSupported, d.ID)
		return d.err
	}

	if d.err = binary.Read(d.r, binary.BigEndian, &d.Size); d.err != nil {
		return d.err
	}
	if d.err = binary.Read(d.r, binary.BigEndian, &d.Format); d.err != nil {
		return d.err
	}

	// Must be a AIFF or AIFC form type
	if d.Format != aiffID && d.Format != aifcID {
		d.err = fmt.Errorf("%s - %s", ErrFmtNotSupported, d.Format)
		return d.err
	}

	return nil
}

func (d *Decoder) parseCommChunk(size uint32) error {
	d.commSize = size
	// don't re-parse the comm chunk
	if d.numChans > 0 {
		return nil
	}

	if d.err = binary.Read(d.r, binary.BigEndian, &d.numChans); d.err != nil {
		d.err = fmt.Errorf("num of channels failed to parse - %s", d.err)
		return d.err
	}
	if d.err = binary.Read(d.r, binary.BigEndian, &d.numSampleFrames); d.err != nil {
		d.err = fmt.Errorf("num of sample frames failed to parse - %s", d.err)
		return d.err
	}
	if d.err = binary.Read(d.r, binary.BigEndian, &d.sampleSize); d.err != nil {
		d.err = fmt.Errorf("sample size failed to parse - %s", d.err)
		return d.err
	}
	var srBytes [10]byte
	if d.err = binary.Read(d.r, binary.BigEndian, &srBytes); d.err != nil {
		d.err = fmt.Errorf("sample rate failed to parse - %s", d.err)
		return d.err
	}
	d.sampleRate = misc.IeeeFloatToInt(srBytes)

	if d.Format == aifcID {
		if d.err = binary.Read(d.r, binary.BigEndian, &d.Encoding); d.err != nil {
			d.err = fmt.Errorf("AIFC encoding failed to parse - %s", d.err)
			return d.err
		}
		// pascal style string with the description of the encoding
		var size uint8
		if d.err = binary.Read(d.r, binary.BigEndian, &size); d.err != nil {
			d.err = fmt.Errorf("AIFC encoding failed to parse - %s", d.err)
			return d.err
		}

		desc := make([]byte, size)
		if d.err = binary.Read(d.r, binary.BigEndian, &desc); d.err != nil {
			d.err = fmt.Errorf("AIFC encoding failed to parse - %s", d.err)
			return d.err
		}
		d.EncodingName = string(desc)
	}

	return nil

}

func (d *Decoder) dispatchToChan(id [4]byte, size uint32) error {
	if d.Chan == nil {
		if d.err = d.jumpTo(int(size)); d.err != nil {
			return d.err
		}
		return nil
	}
	okC := make(chan bool)
	bSize := int(size)
	d.Wg.Add(1)
	d.Chan <- &Chunk{
		ID:     id,
		Size:   bSize,
		R:      io.LimitReader(d.r, int64(bSize)),
		okChan: okC,
		Wg:     &d.Wg,
	}
	d.Wg.Wait()
	// TODO: timeout
	return nil
}

// Duration returns the time duration for the current AIFF container
func (d *Decoder) Duration() (time.Duration, error) {
	if d == nil {
		return 0, errors.New("can't calculate the duration of a nil pointer")
	}
	duration := time.Duration(float64(d.numSampleFrames) / float64(d.sampleRate) * float64(time.Second))
	return duration, nil
}

// String implements the Stringer interface.
func (d *Decoder) String() string {
	out := fmt.Sprintf("Format: %s - ", d.Format)
	if d.Format == aifcID {
		out += fmt.Sprintf("%s - ", d.EncodingName)
	}
	if d.sampleRate != 0 {
		out += fmt.Sprintf("%d channels @ %d / %d bits - ", d.numChans, d.sampleRate, d.sampleSize)
		dur, _ := d.Duration()
		out += fmt.Sprintf("Duration: %f seconds\n", dur.Seconds())
	}
	return out
}

// iDnSize returns the next ID + block size
func (d *Decoder) iDnSize() ([4]byte, uint32, error) {
	var ID [4]byte
	var blockSize uint32
	if d.err = binary.Read(d.r, binary.BigEndian, &ID); d.err != nil {
		return ID, blockSize, d.err
	}
	if d.err = binary.Read(d.r, binary.BigEndian, &blockSize); d.err != nil {
		return ID, blockSize, d.err
	}
	return ID, blockSize, nil
}

// jumpTo advances the reader to the amount of bytes provided
func (d *Decoder) jumpTo(bytesAhead int) error {
	var err error
	if bytesAhead > 0 {
		_, err = io.CopyN(ioutil.Discard, d.r, int64(bytesAhead))
	}
	return err
}
<|endoftext|>


<commit_before>package archiver

import (
	"archive/zip"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strconv"

	"github.com/go-errors/errors"
	"github.com/itchio/wharf/counter"
	"github.com/itchio/wharf/state"
)

func ExtractZip(readerAt io.ReaderAt, size int64, dir string, settings ExtractSettings) (*ExtractResult, error) {
	dirCount := 0
	regCount := 0
	symlinkCount := 0

	reader, err := zip.NewReader(readerAt, size)
	if err != nil {
		return nil, errors.Wrap(err, 1)
	}

	var totalSize int64
	for _, file := range reader.File {
		totalSize += int64(file.UncompressedSize64)
	}

	if settings.OnUncompressedSizeKnown != nil {
		settings.OnUncompressedSizeKnown(totalSize)
	}

	var doneSize uint64
	var lastDoneIndex int

	func() {
		if settings.ResumeFrom == "" {
			return
		}

		resBytes, resErr := ioutil.ReadFile(settings.ResumeFrom)
		if resErr != nil {
			settings.Consumer.Warnf("Couldn't read resume file: %s", resErr.Error())
			return
		}

		lastDone64, resErr := strconv.ParseInt(string(resBytes), 10, 64)
		if resErr != nil {
			settings.Consumer.Warnf("Couldn't read resume file: %s", resErr.Error())
			return
		}

		lastDoneIndex = int(lastDone64)
	}()

	writeProgress := func(fileIndex int) {
		if settings.ResumeFrom == "" {
			return
		}

		payload := fmt.Sprintf("%d", fileIndex)

		wErr := ioutil.WriteFile(settings.ResumeFrom, []byte(payload), 0644)
		if wErr != nil {
			settings.Consumer.Warnf("Couldn't save resume file: %s", err.Error())
			return
		}
	}

	for fileIndex, file := range reader.File {
		if fileIndex <= lastDoneIndex {
			continue
		}

		err = func() error {
			rel := file.Name
			filename := path.Join(dir, filepath.FromSlash(rel))

			info := file.FileInfo()
			mode := info.Mode()

			if info.IsDir() {
				err = Mkdir(filename)
				if err != nil {
					return errors.Wrap(err, 1)
				}
				dirCount++
			} else if mode&os.ModeSymlink > 0 {
				fileReader, fErr := file.Open()
				if fErr != nil {
					return errors.Wrap(fErr, 1)
				}
				defer fileReader.Close()

				linkname, lErr := ioutil.ReadAll(fileReader)
				lErr = Symlink(string(linkname), filename, settings.Consumer)
				if lErr != nil {
					return errors.Wrap(err, 1)
				}
				symlinkCount++
			} else {
				regCount++

				fileReader, fErr := file.Open()
				if fErr != nil {
					return errors.Wrap(fErr, 1)
				}
				defer fileReader.Close()

				settings.Consumer.Debugf("extract %s", filename)
				countingReader := counter.NewReaderCallback(func(offset int64) {
					currentSize := int64(doneSize) + offset
					settings.Consumer.Progress(float64(currentSize) / float64(totalSize))
				}, fileReader)

				err = CopyFile(filename, os.FileMode(mode&LuckyMode|ModeMask), countingReader)
				if err != nil {
					return errors.Wrap(err, 1)
				}
			}

			return nil
		}()
		if err != nil {
			return nil, errors.Wrap(err, 1)
		}

		doneSize += file.UncompressedSize64
		settings.Consumer.Progress(float64(doneSize) / float64(totalSize))

		writeProgress(fileIndex)
	}

	return &ExtractResult{
		Dirs:     dirCount,
		Files:    regCount,
		Symlinks: symlinkCount,
	}, nil
}

func CompressZip(archiveWriter io.Writer, dir string, consumer *state.Consumer) (*CompressResult, error) {
	var err error
	var uncompressedSize int64
	var compressedSize int64

	archiveCounter := counter.NewWriter(archiveWriter)

	zipWriter := zip.NewWriter(archiveCounter)
	defer zipWriter.Close()
	defer func() {
		if zipWriter != nil {
			if zErr := zipWriter.Close(); err == nil && zErr != nil {
				err = errors.Wrap(zErr, 1)
			}
		}
	}()

	err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		name, wErr := filepath.Rel(dir, path)
		if wErr != nil {
			return wErr
		}

		if name == "." {
			// don't add '.' to zip
			return nil
		}

		name = filepath.ToSlash(name)

		fh, wErr := zip.FileInfoHeader(info)
		if wErr != nil {
			return wErr
		}

		fh.Name = name

		writer, wErr := zipWriter.CreateHeader(fh)
		if wErr != nil {
			return wErr
		}

		if info.IsDir() {
			// good!
		} else if info.Mode()&os.ModeSymlink > 0 {
			dest, wErr := os.Readlink(path)
			if wErr != nil {
				return wErr
			}

			_, wErr = writer.Write([]byte(dest))
			if wErr != nil {
				return wErr
			}
		} else if info.Mode().IsRegular() {
			reader, wErr := os.Open(path)
			if wErr != nil {
				return wErr
			}
			defer reader.Close()

			copiedBytes, wErr := io.Copy(writer, reader)
			if wErr != nil {
				return wErr
			}

			uncompressedSize += copiedBytes
		}

		return nil
	})

	err = zipWriter.Close()
	if err != nil {
		return nil, errors.Wrap(err, 1)
	}
	zipWriter = nil

	compressedSize = archiveCounter.Count()

	return &CompressResult{
		UncompressedSize: uncompressedSize,
		CompressedSize:   compressedSize,
	}, err
}
<commit_msg>zip: Count skipped files as progress, delete resume-file when done<commit_after>package archiver

import (
	"archive/zip"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strconv"

	"github.com/go-errors/errors"
	"github.com/itchio/wharf/counter"
	"github.com/itchio/wharf/state"
)

func ExtractZip(readerAt io.ReaderAt, size int64, dir string, settings ExtractSettings) (*ExtractResult, error) {
	dirCount := 0
	regCount := 0
	symlinkCount := 0

	reader, err := zip.NewReader(readerAt, size)
	if err != nil {
		return nil, errors.Wrap(err, 1)
	}

	var totalSize int64
	for _, file := range reader.File {
		totalSize += int64(file.UncompressedSize64)
	}

	var doneSize uint64
	var lastDoneIndex int

	func() {
		if settings.ResumeFrom == "" {
			return
		}

		resBytes, resErr := ioutil.ReadFile(settings.ResumeFrom)
		if resErr != nil {
			if !errors.Is(resErr, os.ErrNotExist) {
				settings.Consumer.Warnf("Couldn't read resume file: %s", resErr.Error())
			}
			return
		}

		lastDone64, resErr := strconv.ParseInt(string(resBytes), 10, 64)
		if resErr != nil {
			settings.Consumer.Warnf("Couldn't parse resume file: %s", resErr.Error())
			return
		}

		lastDoneIndex = int(lastDone64)
		settings.Consumer.Infof("Resuming from file %d", lastDoneIndex)
	}()

	warnedAboutWrite := false

	writeProgress := func(fileIndex int) {
		if settings.ResumeFrom == "" {
			return
		}

		payload := fmt.Sprintf("%d", fileIndex)

		wErr := ioutil.WriteFile(settings.ResumeFrom, []byte(payload), 0644)
		if wErr != nil {
			if !warnedAboutWrite {
				warnedAboutWrite = true
				settings.Consumer.Warnf("Couldn't save resume file: %s", wErr.Error())
			}
			return
		}
	}

	defer func() {
		if settings.ResumeFrom == "" {
			return
		}

		rErr := os.Remove(settings.ResumeFrom)
		if rErr != nil {
			settings.Consumer.Warnf("Couldn't remove resume file: %s", rErr.Error())
		}
	}()

	if settings.OnUncompressedSizeKnown != nil {
		settings.OnUncompressedSizeKnown(totalSize)
	}

	for fileIndex, file := range reader.File {
		if fileIndex <= lastDoneIndex {
			settings.Consumer.Debugf("Skipping file %d")
			doneSize += file.UncompressedSize64
			settings.Consumer.Progress(float64(doneSize) / float64(totalSize))
			continue
		}

		err = func() error {
			rel := file.Name
			filename := path.Join(dir, filepath.FromSlash(rel))

			info := file.FileInfo()
			mode := info.Mode()

			if info.IsDir() {
				err = Mkdir(filename)
				if err != nil {
					return errors.Wrap(err, 1)
				}
				dirCount++
			} else if mode&os.ModeSymlink > 0 {
				fileReader, fErr := file.Open()
				if fErr != nil {
					return errors.Wrap(fErr, 1)
				}
				defer fileReader.Close()

				linkname, lErr := ioutil.ReadAll(fileReader)
				lErr = Symlink(string(linkname), filename, settings.Consumer)
				if lErr != nil {
					return errors.Wrap(err, 1)
				}
				symlinkCount++
			} else {
				regCount++

				fileReader, fErr := file.Open()
				if fErr != nil {
					return errors.Wrap(fErr, 1)
				}
				defer fileReader.Close()

				settings.Consumer.Debugf("extract %s", filename)
				countingReader := counter.NewReaderCallback(func(offset int64) {
					currentSize := int64(doneSize) + offset
					settings.Consumer.Progress(float64(currentSize) / float64(totalSize))
				}, fileReader)

				err = CopyFile(filename, os.FileMode(mode&LuckyMode|ModeMask), countingReader)
				if err != nil {
					return errors.Wrap(err, 1)
				}
			}

			return nil
		}()
		if err != nil {
			return nil, errors.Wrap(err, 1)
		}

		doneSize += file.UncompressedSize64
		settings.Consumer.Progress(float64(doneSize) / float64(totalSize))
		writeProgress(fileIndex)
	}

	return &ExtractResult{
		Dirs:     dirCount,
		Files:    regCount,
		Symlinks: symlinkCount,
	}, nil
}

func CompressZip(archiveWriter io.Writer, dir string, consumer *state.Consumer) (*CompressResult, error) {
	var err error
	var uncompressedSize int64
	var compressedSize int64

	archiveCounter := counter.NewWriter(archiveWriter)

	zipWriter := zip.NewWriter(archiveCounter)
	defer zipWriter.Close()
	defer func() {
		if zipWriter != nil {
			if zErr := zipWriter.Close(); err == nil && zErr != nil {
				err = errors.Wrap(zErr, 1)
			}
		}
	}()

	err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		name, wErr := filepath.Rel(dir, path)
		if wErr != nil {
			return wErr
		}

		if name == "." {
			// don't add '.' to zip
			return nil
		}

		name = filepath.ToSlash(name)

		fh, wErr := zip.FileInfoHeader(info)
		if wErr != nil {
			return wErr
		}

		fh.Name = name

		writer, wErr := zipWriter.CreateHeader(fh)
		if wErr != nil {
			return wErr
		}

		if info.IsDir() {
			// good!
		} else if info.Mode()&os.ModeSymlink > 0 {
			dest, wErr := os.Readlink(path)
			if wErr != nil {
				return wErr
			}

			_, wErr = writer.Write([]byte(dest))
			if wErr != nil {
				return wErr
			}
		} else if info.Mode().IsRegular() {
			reader, wErr := os.Open(path)
			if wErr != nil {
				return wErr
			}
			defer reader.Close()

			copiedBytes, wErr := io.Copy(writer, reader)
			if wErr != nil {
				return wErr
			}

			uncompressedSize += copiedBytes
		}

		return nil
	})

	err = zipWriter.Close()
	if err != nil {
		return nil, errors.Wrap(err, 1)
	}
	zipWriter = nil

	compressedSize = archiveCounter.Count()

	return &CompressResult{
		UncompressedSize: uncompressedSize,
		CompressedSize:   compressedSize,
	}, err
}
<|endoftext|>


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

package cmd

import (
	"io"

	jujucmd "github.com/juju/cmd"
	"github.com/juju/errors"
	"github.com/juju/testing"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
)

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

type UploadSuite struct {
	testing.IsolationSuite

	stubDeps *stubUploadDeps
}

func (s *UploadSuite) SetUpTest(c *gc.C) {
	s.IsolationSuite.SetUpTest(c)

	stub := &testing.Stub{}
	s.stubDeps = &stubUploadDeps{
		stub:   stub,
		file:   &stubFile{stub: stub},
		client: &stubCharmStore{stub: stub},
	}
}

func (*UploadSuite) TestInitEmpty(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{})
	c.Assert(err, jc.Satisfies, errors.IsBadRequest)
}

func (*UploadSuite) TestInitOneArg(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}
	err := u.Init([]string{"foo"})
	c.Assert(err, jc.Satisfies, errors.IsBadRequest)
}

func (*UploadSuite) TestInitJustName(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{"foo", "bar"})
	c.Assert(err, jc.Satisfies, errors.IsNotValid)
}

func (*UploadSuite) TestInitDuplicate(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{"foo", "foo=bar", "foo=baz"})
	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsAlreadyExists)
}

func (*UploadSuite) TestInitNoName(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{"foo", "=foobar"})
	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotValid)
}

func (*UploadSuite) TestInitNoPath(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{"foo", "foobar="})
	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotValid)
}

func (*UploadSuite) TestInitGood(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{"foo", "bar=baz"})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(u.resources, gc.DeepEquals, map[string]string{"bar": "baz"})
	c.Assert(u.service, gc.Equals, "foo")
}

func (*UploadSuite) TestInitTwoResources(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{"foo", "bar=baz", "fizz=buzz"})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(u.resources, gc.DeepEquals, map[string]string{
		"bar":  "baz",
		"fizz": "buzz",
	})
	c.Assert(u.service, gc.Equals, "foo")
}

func (s *UploadSuite) TestInfo(c *gc.C) {
	var command UploadCommand
	info := command.Info()

	c.Check(info, jc.DeepEquals, &jujucmd.Info{
		Name:    "upload",
		Args:    "service name=file [name2=file2 ...]",
		Purpose: "upload a file as a resource for a service",
		Doc: `
This command uploads a file from your local disk to the juju controller to be
used as a resource for a service.
`,
	})
}

func (s *UploadSuite) TestRun(c *gc.C) {
	u := UploadCommand{
		deps: UploadDeps{
			NewClient:    s.stubDeps.NewClient,
			OpenResource: s.stubDeps.OpenResource,
		},
		resources: map[string]string{"foo": "bar", "baz": "bat"},
		service:   "svc",
	}

	err := u.Run(nil)
	c.Assert(err, jc.ErrorIsNil)

	checkCall(c, s.stubDeps.stub, "OpenResource", [][]interface{}{
		{"bar"},
		{"bat"},
	})
	checkCall(c, s.stubDeps.stub, "Upload", [][]interface{}{
		{"svc", "foo", s.stubDeps.file},
		{"svc", "baz", s.stubDeps.file},
	})
}

// checkCall checks that the given function has been called exactly len(args)
// times, and that the args passed to the Nth call match args[N].
func checkCall(c *gc.C, stub *testing.Stub, funcname string, args [][]interface{}) {
	var actual [][]interface{}
	for _, call := range stub.Calls() {
		if call.FuncName == funcname {
			actual = append(actual, call.Args)
		}
	}
	c.Assert(actual, jc.DeepEquals, args)
}

type stubUploadDeps struct {
	stub   *testing.Stub
	file   *stubFile
	client *stubCharmStore
}

func (s *stubUploadDeps) NewClient(c *UploadCommand) (UploadClient, error) {
	s.stub.AddCall("NewClient", c)
	if err := s.stub.NextErr(); err != nil {
		return nil, errors.Trace(err)
	}

	return s.client, nil
}

func (s *stubUploadDeps) OpenResource(path string) (io.ReadCloser, error) {
	s.stub.AddCall("OpenResource", path)
	if err := s.stub.NextErr(); err != nil {
		return nil, errors.Trace(err)
	}

	return s.file, nil
}

type stubFile struct {
	// No one actually tries to read from this during tests.
	io.Reader
	stub *testing.Stub
}

func (s *stubFile) Close() error {
	s.stub.AddCall("FileClose")
	return errors.Trace(s.stub.NextErr())
}
<commit_msg>Fix test ordering.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package cmd

import (
	"io"
	"reflect"

	jujucmd "github.com/juju/cmd"
	"github.com/juju/errors"
	"github.com/juju/testing"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
)

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

type UploadSuite struct {
	testing.IsolationSuite

	stubDeps *stubUploadDeps
}

func (s *UploadSuite) SetUpTest(c *gc.C) {
	s.IsolationSuite.SetUpTest(c)

	stub := &testing.Stub{}
	s.stubDeps = &stubUploadDeps{
		stub:   stub,
		file:   &stubFile{stub: stub},
		client: &stubCharmStore{stub: stub},
	}
}

func (*UploadSuite) TestInitEmpty(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{})
	c.Assert(err, jc.Satisfies, errors.IsBadRequest)
}

func (*UploadSuite) TestInitOneArg(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}
	err := u.Init([]string{"foo"})
	c.Assert(err, jc.Satisfies, errors.IsBadRequest)
}

func (*UploadSuite) TestInitJustName(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{"foo", "bar"})
	c.Assert(err, jc.Satisfies, errors.IsNotValid)
}

func (*UploadSuite) TestInitDuplicate(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{"foo", "foo=bar", "foo=baz"})
	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsAlreadyExists)
}

func (*UploadSuite) TestInitNoName(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{"foo", "=foobar"})
	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotValid)
}

func (*UploadSuite) TestInitNoPath(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{"foo", "foobar="})
	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotValid)
}

func (*UploadSuite) TestInitGood(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{"foo", "bar=baz"})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(u.resources, gc.DeepEquals, map[string]string{"bar": "baz"})
	c.Assert(u.service, gc.Equals, "foo")
}

func (*UploadSuite) TestInitTwoResources(c *gc.C) {
	u := UploadCommand{resources: map[string]string{}}

	err := u.Init([]string{"foo", "bar=baz", "fizz=buzz"})
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(u.resources, gc.DeepEquals, map[string]string{
		"bar":  "baz",
		"fizz": "buzz",
	})
	c.Assert(u.service, gc.Equals, "foo")
}

func (s *UploadSuite) TestInfo(c *gc.C) {
	var command UploadCommand
	info := command.Info()

	c.Check(info, jc.DeepEquals, &jujucmd.Info{
		Name:    "upload",
		Args:    "service name=file [name2=file2 ...]",
		Purpose: "upload a file as a resource for a service",
		Doc: `
This command uploads a file from your local disk to the juju controller to be
used as a resource for a service.
`,
	})
}

func (s *UploadSuite) TestRun(c *gc.C) {
	u := UploadCommand{
		deps: UploadDeps{
			NewClient:    s.stubDeps.NewClient,
			OpenResource: s.stubDeps.OpenResource,
		},
		resources: map[string]string{"foo": "bar", "baz": "bat"},
		service:   "svc",
	}

	err := u.Run(nil)
	c.Assert(err, jc.ErrorIsNil)

	checkCall(c, s.stubDeps.stub, "OpenResource", [][]interface{}{
		{"bar"},
		{"bat"},
	})
	checkCall(c, s.stubDeps.stub, "Upload", [][]interface{}{
		{"svc", "foo", s.stubDeps.file},
		{"svc", "baz", s.stubDeps.file},
	})
}

// checkCall checks that the given function has been called exactly len(args)
// times, and that the args passed to the Nth call match expected[N].
func checkCall(c *gc.C, stub *testing.Stub, funcname string, expected [][]interface{}) {
	var actual [][]interface{}
	for _, call := range stub.Calls() {
		if call.FuncName == funcname {
			actual = append(actual, call.Args)
		}
	}
	checkSameContent(c, actual, expected)
}

func checkSameContent(c *gc.C, actual, expected [][]interface{}) {
	for i, args := range actual {
		if len(expected) == 0 {
			c.Check(actual[i:], gc.HasLen, 0, gc.Commentf("unexpected call"))
			break
		}
		matched := false
		for j, expect := range expected {
			if reflect.DeepEqual(args, expect) {
				expected = append(expected[:j], expected[j+1:]...)
				matched = true
				break
			}
		}
		c.Check(matched, jc.IsTrue, gc.Commentf("extra call %#v", args))
	}
	c.Check(expected, gc.HasLen, 0, gc.Commentf("unmatched calls %#v", expected))
}

type stubUploadDeps struct {
	stub   *testing.Stub
	file   *stubFile
	client *stubCharmStore
}

func (s *stubUploadDeps) NewClient(c *UploadCommand) (UploadClient, error) {
	s.stub.AddCall("NewClient", c)
	if err := s.stub.NextErr(); err != nil {
		return nil, errors.Trace(err)
	}

	return s.client, nil
}

func (s *stubUploadDeps) OpenResource(path string) (io.ReadCloser, error) {
	s.stub.AddCall("OpenResource", path)
	if err := s.stub.NextErr(); err != nil {
		return nil, errors.Trace(err)
	}

	return s.file, nil
}

type stubFile struct {
	// No one actually tries to read from this during tests.
	io.Reader
	stub *testing.Stub
}

func (s *stubFile) Close() error {
	s.stub.AddCall("FileClose")
	return errors.Trace(s.stub.NextErr())
}
<|endoftext|>


<commit_before>package aws

import (
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ecs"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsEcsCluster() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsEcsClusterCreate,
		Read:   resourceAwsEcsClusterRead,
		Delete: resourceAwsEcsClusterDelete,

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

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

	clusterName := d.Get("name").(string)
	log.Printf("[DEBUG] Creating ECS cluster %s", clusterName)

	out, err := conn.CreateCluster(&ecs.CreateClusterInput{
		ClusterName: aws.String(clusterName),
	})
	if err != nil {
		return err
	}
	log.Printf("[DEBUG] ECS cluster %s created", *out.Cluster.ClusterArn)

	d.SetId(*out.Cluster.ClusterArn)
	d.Set("name", *out.Cluster.ClusterName)
	return nil
}

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

	clusterName := d.Get("name").(string)
	log.Printf("[DEBUG] Reading ECS cluster %s", clusterName)
	out, err := conn.DescribeClusters(&ecs.DescribeClustersInput{
		Clusters: []*string{aws.String(clusterName)},
	})
	if err != nil {
		return err
	}
	log.Printf("[DEBUG] Received ECS clusters: %s", out.Clusters)

	d.SetId(*out.Clusters[0].ClusterArn)
	d.Set("name", *out.Clusters[0].ClusterName)

	return nil
}

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

	log.Printf("[DEBUG] Deleting ECS cluster %s", d.Id())

	return resource.Retry(10*time.Minute, func() error {
		out, err := conn.DeleteCluster(&ecs.DeleteClusterInput{
			Cluster: aws.String(d.Id()),
		})

		if err == nil {
			log.Printf("[DEBUG] ECS cluster %s deleted: %s", d.Id(), out)
			return nil
		}

		awsErr, ok := err.(awserr.Error)
		if !ok {
			return resource.RetryError{Err: err}
		}

		if awsErr.Code() == "ClusterContainsContainerInstancesException" {
			log.Printf("[TRACE] Retrying ECS cluster %q deletion after %q", d.Id(), awsErr.Code())
			return err
		}

		if awsErr.Code() == "ClusterContainsServicesException" {
			log.Printf("[TRACE] Retrying ECS cluster %q deletion after %q", d.Id(), awsErr.Code())
			return err
		}

		return resource.RetryError{Err: err}
	})
}
<commit_msg>Fix a panic that could occur if no ECS Cluster was found for a given cluster name<commit_after>package aws

import (
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ecs"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsEcsCluster() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsEcsClusterCreate,
		Read:   resourceAwsEcsClusterRead,
		Delete: resourceAwsEcsClusterDelete,

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

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

	clusterName := d.Get("name").(string)
	log.Printf("[DEBUG] Creating ECS cluster %s", clusterName)

	out, err := conn.CreateCluster(&ecs.CreateClusterInput{
		ClusterName: aws.String(clusterName),
	})
	if err != nil {
		return err
	}
	log.Printf("[DEBUG] ECS cluster %s created", *out.Cluster.ClusterArn)

	d.SetId(*out.Cluster.ClusterArn)
	d.Set("name", *out.Cluster.ClusterName)
	return nil
}

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

	clusterName := d.Get("name").(string)
	log.Printf("[DEBUG] Reading ECS cluster %s", clusterName)
	out, err := conn.DescribeClusters(&ecs.DescribeClustersInput{
		Clusters: []*string{aws.String(clusterName)},
	})
	if err != nil {
		return err
	}
	log.Printf("[DEBUG] Received ECS clusters: %s", out.Clusters)

	for _, c := range out.Clusters {
		if *c.ClusterName == clusterName {
			d.SetId(*c.ClusterArn)
			d.Set("name", c.ClusterName)
			return nil
		}
	}

	log.Printf("[ERR] No matching ECS Cluster found for (%s)", d.Id())
	d.SetId("")
	return nil
}

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

	log.Printf("[DEBUG] Deleting ECS cluster %s", d.Id())

	return resource.Retry(10*time.Minute, func() error {
		out, err := conn.DeleteCluster(&ecs.DeleteClusterInput{
			Cluster: aws.String(d.Id()),
		})

		if err == nil {
			log.Printf("[DEBUG] ECS cluster %s deleted: %s", d.Id(), out)
			return nil
		}

		awsErr, ok := err.(awserr.Error)
		if !ok {
			return resource.RetryError{Err: err}
		}

		if awsErr.Code() == "ClusterContainsContainerInstancesException" {
			log.Printf("[TRACE] Retrying ECS cluster %q deletion after %q", d.Id(), awsErr.Code())
			return err
		}

		if awsErr.Code() == "ClusterContainsServicesException" {
			log.Printf("[TRACE] Retrying ECS cluster %q deletion after %q", d.Id(), awsErr.Code())
			return err
		}

		return resource.RetryError{Err: err}
	})
}
<|endoftext|>