<commit_before>package collision

import (
	"testing"

	"github.com/oakmound/oak/v2/alg/floatgeom"

	"github.com/oakmound/oak/v2/physics"
	"github.com/stretchr/testify/assert"
)

func TestSpaceFuncs(t *testing.T) {
	Clear()
	s := NewUnassignedSpace(10, 10, 10, 10)
	assert.NotEmpty(t, s.String())

	// Getters
	cx, cy := s.GetCenter()
	assert.Equal(t, cx, float64(15))
	assert.Equal(t, cy, float64(15))
	x, y := s.GetPos()
	assert.Equal(t, x, float64(10))
	assert.Equal(t, y, float64(10))

	// Positional comparison
	s2 := NewUnassignedSpace(20, 20, 10, 10)
	assert.True(t, s2.Above(s) < 0)
	assert.True(t, s2.Below(s) > 0)
	assert.True(t, s2.LeftOf(s) < 0)
	assert.True(t, s2.RightOf(s) > 0)

	// Containment
	assert.False(t, s2.Contains(s))
	s3 := NewUnassignedSpace(5, 5, 20, 20)
	assert.True(t, s3.Contains(s))
	s4 := NewUnassignedSpace(15, 15, 10, 10)

	// Overlap
	xover, yover := s4.Overlap(s)
	assert.Equal(t, xover, -5.0)
	assert.Equal(t, yover, -5.0)
	xover, yover = s.Overlap(s4)
	assert.Equal(t, xover, 5.0)
	assert.Equal(t, yover, 5.0)
	xover, yover = s.Overlap(s2)
	assert.Equal(t, xover, 0.0)
	assert.Equal(t, yover, 0.0)
	ov := s.OverlapVector(s4)
	assert.Equal(t, ov, physics.NewVector(5, 5))
	spaces := s.SubtractRect(1, 1, 8, 8)
	assert.Equal(t, len(spaces), 4)
}

func TestNewRect(t *testing.T) {
	s := NewUnassignedSpace(0, 0, 0, 0)
	assert.Equal(t, 1.0, s.GetW())
	assert.Equal(t, 1.0, s.GetH())
	s = NewUnassignedSpace(0, 0, -10, -10)
	assert.Equal(t, 10.0, s.GetW())
	assert.Equal(t, 10.0, s.GetH())
	s = NewRectSpace(floatgeom.NewRect3WH(0, 0, 0, 10, 10, 0), 0, 0)
	assert.Equal(t, 10.0, s.GetW())
	assert.Equal(t, 10.0, s.GetH())
}
<commit_msg>Expand collision package test coverage<commit_after>package collision

import (
	"testing"

	"github.com/oakmound/oak/v2/alg/floatgeom"

	"github.com/oakmound/oak/v2/physics"
	"github.com/stretchr/testify/assert"
)

func TestSpaceFuncs(t *testing.T) {
	Clear()
	s := NewUnassignedSpace(10, 10, 10, 10)
	assert.NotEmpty(t, s.String())
	if s.W() != 10.0 {
		t.Fatalf("expected 10 width, got %v", s.W())
	}
	if s.H() != 10.0 {
		t.Fatalf("expected 10 height, got %v", s.H())
	}

	// Getters
	cx, cy := s.GetCenter()
	assert.Equal(t, cx, float64(15))
	assert.Equal(t, cy, float64(15))
	x, y := s.GetPos()
	assert.Equal(t, x, float64(10))
	assert.Equal(t, y, float64(10))

	// Positional comparison
	s2 := NewUnassignedSpace(20, 20, 10, 10)
	assert.True(t, s2.Above(s) < 0)
	assert.True(t, s2.Below(s) > 0)
	assert.True(t, s2.LeftOf(s) < 0)
	assert.True(t, s2.RightOf(s) > 0)

	// Containment
	assert.False(t, s2.Contains(s))
	s3 := NewUnassignedSpace(5, 5, 20, 20)
	assert.True(t, s3.Contains(s))
	s4 := NewUnassignedSpace(15, 15, 10, 10)

	// Overlap
	xover, yover := s4.Overlap(s)
	assert.Equal(t, xover, -5.0)
	assert.Equal(t, yover, -5.0)
	xover, yover = s.Overlap(s4)
	assert.Equal(t, xover, 5.0)
	assert.Equal(t, yover, 5.0)
	xover, yover = s.Overlap(s2)
	assert.Equal(t, xover, 0.0)
	assert.Equal(t, yover, 0.0)
	ov := s.OverlapVector(s4)
	assert.Equal(t, ov, physics.NewVector(5, 5))
	spaces := s.SubtractRect(1, 1, 8, 8)
	assert.Equal(t, len(spaces), 4)
}

func TestNewRect(t *testing.T) {
	s := NewUnassignedSpace(0, 0, 0, 0)
	assert.Equal(t, 1.0, s.GetW())
	assert.Equal(t, 1.0, s.GetH())
	s = NewUnassignedSpace(0, 0, -10, -10)
	assert.Equal(t, 10.0, s.GetW())
	assert.Equal(t, 10.0, s.GetH())
	s = NewRectSpace(floatgeom.NewRect3WH(0, 0, 0, 10, 10, 0), 0, 0)
	assert.Equal(t, 10.0, s.GetW())
	assert.Equal(t, 10.0, s.GetH())
}

func TestNewRect2Space(t *testing.T) {
	s1 := NewRect2Space(floatgeom.Rect2{
		Min: floatgeom.Point2{5, 10},
		Max: floatgeom.Point2{10, 15},
	}, 0)
	s2 := NewUnassignedSpace(5, 10, 5, 5)
	if s1.X() != s2.X() {
		t.Fatalf("mismatched X: %v vs %v", s1.X(), s2.X())
	}
	if s1.Y() != s2.Y() {
		t.Fatalf("mismatched Y: %v vs %v", s1.Y(), s2.Y())
	}
	if s1.W() != s2.W() {
		t.Fatalf("mismatched W: %v vs %v", s1.W(), s2.W())
	}
	if s1.H() != s2.H() {
		t.Fatalf("mismatched H: %v vs %v", s1.H(), s2.H())
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/pmylund/go-cache"
)

const (
	dataFileExt = ".data"
)

type Sample struct {
	ts int64
	v  float64
}

type perfDB struct {
	baseDir string
	mu      sync.Mutex
}

var timestampCache *cache.Cache

func newPerfDB(baseDir string) (*perfDB, error) {
	if err := os.MkdirAll(baseDir, 0755); err != nil {
		logger.Critical("Failed to initialize datastore: %s", err)
		return nil, err
	}
	timestampCache = cache.New(cache.NoExpiration, cache.NoExpiration)
	return &perfDB{baseDir, sync.Mutex{}}, nil
}

func (pdb *perfDB) getDirPath(dbname string) string {
	return filepath.Join(pdb.baseDir, dbname)
}

func (pdb *perfDB) getFilePath(dbname, metric string) string {
	dataDir := pdb.getDirPath(dbname)

	return filepath.Join(dataDir, metric+dataFileExt)
}

func (pdb *perfDB) checkDbExists(dbname string) error {
	dataDir := pdb.getDirPath(dbname)

	_, err := os.Stat(dataDir)
	return err
}

func (pdb *perfDB) checkMetricExists(dbname string, metric string) error {
	dataFile := pdb.getFilePath(dbname, metric)

	_, err := os.Stat(dataFile)
	return err
}

func parseRecord(record string) (Sample, error) {
	fields := strings.Fields(record)

	ts, err := strconv.ParseInt(fields[0], 10, 64)
	if err != nil {
		return Sample{}, err
	}

	value, err := strconv.ParseFloat(fields[1], 64)
	if err != nil {
		return Sample{}, err
	}
	return Sample{ts, value}, nil
}

func readTimestamp(dataFile string) (int64, error) {
	if cachedData, found := timestampCache.Get(dataFile); found {
		return cachedData.(int64), nil
	}

	record, err := ioutil.ReadFile(dataFile)
	if err != nil {
		return 0, err
	}

	return strconv.ParseInt(string(record), 10, 64)
}

func storeTimestamp(dataFile string, timestamp int64) error {
	file, err := os.OpenFile(dataFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	if _, err = fmt.Fprint(file, timestamp); err != nil {
		return err
	}
	timestampCache.Set(dataFile, timestamp, time.Minute)
	return nil
}

func storeSample(dataFile string, sample Sample) error {
	file, err := os.OpenFile(dataFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = fmt.Fprintf(file, "%d %v\n", sample.ts, sample.v)
	return err
}

func initStore(dataFile string, sample Sample) error {
	if err := storeTimestamp(dataFile+".1", sample.ts); err != nil {
		return err
	}
	if err := storeTimestamp(dataFile+".n", sample.ts); err != nil {
		return err
	}

	sample.ts = 0
	return storeSample(dataFile, sample)
}

func appendSample(dataFile string, sample Sample) error {
	ts, err := readTimestamp(dataFile + ".n")
	if err != nil {
		return err
	}
	if err := storeTimestamp(dataFile+".n", sample.ts); err != nil {
		return err
	}
	sample.ts -= ts
	return storeSample(dataFile, sample)
}

func (pdb *perfDB) addSample(dbname, metric string, sample Sample) error {
	dataDir := pdb.getDirPath(dbname)
	if err := os.MkdirAll(dataDir, 0775); err != nil {
		return err
	}
	dataFile := pdb.getFilePath(dbname, metric)

	pdb.mu.Lock()
	defer pdb.mu.Unlock()

	_, err := os.Stat(dataFile + ".n")
	if err == nil {
		return appendSample(dataFile, sample)
	} else if os.IsNotExist(err) {
		return initStore(dataFile, sample)
	}
	return err
}

func (pdb *perfDB) listDatabases() ([]string, error) {
	files, err := ioutil.ReadDir(pdb.baseDir)
	if err != nil {
		return nil, err
	}

	databases := []string{}
	for _, f := range files {
		databases = append(databases, f.Name())
	}
	return databases, nil
}

func (pdb *perfDB) listMetrics(dbname string) []string {
	dataDir := pdb.getDirPath(dbname)

	matches, _ := filepath.Glob(dataDir + "/*.data") // Glob ignores file system errors

	metrics := []string{}
	for _, match := range matches {
		name := strings.TrimPrefix(match, dataDir+"/")
		name = strings.TrimRight(name, dataFileExt)
		metrics = append(metrics, name)
	}
	return metrics
}

const bufferSize = 1000

func readDeltas(fileName string, done <-chan struct{}) (<-chan string, <-chan error) {
	samples := make(chan string, bufferSize)
	errc := make(chan error, 1)

	go func() {
		defer close(samples)
		defer close(errc)

		file, err := os.Open(fileName)
		if err != nil {
			errc <- err
			return
		}
		defer file.Close()

		scanner := bufio.NewScanner(file)
		for scanner.Scan() {
			select {
			case samples <- scanner.Text():
			case <-done:
				return
			}
		}

		if err := scanner.Err(); err != nil {
			errc <- err
		}
	}()
	return samples, errc
}

func parseSamples(records <-chan string, ts int64) (<-chan Sample, <-chan error) {
	samples := make(chan Sample, bufferSize)
	errc := make(chan error, 1)

	go func() {
		defer close(samples)
		defer close(errc)

		for record := range records {
			sample, err := parseRecord(record)
			if err != nil {
				errc <- err
			} else {
				sample.ts += ts
				ts = sample.ts
				samples <- sample
			}
		}
	}()
	return samples, errc
}

func mergeErrors(errcs ...<-chan error) error {
	for _, errc := range errcs {
		if err := <-errc; err != nil {
			return err
		}
	}
	return nil
}

func (pdb *perfDB) getRawValues(dbname, metric string) ([][]interface{}, error) {
	dataFile := pdb.getFilePath(dbname, metric)

	first, err := readTimestamp(dataFile + ".1")
	if err != nil {
		return nil, err
	}

	done := make(chan struct{}, 1)
	rawSamples, rawErrors := readDeltas(dataFile, done)
	parsedSamples, parsedErrors := parseSamples(rawSamples, first)

	values := [][]interface{}{}
	for sample := range parsedSamples {
		values = append(values, []interface{}{sample.ts, sample.v})
	}

	done <- struct{}{}
	if err := mergeErrors(rawErrors, parsedErrors); err != nil {
		return nil, err
	}
	return values, nil
}

func (pdb *perfDB) getSummary(dbname, metric string) (map[string]interface{}, error) {
	var summary map[string]interface{}

	dataFile := pdb.getFilePath(dbname, metric)

	done := make(chan struct{}, 1)
	defer close(done)

	first, err := readTimestamp(dataFile + ".1")
	if err != nil {
		return nil, err
	}

	rawSamples, rawErrors := readDeltas(dataFile, done)
	parsedSamples, parsedErrors := parseSamples(rawSamples, first)

	values := []float64{}
	sum := 0.0
	for sample := range parsedSamples {
		sum += sample.v
		values = append(values, sample.v)
	}

	done <- struct{}{}
	if err := mergeErrors(rawErrors, parsedErrors); err != nil {
		return nil, err
	}

	count := len(values)
	sort.Float64s(values)

	summary = map[string]interface{}{
		"max":   values[count-1],
		"min":   values[0],
		"count": count,
		"avg":   sum / float64(count),
	}

	for _, percentile := range []float64{0.5, 0.8, 0.9, 0.95, 0.99, 0.999} {
		var pIdx int
		if count > 1 {
			pIdx = int(float64(count)*percentile) - 1
		}
		p := fmt.Sprintf("p%v", percentile*100)
		summary[p] = values[pIdx]
	}

	return summary, nil
}

func (pdb *perfDB) getHeatMap(dbname, metric string) (*heatMap, error) {
	dataFile := pdb.getFilePath(dbname, metric)

	hm := newHeatMap()
	hm.MinTS = int64(^uint64(0) >> 1)

	done := make(chan struct{}, 1)
	defer close(done)

	first, err := readTimestamp(dataFile + ".1")
	if err != nil {
		return nil, err
	}

	rawSamples, rawErrors := readDeltas(dataFile, done)
	parsedSamples, parsedErrors := parseSamples(rawSamples, first)

	samples := []Sample{}
	for sample := range parsedSamples {
		hm.MaxValue = math.Max(hm.MaxValue, sample.v)
		if sample.ts < hm.MinTS {
			hm.MinTS = sample.ts
		} else if sample.ts > hm.MaxTS {
			hm.MaxTS = sample.ts
		}
		samples = append(samples, sample)
	}

	done <- struct{}{}
	if err := mergeErrors(rawErrors, parsedErrors); err != nil {
		return nil, err
	}

	for _, sample := range samples {
		x := math.Floor(heatMapWidth * float64(sample.ts-hm.MinTS) / float64(hm.MaxTS-hm.MinTS))
		y := math.Floor(heatMapHeight * sample.v / hm.MaxValue)
		if x == heatMapWidth {
			x--
		}
		if y == heatMapHeight {
			y--
		}
		hm.Map[int(y)][int(x)]++
		if hm.Map[int(y)][int(x)] > hm.maxDensity {
			hm.maxDensity = hm.Map[int(y)][int(x)]
		}
	}
	return hm, nil
}
<commit_msg>Ensure periodic cache cleanups<commit_after>package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/pmylund/go-cache"
)

const (
	dataFileExt = ".data"
)

type Sample struct {
	ts int64
	v  float64
}

type perfDB struct {
	baseDir string
	mu      sync.Mutex
}

var timestampCache *cache.Cache

func newPerfDB(baseDir string) (*perfDB, error) {
	if err := os.MkdirAll(baseDir, 0755); err != nil {
		logger.Critical("Failed to initialize datastore: %s", err)
		return nil, err
	}
	timestampCache = cache.New(time.Minute, time.Hour)
	return &perfDB{baseDir, sync.Mutex{}}, nil
}

func (pdb *perfDB) getDirPath(dbname string) string {
	return filepath.Join(pdb.baseDir, dbname)
}

func (pdb *perfDB) getFilePath(dbname, metric string) string {
	dataDir := pdb.getDirPath(dbname)

	return filepath.Join(dataDir, metric+dataFileExt)
}

func (pdb *perfDB) checkDbExists(dbname string) error {
	dataDir := pdb.getDirPath(dbname)

	_, err := os.Stat(dataDir)
	return err
}

func (pdb *perfDB) checkMetricExists(dbname string, metric string) error {
	dataFile := pdb.getFilePath(dbname, metric)

	_, err := os.Stat(dataFile)
	return err
}

func parseRecord(record string) (Sample, error) {
	fields := strings.Fields(record)

	ts, err := strconv.ParseInt(fields[0], 10, 64)
	if err != nil {
		return Sample{}, err
	}

	value, err := strconv.ParseFloat(fields[1], 64)
	if err != nil {
		return Sample{}, err
	}
	return Sample{ts, value}, nil
}

func readTimestamp(dataFile string) (int64, error) {
	if cachedData, found := timestampCache.Get(dataFile); found {
		return cachedData.(int64), nil
	}

	record, err := ioutil.ReadFile(dataFile)
	if err != nil {
		return 0, err
	}

	return strconv.ParseInt(string(record), 10, 64)
}

func storeTimestamp(dataFile string, timestamp int64) error {
	file, err := os.OpenFile(dataFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	if _, err = fmt.Fprint(file, timestamp); err != nil {
		return err
	}
	timestampCache.Set(dataFile, timestamp, cache.DefaultExpiration)
	return nil
}

func storeSample(dataFile string, sample Sample) error {
	file, err := os.OpenFile(dataFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = fmt.Fprintf(file, "%d %v\n", sample.ts, sample.v)
	return err
}

func initStore(dataFile string, sample Sample) error {
	if err := storeTimestamp(dataFile+".1", sample.ts); err != nil {
		return err
	}
	if err := storeTimestamp(dataFile+".n", sample.ts); err != nil {
		return err
	}

	sample.ts = 0
	return storeSample(dataFile, sample)
}

func appendSample(dataFile string, sample Sample) error {
	ts, err := readTimestamp(dataFile + ".n")
	if err != nil {
		return err
	}
	if err := storeTimestamp(dataFile+".n", sample.ts); err != nil {
		return err
	}
	sample.ts -= ts
	return storeSample(dataFile, sample)
}

func (pdb *perfDB) addSample(dbname, metric string, sample Sample) error {
	dataDir := pdb.getDirPath(dbname)
	if err := os.MkdirAll(dataDir, 0775); err != nil {
		return err
	}
	dataFile := pdb.getFilePath(dbname, metric)

	pdb.mu.Lock()
	defer pdb.mu.Unlock()

	_, err := os.Stat(dataFile + ".n")
	if err == nil {
		return appendSample(dataFile, sample)
	} else if os.IsNotExist(err) {
		return initStore(dataFile, sample)
	}
	return err
}

func (pdb *perfDB) listDatabases() ([]string, error) {
	files, err := ioutil.ReadDir(pdb.baseDir)
	if err != nil {
		return nil, err
	}

	databases := []string{}
	for _, f := range files {
		databases = append(databases, f.Name())
	}
	return databases, nil
}

func (pdb *perfDB) listMetrics(dbname string) []string {
	dataDir := pdb.getDirPath(dbname)

	matches, _ := filepath.Glob(dataDir + "/*.data") // Glob ignores file system errors

	metrics := []string{}
	for _, match := range matches {
		name := strings.TrimPrefix(match, dataDir+"/")
		name = strings.TrimRight(name, dataFileExt)
		metrics = append(metrics, name)
	}
	return metrics
}

const bufferSize = 1000

func readDeltas(fileName string, done <-chan struct{}) (<-chan string, <-chan error) {
	samples := make(chan string, bufferSize)
	errc := make(chan error, 1)

	go func() {
		defer close(samples)
		defer close(errc)

		file, err := os.Open(fileName)
		if err != nil {
			errc <- err
			return
		}
		defer file.Close()

		scanner := bufio.NewScanner(file)
		for scanner.Scan() {
			select {
			case samples <- scanner.Text():
			case <-done:
				return
			}
		}

		if err := scanner.Err(); err != nil {
			errc <- err
		}
	}()
	return samples, errc
}

func parseSamples(records <-chan string, ts int64) (<-chan Sample, <-chan error) {
	samples := make(chan Sample, bufferSize)
	errc := make(chan error, 1)

	go func() {
		defer close(samples)
		defer close(errc)

		for record := range records {
			sample, err := parseRecord(record)
			if err != nil {
				errc <- err
			} else {
				sample.ts += ts
				ts = sample.ts
				samples <- sample
			}
		}
	}()
	return samples, errc
}

func mergeErrors(errcs ...<-chan error) error {
	for _, errc := range errcs {
		if err := <-errc; err != nil {
			return err
		}
	}
	return nil
}

func (pdb *perfDB) getRawValues(dbname, metric string) ([][]interface{}, error) {
	dataFile := pdb.getFilePath(dbname, metric)

	first, err := readTimestamp(dataFile + ".1")
	if err != nil {
		return nil, err
	}

	done := make(chan struct{}, 1)
	rawSamples, rawErrors := readDeltas(dataFile, done)
	parsedSamples, parsedErrors := parseSamples(rawSamples, first)

	values := [][]interface{}{}
	for sample := range parsedSamples {
		values = append(values, []interface{}{sample.ts, sample.v})
	}

	done <- struct{}{}
	if err := mergeErrors(rawErrors, parsedErrors); err != nil {
		return nil, err
	}
	return values, nil
}

func (pdb *perfDB) getSummary(dbname, metric string) (map[string]interface{}, error) {
	var summary map[string]interface{}

	dataFile := pdb.getFilePath(dbname, metric)

	done := make(chan struct{}, 1)
	defer close(done)

	first, err := readTimestamp(dataFile + ".1")
	if err != nil {
		return nil, err
	}

	rawSamples, rawErrors := readDeltas(dataFile, done)
	parsedSamples, parsedErrors := parseSamples(rawSamples, first)

	values := []float64{}
	sum := 0.0
	for sample := range parsedSamples {
		sum += sample.v
		values = append(values, sample.v)
	}

	done <- struct{}{}
	if err := mergeErrors(rawErrors, parsedErrors); err != nil {
		return nil, err
	}

	count := len(values)
	sort.Float64s(values)

	summary = map[string]interface{}{
		"max":   values[count-1],
		"min":   values[0],
		"count": count,
		"avg":   sum / float64(count),
	}

	for _, percentile := range []float64{0.5, 0.8, 0.9, 0.95, 0.99, 0.999} {
		var pIdx int
		if count > 1 {
			pIdx = int(float64(count)*percentile) - 1
		}
		p := fmt.Sprintf("p%v", percentile*100)
		summary[p] = values[pIdx]
	}

	return summary, nil
}

func (pdb *perfDB) getHeatMap(dbname, metric string) (*heatMap, error) {
	dataFile := pdb.getFilePath(dbname, metric)

	hm := newHeatMap()
	hm.MinTS = int64(^uint64(0) >> 1)

	done := make(chan struct{}, 1)
	defer close(done)

	first, err := readTimestamp(dataFile + ".1")
	if err != nil {
		return nil, err
	}

	rawSamples, rawErrors := readDeltas(dataFile, done)
	parsedSamples, parsedErrors := parseSamples(rawSamples, first)

	samples := []Sample{}
	for sample := range parsedSamples {
		hm.MaxValue = math.Max(hm.MaxValue, sample.v)
		if sample.ts < hm.MinTS {
			hm.MinTS = sample.ts
		} else if sample.ts > hm.MaxTS {
			hm.MaxTS = sample.ts
		}
		samples = append(samples, sample)
	}

	done <- struct{}{}
	if err := mergeErrors(rawErrors, parsedErrors); err != nil {
		return nil, err
	}

	for _, sample := range samples {
		x := math.Floor(heatMapWidth * float64(sample.ts-hm.MinTS) / float64(hm.MaxTS-hm.MinTS))
		y := math.Floor(heatMapHeight * sample.v / hm.MaxValue)
		if x == heatMapWidth {
			x--
		}
		if y == heatMapHeight {
			y--
		}
		hm.Map[int(y)][int(x)]++
		if hm.Map[int(y)][int(x)] > hm.maxDensity {
			hm.maxDensity = hm.Map[int(y)][int(x)]
		}
	}
	return hm, nil
}
<|endoftext|>


<commit_before>package period

import (
	"fmt"
	"reflect"
	"time"
)

const (
	UTS               = "1136239445"          //  unixtimestamp
	ICSFORMAT         = "20060102T150405Z"    //ics date time format
	YMDHIS            = "2006-01-02 15:04:05" // Y-m-d H:i:S time format
	ICSFORMATWHOLEDAY = "20060102"            // ics date format ( describes a whole day)
)

var StartWeek time.Weekday = time.Monday
var Timezone *time.Location = time.UTC

type Period struct {
	Start time.Time `json:"start"`
	End   time.Time `json:"end"`
}

// Compare DateTimeInterface objects including microseconds
func compareDate(date1, date2 time.Time) int {
	if date1.After(date2) {
		return 1
	}

	if date1.Before(date2) {
		return -1
	}

	return 0
}

// Create a Period object from a Year and a Week.
func CreateFromWeek(year, week int) (p Period, err error) {
	if week, err = validateRange(week, 1, 53); err != nil {
		return p, err
	}

	p.Start = time.Date(year, 0, 0, 0, 0, 0, 0, Timezone)
	isoYear, isoWeek := p.Start.ISOWeek()
	for p.Start.Weekday() != time.Monday { // iterate back to Monday (ISO_8601 first day of week = Monday)
		p.Start = p.Start.AddDate(0, 0, -1)
		isoYear, isoWeek = p.Start.ISOWeek()
	}
	for isoYear < year { // iterate forward to the first day of the first week
		p.Start = p.Start.AddDate(0, 0, 1)
		isoYear, isoWeek = p.Start.ISOWeek()
		// fmt.Printf("isoYear: %s, year: %s\n", isoYear, year)
	}
	for isoWeek < week { // iterate forward to the first day of the given week
		p.Start = p.Start.AddDate(0, 0, 1)
		isoYear, isoWeek = p.Start.ISOWeek()
	}
	for p.Start.Weekday() != StartWeek { // iterate back to StartWeek
		var diff int
		diff = int(StartWeek) - int(time.Monday)
		p.Start = p.Start.AddDate(0, 0, diff)
		isoYear, isoWeek = p.Start.ISOWeek()
	}

	// add 1 week
	p.End = p.Start.AddDate(0, 0, 7)

	return p, nil
}

func validateRange(value, min, max int) (int, error) {
	if value < min || value > max {
		return value, OutOfRangeError
	}
	return value, nil
}

// Create a Period object from a Year and a Month.
func CreateFromMonth(year, month int) (p Period, err error) {
	if month, err = validateRange(month, 1, 12); err != nil {
		return p, err
	}

	p.Start, err = time.Parse(YMDHIS, fmt.Sprintf("%d-%02d-01 00:00:00", year, month))
	if err != nil {
		return p, err
	}

	p.End = p.Start.AddDate(0, 1, 0)

	return p, nil
}

// Create a Period object from a Year and a Quarter.
func CreateFromQuarter(year, quarter int) (p Period, err error) {
	if quarter, err = validateRange(quarter, 1, 4); err != nil {
		return p, err
	}

	p.Start, err = time.Parse(YMDHIS, fmt.Sprintf("%d-%02d-01 00:00:00", year, ((quarter-1)*3)+1))
	if err != nil {
		return p, err
	}

	p.End = p.Start.AddDate(0, 3, 0)

	return p, nil
}

// Create a Period object from a Year and a Quarter.
func CreateFromSemester(year, semester int) (p Period, err error) {
	if semester, err = validateRange(semester, 1, 2); err != nil {
		return p, err
	}

	p.Start, err = time.Parse(YMDHIS, fmt.Sprintf("%d-%02d-01 00:00:00", year, ((semester-1)*6)+1))
	if err != nil {
		return p, err
	}

	p.End = p.Start.AddDate(0, 6, 0)

	return p, nil
}

// Create a Period object from a Year
func CreateFromYear(year int) (p Period, err error) {
	p.Start, err = time.Parse(YMDHIS, fmt.Sprintf("%d-01-01 00:00:00", year))
	if err != nil {
		return p, err
	}

	p.End = p.Start.AddDate(1, 0, 0)

	return p, nil
}

// Create a Period object from a starting point and an interval.
func CreateFromDuration(start time.Time, duration time.Duration) (p Period) {

	p.Start = start
	p.End = p.Start.Add(duration)

	return p
}

func CreateFromDurationBeforeEnd(end time.Time, duration time.Duration) (p Period) {
	p.Start = end.Add(-1 * duration)
	p.End = end

	return p
}

func (p *Period) Contains(index time.Time) bool {
	return (-1 < compareDate(index, p.Start)) &&
		(-1 == compareDate(index, p.End))
}

func (p *Period) WithDuration(duration time.Duration) {
	p.End = p.Start.Add(duration)
}

func (p *Period) Add(duration time.Duration) {
	p.End = p.End.Add(duration)
}

func (p *Period) Sub(duration time.Duration) {
	p.End = p.End.Add(-1 * duration)
}

func (p *Period) Next() {
	clone := *p
	duration := clone.GetDurationInterval()
	p.Start = clone.End
	p.End = clone.End.Add(duration)
}

func (p *Period) Previous() {
	clone := *p
	duration := clone.GetDurationInterval()
	p.Start = clone.Start.Add(-1 * duration)
	p.End = clone.Start
}

func (p *Period) GetDurationInterval() time.Duration {
	end := p.End.UnixNano()
	start := p.Start.UnixNano()
	return time.Duration(end-start) * time.Nanosecond
}

func (p *Period) Overlaps(period Period) bool {
	if abuts, _ := p.Abuts(period); abuts {
		return false
	}

	return (-1 == compareDate(p.Start, period.End)) &&
		(1 == compareDate(p.End, period.Start))
}

// Tells whether a Period is entirely after the specified index
func (p *Period) After(period Period) bool {
	return p.Start.After(period.End)
}

// Tells whether a Period is entirely before the specified index
func (p *Period) Before(period Period) bool {
	return p.End.Before(period.Start)
}

// Tells whether two Period object abuts
func (p *Period) Abuts(period Period) (bool, int) {
	if p.Start.Equal(period.End) || p.End.Equal(period.Start) {
		var pos int = -1

		if compareDate(p.Start, period.End) == 0 {
			pos = 0
		} else if compareDate(p.End, period.Start) == 0 {
			pos = 1
		}

		return true, pos
	}

	return false, -1
}

func (p *Period) Diff(period Period) ([]Period, error) {
	if p.Overlaps(period) == false {
		return nil, ShouldOverlapsError
	}

	var res = []Period{}
	var period1 Period = createFromDatepoints(p.Start, period.Start)
	var period2 Period = createFromDatepoints(p.End, period.End)

	if compareDate(period1.Start, period1.End) != 0 {
		res = append(res, period1)
	}

	if compareDate(period2.Start, period2.End) != 0 {
		res = append(res, period2)
	}

	return res, nil
}

// Merges one or more Period objects to return a new Period object.
// The resultant object englobes the largest duration possible.
func (p *Period) Merge(periods ...Period) {
	// allPeriods := []Period{}
	for _, period := range periods {
		if 1 == compareDate(p.Start, period.Start) {
			p.Start = period.Start
		}

		if -1 == compareDate(p.End, period.End) {
			p.End = period.End
		}
	}
}

// Computes the intersection between two Period objects.
func (p *Period) Intersect(period Period) (Period, error) {
	var newPeriod Period
	if abuts, _ := p.Abuts(period); abuts {
		return newPeriod, BothShouldNotAbuts
	}

	if newPeriod.Start = p.Start; 1 == compareDate(period.Start, p.Start) {
		newPeriod.Start = period.Start
	}

	if newPeriod.End = p.End; -1 == compareDate(period.End, p.End) {
		newPeriod.End = period.End
	}

	return newPeriod, nil
}

func (p *Period) Gap(period Period) (newPeriod Period) {

	if 1 == compareDate(period.Start, p.Start) {
		newPeriod.Start = p.End
		newPeriod.End = period.Start
		return
	}

	newPeriod.Start = period.End
	newPeriod.End = p.Start
	return
}

// Compares two Period objects according to their duration.
func (p *Period) CompareDuration(period Period) int {
	return compareDate(p.End, period.End)
}

// Tells whether the current Period object duration
// is greater than the submitted one.
func (p *Period) DurationGreaterThan(period Period) bool {
	return 1 == p.CompareDuration(period)
}

// Tells whether the current Period object duration
// is less than the submitted one.
func (p *Period) DurationLessThan(period Period) bool {
	return -1 == p.CompareDuration(period)
}

// Tells whether the current Period object duration
// is equal to the submitted one
func (p *Period) SameDurationAs(period Period) bool {
	return 0 == p.CompareDuration(period)
}

// Create a Period object from a Year and a Quarter.
func (p *Period) DurationDiff(period Period) time.Duration {
	return time.Duration(p.TimestampDurationDiff(period)) * time.Nanosecond
}

func (p *Period) TimestampDurationDiff(period Period) int64 {
	return p.GetDurationInterval().Nanoseconds() - period.GetDurationInterval().Nanoseconds()
}

func createFromDatepoints(date1, date2 time.Time) Period {
	if 1 == compareDate(date1, date2) {
		return Period{date2, date1}
	}

	return Period{date1, date2}
}

// http://codereview.stackexchange.com/questions/60074/in-array-in-go
func in_array(val interface{}, array interface{}) (exists bool, index int) {
	exists = false
	index = -1

	switch reflect.TypeOf(array).Kind() {
	case reflect.Slice:
		s := reflect.ValueOf(array)

		for i := 0; i < s.Len(); i++ {
			if reflect.DeepEqual(val, s.Index(i).Interface()) == true {
				index = i
				exists = true
				return
			}
		}
	}

	return
}
<commit_msg>Refactoring GetDurationInterval<commit_after>package period

import (
	"fmt"
	"reflect"
	"time"
)

const (
	UTS               = "1136239445"          //  unixtimestamp
	ICSFORMAT         = "20060102T150405Z"    //ics date time format
	YMDHIS            = "2006-01-02 15:04:05" // Y-m-d H:i:S time format
	ICSFORMATWHOLEDAY = "20060102"            // ics date format ( describes a whole day)
)

var StartWeek time.Weekday = time.Monday
var Timezone *time.Location = time.UTC

type Period struct {
	Start time.Time `json:"start"`
	End   time.Time `json:"end"`
}

// Compare DateTimeInterface objects including microseconds
func compareDate(date1, date2 time.Time) int {
	if date1.After(date2) {
		return 1
	}

	if date1.Before(date2) {
		return -1
	}

	return 0
}

// Create a Period object from a Year and a Week.
func CreateFromWeek(year, week int) (p Period, err error) {
	if week, err = validateRange(week, 1, 53); err != nil {
		return p, err
	}

	p.Start = time.Date(year, 0, 0, 0, 0, 0, 0, Timezone)
	isoYear, isoWeek := p.Start.ISOWeek()
	for p.Start.Weekday() != time.Monday { // iterate back to Monday (ISO_8601 first day of week = Monday)
		p.Start = p.Start.AddDate(0, 0, -1)
		isoYear, isoWeek = p.Start.ISOWeek()
	}
	for isoYear < year { // iterate forward to the first day of the first week
		p.Start = p.Start.AddDate(0, 0, 1)
		isoYear, isoWeek = p.Start.ISOWeek()
		// fmt.Printf("isoYear: %s, year: %s\n", isoYear, year)
	}
	for isoWeek < week { // iterate forward to the first day of the given week
		p.Start = p.Start.AddDate(0, 0, 1)
		isoYear, isoWeek = p.Start.ISOWeek()
	}
	for p.Start.Weekday() != StartWeek { // iterate back to StartWeek
		var diff int
		diff = int(StartWeek) - int(time.Monday)
		p.Start = p.Start.AddDate(0, 0, diff)
		isoYear, isoWeek = p.Start.ISOWeek()
	}

	// add 1 week
	p.End = p.Start.AddDate(0, 0, 7)

	return p, nil
}

func validateRange(value, min, max int) (int, error) {
	if value < min || value > max {
		return value, OutOfRangeError
	}
	return value, nil
}

// Create a Period object from a Year and a Month.
func CreateFromMonth(year, month int) (p Period, err error) {
	if month, err = validateRange(month, 1, 12); err != nil {
		return p, err
	}

	p.Start, err = time.Parse(YMDHIS, fmt.Sprintf("%d-%02d-01 00:00:00", year, month))
	if err != nil {
		return p, err
	}

	p.End = p.Start.AddDate(0, 1, 0)

	return p, nil
}

// Create a Period object from a Year and a Quarter.
func CreateFromQuarter(year, quarter int) (p Period, err error) {
	if quarter, err = validateRange(quarter, 1, 4); err != nil {
		return p, err
	}

	p.Start, err = time.Parse(YMDHIS, fmt.Sprintf("%d-%02d-01 00:00:00", year, ((quarter-1)*3)+1))
	if err != nil {
		return p, err
	}

	p.End = p.Start.AddDate(0, 3, 0)

	return p, nil
}

// Create a Period object from a Year and a Quarter.
func CreateFromSemester(year, semester int) (p Period, err error) {
	if semester, err = validateRange(semester, 1, 2); err != nil {
		return p, err
	}

	p.Start, err = time.Parse(YMDHIS, fmt.Sprintf("%d-%02d-01 00:00:00", year, ((semester-1)*6)+1))
	if err != nil {
		return p, err
	}

	p.End = p.Start.AddDate(0, 6, 0)

	return p, nil
}

// Create a Period object from a Year
func CreateFromYear(year int) (p Period, err error) {
	p.Start, err = time.Parse(YMDHIS, fmt.Sprintf("%d-01-01 00:00:00", year))
	if err != nil {
		return p, err
	}

	p.End = p.Start.AddDate(1, 0, 0)

	return p, nil
}

// Create a Period object from a starting point and an interval.
func CreateFromDuration(start time.Time, duration time.Duration) (p Period) {

	p.Start = start
	p.End = p.Start.Add(duration)

	return p
}

func CreateFromDurationBeforeEnd(end time.Time, duration time.Duration) (p Period) {
	p.Start = end.Add(-1 * duration)
	p.End = end

	return p
}

func (p *Period) Contains(index time.Time) bool {
	return (-1 < compareDate(index, p.Start)) &&
		(-1 == compareDate(index, p.End))
}

func (p *Period) WithDuration(duration time.Duration) {
	p.End = p.Start.Add(duration)
}

func (p *Period) Add(duration time.Duration) {
	p.End = p.End.Add(duration)
}

func (p *Period) Sub(duration time.Duration) {
	p.End = p.End.Add(-1 * duration)
}

func (p *Period) Next() {
	clone := *p
	duration := clone.GetDurationInterval()
	p.Start = clone.End
	p.End = clone.End.Add(duration)
}

func (p *Period) Previous() {
	clone := *p
	duration := clone.GetDurationInterval()
	p.Start = clone.Start.Add(-1 * duration)
	p.End = clone.Start
}

func (p *Period) GetDurationInterval() time.Duration {
	return p.End.Sub(p.Start)
}

func (p *Period) Overlaps(period Period) bool {
	if abuts, _ := p.Abuts(period); abuts {
		return false
	}

	return (-1 == compareDate(p.Start, period.End)) &&
		(1 == compareDate(p.End, period.Start))
}

// Tells whether a Period is entirely after the specified index
func (p *Period) After(period Period) bool {
	return p.Start.After(period.End)
}

// Tells whether a Period is entirely before the specified index
func (p *Period) Before(period Period) bool {
	return p.End.Before(period.Start)
}

// Tells whether two Period object abuts
func (p *Period) Abuts(period Period) (bool, int) {
	if p.Start.Equal(period.End) || p.End.Equal(period.Start) {
		var pos int = -1

		if compareDate(p.Start, period.End) == 0 {
			pos = 0
		} else if compareDate(p.End, period.Start) == 0 {
			pos = 1
		}

		return true, pos
	}

	return false, -1
}

func (p *Period) Diff(period Period) ([]Period, error) {
	if p.Overlaps(period) == false {
		return nil, ShouldOverlapsError
	}

	var res = []Period{}
	var period1 Period = createFromDatepoints(p.Start, period.Start)
	var period2 Period = createFromDatepoints(p.End, period.End)

	if compareDate(period1.Start, period1.End) != 0 {
		res = append(res, period1)
	}

	if compareDate(period2.Start, period2.End) != 0 {
		res = append(res, period2)
	}

	return res, nil
}

// Merges one or more Period objects to return a new Period object.
// The resultant object englobes the largest duration possible.
func (p *Period) Merge(periods ...Period) {
	// allPeriods := []Period{}
	for _, period := range periods {
		if 1 == compareDate(p.Start, period.Start) {
			p.Start = period.Start
		}

		if -1 == compareDate(p.End, period.End) {
			p.End = period.End
		}
	}
}

// Computes the intersection between two Period objects.
func (p *Period) Intersect(period Period) (Period, error) {
	var newPeriod Period
	if abuts, _ := p.Abuts(period); abuts {
		return newPeriod, BothShouldNotAbuts
	}

	if newPeriod.Start = p.Start; 1 == compareDate(period.Start, p.Start) {
		newPeriod.Start = period.Start
	}

	if newPeriod.End = p.End; -1 == compareDate(period.End, p.End) {
		newPeriod.End = period.End
	}

	return newPeriod, nil
}

func (p *Period) Gap(period Period) (newPeriod Period) {

	if 1 == compareDate(period.Start, p.Start) {
		newPeriod.Start = p.End
		newPeriod.End = period.Start
		return
	}

	newPeriod.Start = period.End
	newPeriod.End = p.Start
	return
}

// Compares two Period objects according to their duration.
func (p *Period) CompareDuration(period Period) int {
	return compareDate(p.End, period.End)
}

// Tells whether the current Period object duration
// is greater than the submitted one.
func (p *Period) DurationGreaterThan(period Period) bool {
	return 1 == p.CompareDuration(period)
}

// Tells whether the current Period object duration
// is less than the submitted one.
func (p *Period) DurationLessThan(period Period) bool {
	return -1 == p.CompareDuration(period)
}

// Tells whether the current Period object duration
// is equal to the submitted one
func (p *Period) SameDurationAs(period Period) bool {
	return 0 == p.CompareDuration(period)
}

// Create a Period object from a Year and a Quarter.
func (p *Period) DurationDiff(period Period) time.Duration {
	return time.Duration(p.TimestampDurationDiff(period)) * time.Nanosecond
}

func (p *Period) TimestampDurationDiff(period Period) int64 {
	return p.GetDurationInterval().Nanoseconds() - period.GetDurationInterval().Nanoseconds()
}

func createFromDatepoints(date1, date2 time.Time) Period {
	if 1 == compareDate(date1, date2) {
		return Period{date2, date1}
	}

	return Period{date1, date2}
}

// http://codereview.stackexchange.com/questions/60074/in-array-in-go
func in_array(val interface{}, array interface{}) (exists bool, index int) {
	exists = false
	index = -1

	switch reflect.TypeOf(array).Kind() {
	case reflect.Slice:
		s := reflect.ValueOf(array)

		for i := 0; i < s.Len(); i++ {
			if reflect.DeepEqual(val, s.Index(i).Interface()) == true {
				index = i
				exists = true
				return
			}
		}
	}

	return
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The Kubernetes Authors.

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

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

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

package flag

import (
	goflag "flag"
	"fmt"
	"strings"

	"github.com/spf13/pflag"
)

// StringSlice implements goflag.Value and plfag.Value,
// and allows set to be invoked repeatedly to accumulate values.
type StringSlice struct {
	value   *[]string
	changed bool
}

func NewStringSlice(s *[]string) *StringSlice {
	return &StringSlice{value: s}
}

var _ goflag.Value = &StringSlice{}
var _ pflag.Value = &StringSlice{}

func (s *StringSlice) String() string {
	if s == nil || s.value == nil {
		return ""
	}
	return strings.Join(*s.value, " ")
}

func (s *StringSlice) Set(val string) error {
	if s.value == nil {
		return fmt.Errorf("no target (nil pointer to []string)")
	}
	if *s.value == nil || !s.changed {
		*s.value = make([]string, 0)
	}
	*s.value = append(*s.value, val)
	s.changed = true
	return nil
}

func (StringSlice) Type() string {
	return "sliceString"
}
<commit_msg>remove redundant clause in string_slice_flag<commit_after>/*
Copyright 2021 The Kubernetes Authors.

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

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

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

package flag

import (
	goflag "flag"
	"fmt"
	"strings"

	"github.com/spf13/pflag"
)

// StringSlice implements goflag.Value and plfag.Value,
// and allows set to be invoked repeatedly to accumulate values.
type StringSlice struct {
	value   *[]string
	changed bool
}

func NewStringSlice(s *[]string) *StringSlice {
	return &StringSlice{value: s}
}

var _ goflag.Value = &StringSlice{}
var _ pflag.Value = &StringSlice{}

func (s *StringSlice) String() string {
	if s == nil || s.value == nil {
		return ""
	}
	return strings.Join(*s.value, " ")
}

func (s *StringSlice) Set(val string) error {
	if s.value == nil {
		return fmt.Errorf("no target (nil pointer to []string)")
	}
	if !s.changed {
		*s.value = make([]string, 0)
	}
	*s.value = append(*s.value, val)
	s.changed = true
	return nil
}

func (StringSlice) Type() string {
	return "sliceString"
}
<|endoftext|>


<commit_before>package chirp

/*
// To see this simple compiler in action, try
// $ GOPATH=$(pwd) go test -test.v src/terp/*test.go 2>&1 | grep PRECOMP
*/

import (
	. "fmt"
	// "log"
	"regexp"
	"strings"
)

// interfaces

// A Evaler is some piece of code that can be evaluated.
// It may be a single command (Words) or a sequence
// of commands (Seq).
type Evaler interface {
	Eval(fr *Frame) T
}

// A word becomes one argument, or the command word itself.
// Substitutions take place each time it is to be used.
type Word interface {
	Subst(fr *Frame) T
}

// structs

// Seq is a list of Stmts that can be evaluated.
// The value is usually the value of the last one.
// If empty, the value is Empty.
type Seq struct { // implements Evaler
	stmts []Evaler
}

// Words is a single command statement;
// it cannot be an empty list.
// The first word usually names the command.
type Words struct { // * implements Evaler
	words []Word
}

type WordParts struct { // * implements Word // TODO
	parts []Word
}

type BareWord struct { // * implements Word
	word T
}

type Dollar1 struct { // * implements Word
	varName string
}

type Dollar2 struct { // * implements Word // TODO
	varName   string
	subscript Word
}

type Square struct { // * implements Word // TODO
	body Evaler
}

// Eval each member of the sequence, returning the last result.
// If sequence is empty, return Empty.
func (me *Seq) Eval(fr *Frame) T {
	// log.Printf("PRECOMPILED SEQUENCE << %v", me.stmts)
	var z T = Empty
	for _, stmt := range me.stmts {
		z = stmt.Eval(fr)
	}
	// log.Printf("PRECOMPILED SEQUENCE >> %s", Show(z))
	return z
}

// Eval Words by substituting each word, and then Applying the resulting list.
func (me *Words) Eval(fr *Frame) T {
	// log.Printf("PRECOMPILED WORDS << %v", me.words)
	cmd := make([]T, len(me.words))
	for i, w := range me.words {
		cmd[i] = w.Subst(fr)
	}
	z := fr.Apply(cmd)
	// log.Printf("PRECOMPILED WORDS >> %s", Show(z))
	return z
}

// Subst on a BareWord just returns the bare word.
func (me *BareWord) Subst(fr *Frame) T {
	return me.word
}

// Subst on a Dollar1 looks up the variable.
func (me *Dollar1) Subst(fr *Frame) T {
	return fr.GetVar(me.varName)
}

var InertChars = "[!%-/0-9:<-@A-Z^_`a-z|~]"
var BareWordPattern = "(" + InertChars + "+)"

var AlphanumChars = "[A-Za-z0-9_]"
var DumbDollarPattern = "[$](" + AlphanumChars + "+)"

var MatchBareWord = regexp.MustCompile("^" + BareWordPattern + "$")
var MatchDumbDollar = regexp.MustCompile("^" + DumbDollarPattern + "$")

// Very dumb compiler.
// Understands things made only of inert bare words and simple dollar words.
// e.g.   proc doublePlus11 {x} { set a 10 ; + $x $x 1 $a }
//    or  proc f {x} { /fmt/Sprintf %e $x }
// If it finds something it doesn't grok, it panics.
func dumbCompileSequence(fr *Frame, s string) Evaler {
	s = strings.Replace(s, "\t", " ", -1) // get rid of tabs
	s = strings.Replace(s, ";", "\n", -1) // get rid of semicolons

	seq := &Seq{stmts: make([]Evaler, 0, 4)}
	lines := strings.Split(s, "\n")

	for _, line := range lines {
		words := &Words{words: make([]Word, 0, 4)}

		v := strings.Split(line, " ")
		for _, e := range v {
			if e == "" {
				continue
			}
			mw := MatchBareWord.FindStringSubmatch(e)
			if mw != nil {
				words.words = append(words.words, &BareWord{word: MkString(e)})
				continue
			}
			md := MatchDumbDollar.FindStringSubmatch(e)
			if md != nil {
				words.words = append(words.words, &Dollar1{varName: md[1]})
				continue
			}
			panic(Sprintf("Not Handled by CommpileSequence: %q", e))
		}

		if len(words.words) > 0 {
			seq.stmts = append(seq.stmts, words)
		}
	}

	// log.Printf("dumbCompileSequence: SUCCESSFULLY PRECOMPILED: %v <- %q", *seq, s)
	return seq
}

// CompileSequence uses dumbCompileSequence.
// If dumbCompileSequence panics, we recover and return nil.
func CompileSequence(fr *Frame, s string) (z Evaler) {
	defer func() {
		ex := recover() // z stays nil
		if ex != nil {
			Say("CompileSequence BAD", s, ex)
		}
	}()

	// Older Dumber version:
	// return dumbCompileSequence(fr, s)

	lex := NewLex(s)
	z = Parse2Seq(lex)
	if lex.Tok != TokEnd {
		Sayf("CompileSequence Non-Empty rest: %q", s)
		return nil
	}
	return
}
<commit_msg>dumbCompileSequence no longer needed.<commit_after>package chirp

/*
// To see this simple compiler in action, try
// $ GOPATH=$(pwd) go test -test.v src/terp/*test.go 2>&1 | grep PRECOMP
*/

import (
	// . "fmt"
	// "log"
	"regexp"
	// "strings"
)

// interfaces

// A Evaler is some piece of code that can be evaluated.
// It may be a single command (Words) or a sequence
// of commands (Seq).
type Evaler interface {
	Eval(fr *Frame) T
}

// A word becomes one argument, or the command word itself.
// Substitutions take place each time it is to be used.
type Word interface {
	Subst(fr *Frame) T
}

// structs

// Seq is a list of Stmts that can be evaluated.
// The value is usually the value of the last one.
// If empty, the value is Empty.
type Seq struct { // implements Evaler
	stmts []Evaler
}

// Words is a single command statement;
// it cannot be an empty list.
// The first word usually names the command.
type Words struct { // * implements Evaler
	words []Word
}

type WordParts struct { // * implements Word // TODO
	parts []Word
}

type BareWord struct { // * implements Word
	word T
}

type Dollar1 struct { // * implements Word
	varName string
}

type Dollar2 struct { // * implements Word // TODO
	varName   string
	subscript Word
}

type Square struct { // * implements Word // TODO
	body Evaler
}

// Eval each member of the sequence, returning the last result.
// If sequence is empty, return Empty.
func (me *Seq) Eval(fr *Frame) T {
	// log.Printf("PRECOMPILED SEQUENCE << %v", me.stmts)
	var z T = Empty
	for _, stmt := range me.stmts {
		z = stmt.Eval(fr)
	}
	// log.Printf("PRECOMPILED SEQUENCE >> %s", Show(z))
	return z
}

// Eval Words by substituting each word, and then Applying the resulting list.
func (me *Words) Eval(fr *Frame) T {
	// log.Printf("PRECOMPILED WORDS << %v", me.words)
	cmd := make([]T, len(me.words))
	for i, w := range me.words {
		cmd[i] = w.Subst(fr)
	}
	z := fr.Apply(cmd)
	// log.Printf("PRECOMPILED WORDS >> %s", Show(z))
	return z
}

// Subst on a BareWord just returns the bare word.
func (me *BareWord) Subst(fr *Frame) T {
	return me.word
}

// Subst on a Dollar1 looks up the variable.
func (me *Dollar1) Subst(fr *Frame) T {
	return fr.GetVar(me.varName)
}

var InertChars = "[!%-/0-9:<-@A-Z^_`a-z|~]"
var BareWordPattern = "(" + InertChars + "+)"

var AlphanumChars = "[A-Za-z0-9_]"
var DumbDollarPattern = "[$](" + AlphanumChars + "+)"

var MatchBareWord = regexp.MustCompile("^" + BareWordPattern + "$")
var MatchDumbDollar = regexp.MustCompile("^" + DumbDollarPattern + "$")

/*
// Very dumb compiler.
// Understands things made only of inert bare words and simple dollar words.
// e.g.   proc doublePlus11 {x} { set a 10 ; + $x $x 1 $a }
//    or  proc f {x} { /fmt/Sprintf %e $x }
// If it finds something it doesn't grok, it panics.
func dumbCompileSequence(fr *Frame, s string) Evaler {
	s = strings.Replace(s, "\t", " ", -1) // get rid of tabs
	s = strings.Replace(s, ";", "\n", -1) // get rid of semicolons

	seq := &Seq{stmts: make([]Evaler, 0, 4)}
	lines := strings.Split(s, "\n")

	for _, line := range lines {
		words := &Words{words: make([]Word, 0, 4)}

		v := strings.Split(line, " ")
		for _, e := range v {
			if e == "" {
				continue
			}
			mw := MatchBareWord.FindStringSubmatch(e)
			if mw != nil {
				words.words = append(words.words, &BareWord{word: MkString(e)})
				continue
			}
			md := MatchDumbDollar.FindStringSubmatch(e)
			if md != nil {
				words.words = append(words.words, &Dollar1{varName: md[1]})
				continue
			}
			panic(Sprintf("Not Handled by CommpileSequence: %q", e))
		}

		if len(words.words) > 0 {
			seq.stmts = append(seq.stmts, words)
		}
	}

	// log.Printf("dumbCompileSequence: SUCCESSFULLY PRECOMPILED: %v <- %q", *seq, s)
	return seq
}
*/

// CompileSequence uses dumbCompileSequence.
// If dumbCompileSequence panics, we recover and return nil.
func CompileSequence(fr *Frame, s string) (z Evaler) {
	defer func() {
		ex := recover() // z stays nil
		if ex != nil {
			Say("CompileSequence BAD", s, ex)
		}
	}()

	// Older Dumber version:
	// return dumbCompileSequence(fr, s)

	lex := NewLex(s)
	z = Parse2Seq(lex)
	if lex.Tok != TokEnd {
		Sayf("CompileSequence Non-Empty rest: %q", s)
		return nil
	}
	return
}
<|endoftext|>


<commit_before>package device

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"path/filepath"
	"sync"
	"time"

	"github.com/jochenvg/go-udev"
	"github.com/tarm/serial"
)

var modemCommands = struct {
	IMEI, IMSI string
}{
	"AT+GSN", "AT+CIMI",
}

// Manager manages devices that are plugged into the system. It supports auto
// detection of devices.
//
// Serial ports are opened each for a device, and a clean API for communicating
// is provided via Read, Write and Flush methods.
//
// The devices are monitored via udev, and any changes that requires reloading
// of the  ports are handled by reloading the ports to the devices.
//
// This is safe to use concurrently in multiple goroutines
type Manager struct {
	devices map[string]serial.Config
	modems  map[string]*Conn
	mu      sync.RWMutex
	monitor *udev.Monitor
	done    chan struct{}
	stop    chan struct{}
}

// New returns a new Manager instance
func New() *Manager {
	return &Manager{
		devices: make(map[string]serial.Config),
		modems:  make(map[string]*Conn),
		done:    make(chan struct{}),
		stop:    make(chan struct{}),
	}
}

// Init initializes the manager. This involves creating a new goroutine to watch
// over the changes detected by udev for any device interaction with the system.
//
// The only interesting device actions are add and reomove for adding and
// removing devices respctively.
func (m *Manager) Init() {
	u := udev.Udev{}
	monitor := u.NewMonitorFromNetlink("udev")
	monitor.FilterAddMatchTag("systemd")
	devCh, err := monitor.DeviceChan(m.done)
	if err != nil {
		panic(err)
	}
	m.monitor = monitor
	go func() {
	stop:
		for {
			select {
			case d := <-devCh:
				dpath := filepath.Join("/dev", filepath.Base(d.Devpath()))
				switch d.Action() {
				case "add":
					m.AddDevice(dpath)
					m.reload()
				case "remove":
					m.RemoveDevice(dpath)
					m.reload()
				}
			case quit := <-m.stop:
				m.done <- quit
				break stop
			}
		}
	}()
}

// AddDevice adds device name to the manager
func (m *Manager) AddDevice(name string) error {
	cfg := serial.Config{Name: name, Baud: 9600, ReadTimeout: time.Second}
	m.mu.Lock()
	m.devices[name] = cfg
	m.mu.Unlock()
	return nil
}

// List serves the list of current devices. The list wont cover all devices ,
// only the significant ones( modems for now)
func (m *Manager) List(w http.ResponseWriter, r *http.Request) {
	data := make(map[string]interface{})
	for imei, c := range m.modems {
		data[imei] = c.device.Name
	}
	json.NewEncoder(w).Encode(data)
	w.Header().Set("Content-Type", "application/json")
}

// RunComand runs commands to the exposed devices over serial ports
func (m *Manager) RunCommand(w http.ResponseWriter, r *http.Request) {
}

// RemoveDevice removes device name from the manager
func (m *Manager) RemoveDevice(name string) error {
	m.mu.RLock()
	delete(m.devices, name)
	m.mu.RUnlock()
	return nil
}

func (m *Manager) reload() {
	for _, v := range m.devices {
		conn := &Conn{device: v}
		imei, err := conn.Run(modemCommands.IMEI)
		if err != nil {
			_ = conn.Close()
			continue
		}
		i, err := cleanResult(imei)
		if err != nil {
			_ = conn.Close()
			continue
		}
		conn.imei = string(i)
		fmt.Println(conn.imei, " ", conn.device.Name)
		m.modems[conn.imei] = conn
	}
}

func cleanResult(src []byte) ([]byte, error) {
	i := bytes.Index(src, []byte("OK"))
	if i == -1 {
		return nil, errors.New("not okay")
	}
	ns := bytes.TrimSpace(src[:i])
	return ns, nil
}

//Close shuts down the device manager. This makes sure the udev monitor is
//closed and all goroutines are properly exited.
func (m *Manager) Close() {
	m.stop <- struct{}{}
}

// Conn is a device serial connection
type Conn struct {
	device serial.Config
	imei   string
	port   *serial.Port
	isOpen bool
}

// Open opens a serial port to the undelying device
func (c *Conn) Open() error {
	p, err := serial.OpenPort(&c.device)
	if err != nil {
		return err
	}
	c.port = p
	c.isOpen = true
	return nil
}

// Close closes the port helt by *Conn.
func (c *Conn) Close() error {
	if c.isOpen {
		return c.port.Close()
	}
	return nil
}

// Write wites b to the serieal port
func (c *Conn) Write(b []byte) (int, error) {
	return c.port.Write(b)
}

// Read reads from serial port
func (c *Conn) Read(b []byte) (int, error) {
	return c.port.Read(b)
}

// Exec sends the command over serial port and rrturns the response. If the port
// is closed it is opened  before sending the command.
func (c *Conn) Exec(cmd string) ([]byte, error) {
	if !c.isOpen {
		err := c.Open()
		if err != nil {
			return nil, err
		}
	}
	defer func() {
		_ = c.port.Flush()
		_ = c.port.Close()
		c.isOpen = false
	}()
	_, err := c.Write([]byte(cmd))
	if err != nil {
		return nil, err
	}
	buf := make([]byte, 128)
	_, err = c.Read(buf)
	if err != nil {
		return nil, err
	}
	if !bytes.Contains(buf, []byte("OK")) {
		return nil, errors.New(" not Okay")
	}
	return buf, nil
}

// Run helper for Exec that adds \r to the command
func (c *Conn) Run(cmd string) ([]byte, error) {
	return c.Exec(fmt.Sprintf("%s \r", cmd))
}
<commit_msg>Remove unnecessary metjod call<commit_after>package device

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"path/filepath"
	"sync"
	"time"

	"github.com/jochenvg/go-udev"
	"github.com/tarm/serial"
)

var modemCommands = struct {
	IMEI, IMSI string
}{
	"AT+GSN", "AT+CIMI",
}

// Manager manages devices that are plugged into the system. It supports auto
// detection of devices.
//
// Serial ports are opened each for a device, and a clean API for communicating
// is provided via Read, Write and Flush methods.
//
// The devices are monitored via udev, and any changes that requires reloading
// of the  ports are handled by reloading the ports to the devices.
//
// This is safe to use concurrently in multiple goroutines
type Manager struct {
	devices map[string]serial.Config
	modems  map[string]*Conn
	mu      sync.RWMutex
	monitor *udev.Monitor
	done    chan struct{}
	stop    chan struct{}
}

// New returns a new Manager instance
func New() *Manager {
	return &Manager{
		devices: make(map[string]serial.Config),
		modems:  make(map[string]*Conn),
		done:    make(chan struct{}),
		stop:    make(chan struct{}),
	}
}

// Init initializes the manager. This involves creating a new goroutine to watch
// over the changes detected by udev for any device interaction with the system.
//
// The only interesting device actions are add and reomove for adding and
// removing devices respctively.
func (m *Manager) Init() {
	u := udev.Udev{}
	monitor := u.NewMonitorFromNetlink("udev")
	monitor.FilterAddMatchTag("systemd")
	devCh, err := monitor.DeviceChan(m.done)
	if err != nil {
		panic(err)
	}
	m.monitor = monitor
	go func() {
	stop:
		for {
			select {
			case d := <-devCh:
				dpath := filepath.Join("/dev", filepath.Base(d.Devpath()))
				switch d.Action() {
				case "add":
					m.AddDevice(dpath)
					m.reload()
				case "remove":
					m.RemoveDevice(dpath)
					m.reload()
				}
			case quit := <-m.stop:
				m.done <- quit
				break stop
			}
		}
	}()
}

// AddDevice adds device name to the manager
func (m *Manager) AddDevice(name string) error {
	cfg := serial.Config{Name: name, Baud: 9600, ReadTimeout: time.Second}
	m.mu.Lock()
	m.devices[name] = cfg
	m.mu.Unlock()
	return nil
}

// List serves the list of current devices. The list wont cover all devices ,
// only the significant ones( modems for now)
func (m *Manager) List(w http.ResponseWriter, r *http.Request) {
	data := make(map[string]interface{})
	for imei, c := range m.modems {
		data[imei] = c.device.Name
	}
	json.NewEncoder(w).Encode(data)
	w.Header().Set("Content-Type", "application/json")
}

// RunComand runs commands to the exposed devices over serial ports
func (m *Manager) RunCommand(w http.ResponseWriter, r *http.Request) {
}

// RemoveDevice removes device name from the manager
func (m *Manager) RemoveDevice(name string) error {
	m.mu.RLock()
	delete(m.devices, name)
	m.mu.RUnlock()
	return nil
}

func (m *Manager) reload() {
	for _, v := range m.devices {
		conn := &Conn{device: v}
		imei, err := conn.Run(modemCommands.IMEI)
		if err != nil {
			continue
		}
		i, err := cleanResult(imei)
		if err != nil {
			continue
		}
		conn.imei = string(i)
		fmt.Println(conn.imei, " ", conn.device.Name)
		m.modems[conn.imei] = conn
	}
}

func cleanResult(src []byte) ([]byte, error) {
	i := bytes.Index(src, []byte("OK"))
	if i == -1 {
		return nil, errors.New("not okay")
	}
	ns := bytes.TrimSpace(src[:i])
	return ns, nil
}

//Close shuts down the device manager. This makes sure the udev monitor is
//closed and all goroutines are properly exited.
func (m *Manager) Close() {
	m.stop <- struct{}{}
}

// Conn is a device serial connection
type Conn struct {
	device serial.Config
	imei   string
	port   *serial.Port
	isOpen bool
}

// Open opens a serial port to the undelying device
func (c *Conn) Open() error {
	p, err := serial.OpenPort(&c.device)
	if err != nil {
		return err
	}
	c.port = p
	c.isOpen = true
	return nil
}

// Close closes the port helt by *Conn.
func (c *Conn) Close() error {
	if c.isOpen {
		return c.port.Close()
	}
	return nil
}

// Write wites b to the serieal port
func (c *Conn) Write(b []byte) (int, error) {
	return c.port.Write(b)
}

// Read reads from serial port
func (c *Conn) Read(b []byte) (int, error) {
	return c.port.Read(b)
}

// Exec sends the command over serial port and rrturns the response. If the port
// is closed it is opened  before sending the command.
func (c *Conn) Exec(cmd string) ([]byte, error) {
	if !c.isOpen {
		err := c.Open()
		if err != nil {
			return nil, err
		}
	}
	defer func() {
		_ = c.port.Flush()
		_ = c.port.Close()
		c.isOpen = false
	}()
	_, err := c.Write([]byte(cmd))
	if err != nil {
		return nil, err
	}
	buf := make([]byte, 128)
	_, err = c.Read(buf)
	if err != nil {
		return nil, err
	}
	if !bytes.Contains(buf, []byte("OK")) {
		return nil, errors.New(" not Okay")
	}
	return buf, nil
}

// Run helper for Exec that adds \r to the command
func (c *Conn) Run(cmd string) ([]byte, error) {
	return c.Exec(fmt.Sprintf("%s \r", cmd))
}
<|endoftext|>


<commit_before>package devices

import (
	"encoding/json"
	"fmt"
	"sync"

	"github.com/ninjasphere/go-ninja"
	"github.com/ninjasphere/go-ninja/logger"

	"github.com/ninjasphere/go-ninja/channels"
)

var log = logger.GetLogger("LightDevice")

type LightDeviceState struct {
	OnOff      *bool                `json:"on-off,omitempty"`
	Color      *channels.ColorState `json:"color,omitempty"`
	Brightness *float64             `json:"brightness,omitempty"`
	Transition *int                 `json:"transition,omitempty"`
}

func (c *LightDeviceState) Clone() *LightDeviceState {
	text, _ := json.Marshal(c)
	state := &LightDeviceState{}
	json.Unmarshal(text, state)
	return state
}

type LightBatchChannel struct {
	light *LightDevice
}

func (c *LightBatchChannel) SetBatch(state *LightDeviceState, reply *interface{}) error {
	return c.light.SetBatch(state)
}

type LightDevice struct {
	sync.Mutex

	// SetLightState is required, and should actually set the state on the physical light
	ApplyLightState func(state *LightDeviceState) error

	// The following three are optional, and are used instead of ApplyLightState
	// if only a single channel state is being set.
	ApplyOnOff      func(state bool) error
	ApplyBrightness func(state float64) error
	ApplyColor      func(state *channels.ColorState) error
	ApplyTransition func(state int) error

	bus   *ninja.DeviceBus
	state *LightDeviceState
	batch bool
	log   *logger.Logger

	onOff      *channels.OnOffChannel
	brightness *channels.BrightnessChannel
	color      *channels.ColorChannel
	transition *channels.TransitionChannel
}

func (d *LightDevice) SetLightState(state *LightDeviceState) error {
	d.Lock()

	var err error

	d.state = state

	if state.OnOff != nil {
		err = d.onOff.SendEvent("state", *state.OnOff)
	}

	if err != nil {
		return fmt.Errorf("Failed emitting on-off state: %s", err)
	}

	if state.Brightness != nil {
		err = d.brightness.SendEvent("state", *state.Brightness)
	}

	if err != nil {
		return fmt.Errorf("Failed emitting brightness state: %s", err)
	}

	if state.Color != nil {
		err = d.color.SendEvent("state", *state.Color)
	}

	if err != nil {
		return fmt.Errorf("Failed emitting color state: %s", err)
	}

	if state.Transition != nil {
		err = d.transition.SendEvent("state", *state.Transition)
	}

	if err != nil {
		return fmt.Errorf("Failed emitting transition state: %s", err)
	}

	d.Unlock()
	return nil
}

func (d *LightDevice) SetBatch(state *LightDeviceState) error {
	d.Lock()

	mergedState := d.state.Clone()
	if state.OnOff != nil {
		mergedState.OnOff = state.OnOff
	}
	if state.Brightness != nil {
		mergedState.Brightness = state.Brightness
	}
	if state.Color != nil {
		mergedState.Color = state.Color
	}
	if state.Transition != nil {
		mergedState.Transition = state.Transition
	}
	err := d.ApplyLightState(mergedState)

	d.Unlock()
	return err
}

func (d *LightDevice) SetOnOff(state bool) error {
	d.Lock()

	var err error

	if state == true {
		d.log.Infof("Turning Off")
	} else {
		d.log.Infof("Turning On")
	}

	if d.ApplyOnOff != nil {
		err = d.ApplyOnOff(state)
	} else {
		lightState := d.state.Clone()
		lightState.OnOff = &state

		err = d.ApplyLightState(lightState)
	}

	d.Unlock()
	return err
}

func (d *LightDevice) SetBrightness(state float64) error {
	if d.brightness == nil {
		return fmt.Errorf("This device does not have a brightness channel")
	}

	d.Lock()

	var err error

	d.log.Infof("Setting brightness to %f", state)

	if d.ApplyBrightness != nil {
		err = d.ApplyBrightness(state)
	} else {
		lightState := d.state.Clone()
		lightState.Brightness = &state

		err = d.ApplyLightState(lightState)
	}

	d.Unlock()
	return err
}

func (d *LightDevice) SetColor(state *channels.ColorState) error {
	if d.color == nil {
		return fmt.Errorf("This device does not have a color channel")
	}

	d.Lock()

	var err error

	json, _ := json.Marshal(state)
	d.log.Infof("Setting Color to %s", json)

	if d.ApplyColor != nil {
		err = d.ApplyColor(state)
	} else {
		lightState := d.state.Clone()
		lightState.Color = state

		err = d.ApplyLightState(lightState)
	}

	d.Unlock()
	return err
}

func (d *LightDevice) SetTransition(state int) error {
	if d.transition == nil {
		return fmt.Errorf("This device does not have a transition channel")
	}

	d.Lock()

	d.state.Transition = &state

	d.Unlock()
	return nil
}

func (d *LightDevice) ToggleOnOff() error {
	if d.state.OnOff == nil {
		d.log.Warningf("On-off channel is in an unknown state for toggling. Setting to off.")
		return d.SetOnOff(false)
	}
	return d.SetOnOff(!*d.state.OnOff)
}

func (d *LightDevice) EnableOnOffChannel() error {
	d.onOff = channels.NewOnOffChannel(d)
	return d.bus.AddChannel(d.onOff, "on-off", "on-off")
}

func (d *LightDevice) EnableBrightnessChannel() error {
	d.brightness = channels.NewBrightnessChannel(d)
	return d.bus.AddChannel(d.brightness, "brightness", "brightness")
}

func (d *LightDevice) EnableColorChannel() error {
	d.color = channels.NewColorChannel(d)
	return d.bus.AddChannel(d.color, "color", "color")
}

func (d *LightDevice) EnableTransitionChannel() error {
	d.transition = channels.NewTransitionChannel(d)
	return d.bus.AddChannel(d.transition, "transition", "transition")
}

func CreateLightDevice(name string, bus *ninja.DeviceBus) (*LightDevice, error) {

	light := &LightDevice{
		bus: bus,
		log: logger.GetLogger("LightDevice - " + name),
	}

	bus.AddChannel(&LightBatchChannel{light}, "core.batching", "batch")

	light.log.Infof("Created")

	return light, nil
}
<commit_msg>Add colour space conversion xy,hue,temperature -> hue<commit_after>package devices

import (
	"encoding/json"
	"fmt"
	"math"
	"sync"

	"github.com/lucasb-eyer/go-colorful"
	"github.com/ninjasphere/go-ninja"
	"github.com/ninjasphere/go-ninja/logger"

	"github.com/ninjasphere/go-ninja/channels"
)

var log = logger.GetLogger("LightDevice")

type LightDeviceState struct {
	OnOff      *bool                `json:"on-off,omitempty"`
	Color      *channels.ColorState `json:"color,omitempty"`
	Brightness *float64             `json:"brightness,omitempty"`
	Transition *int                 `json:"transition,omitempty"`
}

func (c *LightDeviceState) Clone() *LightDeviceState {
	text, _ := json.Marshal(c)
	state := &LightDeviceState{}
	json.Unmarshal(text, state)
	return state
}

type LightBatchChannel struct {
	light *LightDevice
}

func (c *LightBatchChannel) SetBatch(state *LightDeviceState, reply *interface{}) error {
	return c.light.SetBatch(state)
}

type LightDevice struct {
	sync.Mutex

	// SetLightState is required, and should actually set the state on the physical light
	ApplyLightState func(state *LightDeviceState) error

	// The following three are optional, and are used instead of ApplyLightState
	// if only a single channel state is being set.
	ApplyOnOff      func(state bool) error
	ApplyBrightness func(state float64) error
	ApplyColor      func(state *channels.ColorState) error
	ApplyTransition func(state int) error

	bus        *ninja.DeviceBus
	state      *LightDeviceState
	batch      bool
	log        *logger.Logger
	colorModes []string

	onOff      *channels.OnOffChannel
	brightness *channels.BrightnessChannel
	color      *channels.ColorChannel
	transition *channels.TransitionChannel
}

func (d *LightDevice) SetLightState(state *LightDeviceState) error {
	d.Lock()

	var err error

	d.state = state

	if state.OnOff != nil {
		err = d.onOff.SendEvent("state", *state.OnOff)
	}

	if err != nil {
		return fmt.Errorf("Failed emitting on-off state: %s", err)
	}

	if state.Brightness != nil {
		err = d.brightness.SendEvent("state", *state.Brightness)
	}

	if err != nil {
		return fmt.Errorf("Failed emitting brightness state: %s", err)
	}

	if state.Color != nil {
		err = d.color.SendEvent("state", *state.Color)
	}

	if err != nil {
		return fmt.Errorf("Failed emitting color state: %s", err)
	}

	if state.Transition != nil {
		err = d.transition.SendEvent("state", *state.Transition)
	}

	if err != nil {
		return fmt.Errorf("Failed emitting transition state: %s", err)
	}

	d.Unlock()
	return nil
}

func (d *LightDevice) SetBatch(state *LightDeviceState) error {
	d.Lock()

	mergedState := d.state.Clone()
	if state.OnOff != nil {
		mergedState.OnOff = state.OnOff
	}
	if state.Brightness != nil {
		mergedState.Brightness = state.Brightness
	}
	if state.Color != nil {
		mergedState.Color = state.Color
	}
	if state.Transition != nil {
		mergedState.Transition = state.Transition
	}
	err := d.ApplyLightState(mergedState)

	d.Unlock()
	return err
}

func (d *LightDevice) SetOnOff(state bool) error {
	d.Lock()

	var err error

	if state == true {
		d.log.Infof("Turning Off")
	} else {
		d.log.Infof("Turning On")
	}

	if d.ApplyOnOff != nil {
		err = d.ApplyOnOff(state)
	} else {
		lightState := d.state.Clone()
		lightState.OnOff = &state

		err = d.ApplyLightState(lightState)
	}

	d.Unlock()
	return err
}

func (d *LightDevice) SetBrightness(state float64) error {
	if d.brightness == nil {
		return fmt.Errorf("This device does not have a brightness channel")
	}

	d.Lock()

	var err error

	d.log.Infof("Setting brightness to %f", state)

	if d.ApplyBrightness != nil {
		err = d.ApplyBrightness(state)
	} else {
		lightState := d.state.Clone()
		lightState.Brightness = &state

		err = d.ApplyLightState(lightState)
	}

	d.Unlock()
	return err
}

func containsString(haystack []string, needle string) bool {
	for _, v := range haystack {
		if v == needle {
			return true
		}
	}
	return false
}

func (d *LightDevice) SetColor(state *channels.ColorState) error {
	if d.color == nil {
		return fmt.Errorf("This device does not have a color channel")
	}

	d.Lock()

	var err error

	lightState := d.state.Clone()
	lightState.Color = state

	if !containsString(d.colorModes, state.Mode) {
		d.log.Debugf("Does not support the color mode: %s, so converting", state.Mode)

		if lightState.Brightness == nil {
			d.log.Warningf("No brightness value available, but can't convert without it, so defaulting to 1")
			brightness := float64(1)
			lightState.Brightness = &brightness
		}

		var color colorful.Color

		switch lightState.Color.Mode {
		case "hue":
			color = colorful.Hsv(*lightState.Color.Hue*float64(360), *lightState.Color.Saturation, *lightState.Brightness)
		case "temperature":
			color = temperatureToColor(float64(*lightState.Color.Temperature))
		case "xy":
			color = colorful.Xyy(*lightState.Color.X, *lightState.Color.Y, *lightState.Brightness)
		default:
			return fmt.Errorf("Unknown color mode: %s", lightState.Color.Mode)
		}

		h, c, _ := color.Hcl()
		lightState.Color = &channels.ColorState{
			Mode:       "hue",
			Hue:        &h,
			Saturation: &c,
		}

	}

	json, _ := json.Marshal(lightState)
	d.log.Infof("Setting Color to %s", json)

	if d.ApplyColor != nil {
		err = d.ApplyColor(state)
	} else {
		err = d.ApplyLightState(lightState)
	}

	d.Unlock()
	return err
}

func (d *LightDevice) SetTransition(state int) error {
	if d.transition == nil {
		return fmt.Errorf("This device does not have a transition channel")
	}

	d.Lock()

	d.state.Transition = &state

	var err error

	if d.ApplyTransition != nil {
		err = d.ApplyTransition(state)
	}
	// I don't think we'd ever want to send a full state to the bulb if we are only updating the transition time

	d.Unlock()
	return err
}

func (d *LightDevice) ToggleOnOff() error {
	if d.state.OnOff == nil {
		d.log.Warningf("On-off channel is in an unknown state for toggling. Setting to off.")
		return d.SetOnOff(false)
	}
	return d.SetOnOff(!*d.state.OnOff)
}

func (d *LightDevice) EnableOnOffChannel() error {
	d.onOff = channels.NewOnOffChannel(d)
	return d.bus.AddChannel(d.onOff, "on-off", "on-off")
}

func (d *LightDevice) EnableBrightnessChannel() error {
	d.brightness = channels.NewBrightnessChannel(d)
	return d.bus.AddChannel(d.brightness, "brightness", "brightness")
}

func (d *LightDevice) EnableColorChannel(supportedModes ...string) error {
	if len(supportedModes) == 0 {
		log.Errorf("You must support at least one color mode")
	}
	if !containsString(supportedModes, "hue") {
		log.Errorf("You must support at least hue color values")
	}
	d.colorModes = supportedModes
	d.color = channels.NewColorChannel(d)
	return d.bus.AddChannel(d.color, "color", "color")
}

func (d *LightDevice) EnableTransitionChannel() error {
	d.transition = channels.NewTransitionChannel(d)
	return d.bus.AddChannel(d.transition, "transition", "transition")
}

func CreateLightDevice(name string, bus *ninja.DeviceBus) (*LightDevice, error) {

	light := &LightDevice{
		bus: bus,
		log: logger.GetLogger("LightDevice - " + name),
	}

	bus.AddChannel(&LightBatchChannel{light}, "core.batching", "batch")

	light.log.Infof("Created")

	return light, nil
}

// from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
func temperatureToColor(Temperature float64) colorful.Color {

	Temperature = Temperature / 100

	//Calculate Red:
	var Red float64

	if Temperature <= 66 {
		Red = 255
	} else {
		Red = Temperature - 60
		Red = 329.698727446 * math.Pow(Red, -0.1332047592)
		if Red < 0 {
			Red = 0
		}
		if Red > 255 {
			Red = 255
		}
	}

	//Calculate Green:
	var Green float64

	if Temperature <= 66 {
		Green = Temperature
		Green = 99.4708025861*math.Log(Green) - 161.1195681661
		if Green < 0 {
			Green = 0
		}
		if Green > 255 {
			Green = 255
		}
	} else {
		Green = Temperature - 60
		Green = 288.1221695283 * math.Pow(Green, -0.0755148492)
		if Green < 0 {
			Green = 0
		}
		if Green > 255 {
			Green = 255
		}
	}

	//Calculate Blue:
	var Blue float64

	if Temperature >= 66 {
		Blue = 255
	} else if Temperature <= 19 {

		Blue = 0
	} else {
		Blue = Temperature - 10
		Blue = 138.5177312231*math.Log(Blue) - 305.0447927307
		if Blue < 0 {
			Blue = 0
		}
		if Blue > 255 {
			Blue = 255
		}
	}

	return colorful.Color{Red / 255.0, Green / 255.0, Blue / 255.0}
}
<|endoftext|>


<commit_before>package db

import (
	"fmt"
	"strings"
	"time"

	"github.com/quilt/quilt/util"
)

// A Container row is created for each container specified by the policy.  Each row will
// eventually be instantiated within its corresponding cluster.
// Used only by the minion.
type Container struct {
	ID int `json:"-"`

	IP                string            `json:",omitempty"`
	Minion            string            `json:",omitempty"`
	EndpointID        string            `json:",omitempty"`
	StitchID          string            `json:",omitempty"`
	DockerID          string            `json:",omitempty"`
	Status            string            `json:",omitempty"`
	Command           []string          `json:",omitempty"`
	Labels            []string          `json:",omitempty"`
	Env               map[string]string `json:",omitempty"`
	FilepathToContent map[string]string `json:",omitempty"`
	Hostname          string            `json:"-"`
	Created           time.Time         `json:","`

	Image      string `json:",omitempty"`
	ImageID    string `json:",omitempty"`
	Dockerfile string `json:"-"`
}

// ContainerSlice is an alias for []Container to allow for joins
type ContainerSlice []Container

// InsertContainer creates a new container row and inserts it into the database.
func (db Database) InsertContainer() Container {
	result := Container{ID: db.nextID()}
	db.insert(result)
	return result
}

// SelectFromContainer gets all containers in the database that satisfy 'check'.
func (db Database) SelectFromContainer(check func(Container) bool) []Container {
	var result []Container
	for _, row := range db.selectRows(ContainerTable) {
		if check == nil || check(row.(Container)) {
			result = append(result, row.(Container))
		}
	}

	return result
}

// SelectFromContainer gets all containers in the database that satisfy the 'check'.
func (conn Conn) SelectFromContainer(check func(Container) bool) []Container {
	var containers []Container
	conn.Txn(ContainerTable).Run(func(view Database) error {
		containers = view.SelectFromContainer(check)
		return nil
	})
	return containers
}

func (c Container) getID() int {
	return c.ID
}

func (c Container) String() string {
	cmdStr := strings.Join(append([]string{"run", c.Image}, c.Command...), " ")
	tags := []string{cmdStr}

	if c.ImageID != "" {
		id := util.ShortUUID(c.ImageID)
		tags = append(tags, fmt.Sprintf("ImageID: %s", id))
	}

	if c.DockerID != "" {
		id := util.ShortUUID(c.DockerID)
		tags = append(tags, fmt.Sprintf("DockerID: %s", id))
	}

	if c.Minion != "" {
		tags = append(tags, fmt.Sprintf("Minion: %s", c.Minion))
	}

	if c.StitchID != "" {
		tags = append(tags, fmt.Sprintf("StitchID: %s", c.StitchID))
	}

	if c.IP != "" {
		tags = append(tags, fmt.Sprintf("IP: %s", c.IP))
	}

	if c.Hostname != "" {
		tags = append(tags, fmt.Sprintf("Hostname: %s", c.Hostname))
	}

	if len(c.Labels) > 0 {
		tags = append(tags, fmt.Sprintf("Labels: %s", c.Labels))
	}

	if len(c.Env) > 0 {
		tags = append(tags, fmt.Sprintf("Env: %s", c.Env))
	}

	if len(c.Status) > 0 {
		tags = append(tags, fmt.Sprintf("Status: %s", c.Status))
	}

	if !c.Created.IsZero() {
		tags = append(tags, fmt.Sprintf("Created: %s", c.Created.String()))
	}

	return fmt.Sprintf("Container-%d{%s}", c.ID, strings.Join(tags, ", "))
}

func (c Container) less(r row) bool {
	return c.StitchID < r.(Container).StitchID
}

// Less implements less than for sort.Interface.
func (cs ContainerSlice) Less(i, j int) bool {
	return cs[i].less(cs[j])
}

// Swap implements swapping for sort.Interface.
func (cs ContainerSlice) Swap(i, j int) {
	cs[i], cs[j] = cs[j], cs[i]
}

// Get returns the value contained at the given index
func (cs ContainerSlice) Get(ii int) interface{} {
	return cs[ii]
}

// Len returns the number of items in the slice
func (cs ContainerSlice) Len() int {
	return len(cs)
}
<commit_msg>api: Make container hostnames viewable through the API client<commit_after>package db

import (
	"fmt"
	"strings"
	"time"

	"github.com/quilt/quilt/util"
)

// A Container row is created for each container specified by the policy.  Each row will
// eventually be instantiated within its corresponding cluster.
// Used only by the minion.
type Container struct {
	ID int `json:"-"`

	IP                string            `json:",omitempty"`
	Minion            string            `json:",omitempty"`
	EndpointID        string            `json:",omitempty"`
	StitchID          string            `json:",omitempty"`
	DockerID          string            `json:",omitempty"`
	Status            string            `json:",omitempty"`
	Command           []string          `json:",omitempty"`
	Labels            []string          `json:",omitempty"`
	Env               map[string]string `json:",omitempty"`
	FilepathToContent map[string]string `json:",omitempty"`
	Hostname          string            `json:",omitempty"`
	Created           time.Time         `json:","`

	Image      string `json:",omitempty"`
	ImageID    string `json:",omitempty"`
	Dockerfile string `json:"-"`
}

// ContainerSlice is an alias for []Container to allow for joins
type ContainerSlice []Container

// InsertContainer creates a new container row and inserts it into the database.
func (db Database) InsertContainer() Container {
	result := Container{ID: db.nextID()}
	db.insert(result)
	return result
}

// SelectFromContainer gets all containers in the database that satisfy 'check'.
func (db Database) SelectFromContainer(check func(Container) bool) []Container {
	var result []Container
	for _, row := range db.selectRows(ContainerTable) {
		if check == nil || check(row.(Container)) {
			result = append(result, row.(Container))
		}
	}

	return result
}

// SelectFromContainer gets all containers in the database that satisfy the 'check'.
func (conn Conn) SelectFromContainer(check func(Container) bool) []Container {
	var containers []Container
	conn.Txn(ContainerTable).Run(func(view Database) error {
		containers = view.SelectFromContainer(check)
		return nil
	})
	return containers
}

func (c Container) getID() int {
	return c.ID
}

func (c Container) String() string {
	cmdStr := strings.Join(append([]string{"run", c.Image}, c.Command...), " ")
	tags := []string{cmdStr}

	if c.ImageID != "" {
		id := util.ShortUUID(c.ImageID)
		tags = append(tags, fmt.Sprintf("ImageID: %s", id))
	}

	if c.DockerID != "" {
		id := util.ShortUUID(c.DockerID)
		tags = append(tags, fmt.Sprintf("DockerID: %s", id))
	}

	if c.Minion != "" {
		tags = append(tags, fmt.Sprintf("Minion: %s", c.Minion))
	}

	if c.StitchID != "" {
		tags = append(tags, fmt.Sprintf("StitchID: %s", c.StitchID))
	}

	if c.IP != "" {
		tags = append(tags, fmt.Sprintf("IP: %s", c.IP))
	}

	if c.Hostname != "" {
		tags = append(tags, fmt.Sprintf("Hostname: %s", c.Hostname))
	}

	if len(c.Labels) > 0 {
		tags = append(tags, fmt.Sprintf("Labels: %s", c.Labels))
	}

	if len(c.Env) > 0 {
		tags = append(tags, fmt.Sprintf("Env: %s", c.Env))
	}

	if len(c.Status) > 0 {
		tags = append(tags, fmt.Sprintf("Status: %s", c.Status))
	}

	if !c.Created.IsZero() {
		tags = append(tags, fmt.Sprintf("Created: %s", c.Created.String()))
	}

	return fmt.Sprintf("Container-%d{%s}", c.ID, strings.Join(tags, ", "))
}

func (c Container) less(r row) bool {
	return c.StitchID < r.(Container).StitchID
}

// Less implements less than for sort.Interface.
func (cs ContainerSlice) Less(i, j int) bool {
	return cs[i].less(cs[j])
}

// Swap implements swapping for sort.Interface.
func (cs ContainerSlice) Swap(i, j int) {
	cs[i], cs[j] = cs[j], cs[i]
}

// Get returns the value contained at the given index
func (cs ContainerSlice) Get(ii int) interface{} {
	return cs[ii]
}

// Len returns the number of items in the slice
func (cs ContainerSlice) Len() int {
	return len(cs)
}
<|endoftext|>


<commit_before>package herd

import (
	"fmt"
	"github.com/Symantec/Dominator/lib/filesystem"
	"github.com/Symantec/Dominator/lib/filter"
	subproto "github.com/Symantec/Dominator/proto/sub"
	"os"
	"path"
)

type state struct {
	subInodeToRequiredInode map[uint64]uint64
}

func (sub *Sub) buildUpdateRequest(request *subproto.UpdateRequest) {
	fmt.Println("buildUpdateRequest()") // TODO(rgooch): Delete debugging.
	subFS := sub.fileSystem
	requiredImage := sub.herd.getImage(sub.requiredImage)
	requiredFS := requiredImage.FileSystem
	filter := requiredImage.Filter
	var state state
	state.subInodeToRequiredInode = make(map[uint64]uint64)
	compareDirectories(request, &state, &subFS.Directory, &requiredFS.Directory,
		"", filter)
	// TODO(rgooch): Implement this.
}

func compareDirectories(request *subproto.UpdateRequest, state *state,
	subDirectory, requiredDirectory *filesystem.Directory,
	parentName string, filter *filter.Filter) {
	requiredPathName := path.Join(parentName, requiredDirectory.Name)
	// First look for entries that should be deleted.
	makeSubDirectory := false
	if subDirectory == nil {
		makeSubDirectory = true
	} else {
		subPathName := path.Join(parentName, subDirectory.Name)
		for name, subEntry := range subDirectory.EntriesByName {
			pathname := path.Join(subPathName, entryName(subEntry))
			if filter.Match(pathname) {
				continue
			}
			if _, ok := requiredDirectory.EntriesByName[name]; !ok {
				request.PathsToDelete = append(request.PathsToDelete, pathname)
				fmt.Printf("Delete: %s\n", pathname) // HACK
			}
		}
		if !filesystem.CompareDirectoriesMetadata(subDirectory,
			requiredDirectory, os.Stdout) {
			fmt.Printf("Different directory: %s...\n", requiredPathName) // HACK
			makeSubDirectory = true
			// TODO(rgooch): Update metadata.
		}
	}
	if makeSubDirectory {
		var newdir subproto.Directory
		newdir.Name = requiredPathName
		newdir.Mode = requiredDirectory.Mode
		newdir.Uid = requiredDirectory.Uid
		newdir.Gid = requiredDirectory.Gid
		request.DirectoriesToMake = append(request.DirectoriesToMake, newdir)
	}
	for name, requiredEntry := range requiredDirectory.EntriesByName {
		pathname := path.Join(requiredPathName, entryName(requiredEntry))
		if filter.Match(pathname) {
			continue
		}
		if subDirectory == nil {
			compareEntries(request, state, nil, requiredEntry, requiredPathName,
				filter)
		} else {
			if subEntry, ok := subDirectory.EntriesByName[name]; ok {
				compareEntries(request, state, subEntry, requiredEntry,
					requiredPathName, filter)
			} else {
				compareEntries(request, state, nil, requiredEntry,
					requiredPathName, filter)
			}
		}
	}
}

func entryName(entry interface{}) string {
	switch e := entry.(type) {
	case *filesystem.RegularFile:
		return e.Name
	case *filesystem.Symlink:
		return e.Name
	case *filesystem.File:
		return e.Name
	case *filesystem.Directory:
		return e.Name
	}
	panic("Unsupported entry type")
}

func compareEntries(request *subproto.UpdateRequest, state *state,
	subEntry, requiredEntry interface{},
	parentName string, filter *filter.Filter) {
	switch re := requiredEntry.(type) {
	case *filesystem.RegularFile:
		compareRegularFile(request, state, subEntry, re, parentName)
		return
	case *filesystem.Symlink:
		compareSymlink(request, state, subEntry, re, parentName)
		return
	case *filesystem.File:
		compareFile(request, state, subEntry, re, parentName)
		return
	case *filesystem.Directory:
		compareDirectory(request, state, subEntry, re, parentName, filter)
		return
	}
	panic("Unsupported entry type")
}

func compareRegularFile(request *subproto.UpdateRequest, state *state,
	subEntry interface{}, requiredRegularFile *filesystem.RegularFile,
	parentName string) {
	debugFilename := path.Join(parentName, requiredRegularFile.Name)
	if subRegularFile, ok := subEntry.(*filesystem.RegularFile); ok {
		if requiredInode, ok :=
			state.subInodeToRequiredInode[subRegularFile.InodeNumber]; ok {
			if requiredInode == requiredRegularFile.InodeNumber {
				//
				fmt.Printf("Different links: %s...\n", debugFilename) // HACK
			}
		} else {
			state.subInodeToRequiredInode[subRegularFile.InodeNumber] =
				requiredRegularFile.InodeNumber
		}
		sameMetadata := filesystem.CompareRegularInodesMetadata(
			subRegularFile.Inode(), requiredRegularFile.Inode(),
			os.Stdout)
		sameData := filesystem.CompareRegularInodesData(subRegularFile.Inode(),
			requiredRegularFile.Inode(), os.Stdout)
		if sameMetadata && sameData {
			return
		}
		fmt.Printf("Different rfile: %s...\n", debugFilename) // HACK
	} else {
		fmt.Printf("Add rfile: %s...\n", debugFilename) // HACK
	}
	// TODO(rgooch): Delete entry and replace.
}

func compareSymlink(request *subproto.UpdateRequest, state *state,
	subEntry interface{}, requiredSymlink *filesystem.Symlink,
	parentName string) {
	debugFilename := path.Join(parentName, requiredSymlink.Name)
	if subSymlink, ok := subEntry.(*filesystem.Symlink); ok {
		if requiredInode, ok :=
			state.subInodeToRequiredInode[subSymlink.InodeNumber]; ok {
			if requiredInode != requiredSymlink.InodeNumber {
				fmt.Printf("Different links: %s...\n", debugFilename) // HACK
			}
		} else {
			state.subInodeToRequiredInode[subSymlink.InodeNumber] =
				requiredSymlink.InodeNumber
		}
		if filesystem.CompareSymlinkInodes(subSymlink.Inode(),
			requiredSymlink.Inode(), os.Stdout) {
			return
		}
		fmt.Printf("Different symlink: %s...\n", debugFilename) // HACK
	} else {
		fmt.Printf("Add symlink: %s...\n", debugFilename) // HACK
	}
	// TODO(rgooch): Delete entry and replace.
}

func compareFile(request *subproto.UpdateRequest, state *state,
	subEntry interface{}, requiredFile *filesystem.File,
	parentName string) {
	debugFilename := path.Join(parentName, requiredFile.Name)
	if subFile, ok := subEntry.(*filesystem.File); ok {
		if requiredInode, ok :=
			state.subInodeToRequiredInode[subFile.InodeNumber]; ok {
			if requiredInode != requiredFile.InodeNumber {
				fmt.Printf("Different links: %s...\n", debugFilename) // HACK
			}
		} else {
			state.subInodeToRequiredInode[subFile.InodeNumber] =
				requiredFile.InodeNumber
		}
		if filesystem.CompareInodes(subFile.Inode(), requiredFile.Inode(),
			os.Stdout) {
			return
		}
		fmt.Printf("Different file: %s...\n", debugFilename) // HACK
	} else {
		fmt.Printf("Add file: %s...\n", debugFilename) // HACK
	}
	// TODO(rgooch): Delete entry and replace.
}

func compareDirectory(request *subproto.UpdateRequest, state *state,
	subEntry interface{}, requiredDirectory *filesystem.Directory,
	parentName string, filter *filter.Filter) {
	if subDirectory, ok := subEntry.(*filesystem.Directory); ok {
		compareDirectories(request, state, subDirectory, requiredDirectory,
			parentName, filter)
	} else {
		compareDirectories(request, state, nil, requiredDirectory, parentName,
			filter)
	}
}
<commit_msg>Pass triggers to sub in buildUpdateRequest().<commit_after>package herd

import (
	"fmt"
	"github.com/Symantec/Dominator/lib/filesystem"
	"github.com/Symantec/Dominator/lib/filter"
	subproto "github.com/Symantec/Dominator/proto/sub"
	"os"
	"path"
)

type state struct {
	subInodeToRequiredInode map[uint64]uint64
}

func (sub *Sub) buildUpdateRequest(request *subproto.UpdateRequest) {
	fmt.Println("buildUpdateRequest()") // TODO(rgooch): Delete debugging.
	subFS := sub.fileSystem
	requiredImage := sub.herd.getImage(sub.requiredImage)
	requiredFS := requiredImage.FileSystem
	filter := requiredImage.Filter
	request.Triggers = requiredImage.Triggers
	var state state
	state.subInodeToRequiredInode = make(map[uint64]uint64)
	compareDirectories(request, &state, &subFS.Directory, &requiredFS.Directory,
		"", filter)
	// TODO(rgooch): Implement this.
}

func compareDirectories(request *subproto.UpdateRequest, state *state,
	subDirectory, requiredDirectory *filesystem.Directory,
	parentName string, filter *filter.Filter) {
	requiredPathName := path.Join(parentName, requiredDirectory.Name)
	// First look for entries that should be deleted.
	makeSubDirectory := false
	if subDirectory == nil {
		makeSubDirectory = true
	} else {
		subPathName := path.Join(parentName, subDirectory.Name)
		for name, subEntry := range subDirectory.EntriesByName {
			pathname := path.Join(subPathName, entryName(subEntry))
			if filter.Match(pathname) {
				continue
			}
			if _, ok := requiredDirectory.EntriesByName[name]; !ok {
				request.PathsToDelete = append(request.PathsToDelete, pathname)
				fmt.Printf("Delete: %s\n", pathname) // HACK
			}
		}
		if !filesystem.CompareDirectoriesMetadata(subDirectory,
			requiredDirectory, os.Stdout) {
			fmt.Printf("Different directory: %s...\n", requiredPathName) // HACK
			makeSubDirectory = true
			// TODO(rgooch): Update metadata.
		}
	}
	if makeSubDirectory {
		var newdir subproto.Directory
		newdir.Name = requiredPathName
		newdir.Mode = requiredDirectory.Mode
		newdir.Uid = requiredDirectory.Uid
		newdir.Gid = requiredDirectory.Gid
		request.DirectoriesToMake = append(request.DirectoriesToMake, newdir)
	}
	for name, requiredEntry := range requiredDirectory.EntriesByName {
		pathname := path.Join(requiredPathName, entryName(requiredEntry))
		if filter.Match(pathname) {
			continue
		}
		if subDirectory == nil {
			compareEntries(request, state, nil, requiredEntry, requiredPathName,
				filter)
		} else {
			if subEntry, ok := subDirectory.EntriesByName[name]; ok {
				compareEntries(request, state, subEntry, requiredEntry,
					requiredPathName, filter)
			} else {
				compareEntries(request, state, nil, requiredEntry,
					requiredPathName, filter)
			}
		}
	}
}

func entryName(entry interface{}) string {
	switch e := entry.(type) {
	case *filesystem.RegularFile:
		return e.Name
	case *filesystem.Symlink:
		return e.Name
	case *filesystem.File:
		return e.Name
	case *filesystem.Directory:
		return e.Name
	}
	panic("Unsupported entry type")
}

func compareEntries(request *subproto.UpdateRequest, state *state,
	subEntry, requiredEntry interface{},
	parentName string, filter *filter.Filter) {
	switch re := requiredEntry.(type) {
	case *filesystem.RegularFile:
		compareRegularFile(request, state, subEntry, re, parentName)
		return
	case *filesystem.Symlink:
		compareSymlink(request, state, subEntry, re, parentName)
		return
	case *filesystem.File:
		compareFile(request, state, subEntry, re, parentName)
		return
	case *filesystem.Directory:
		compareDirectory(request, state, subEntry, re, parentName, filter)
		return
	}
	panic("Unsupported entry type")
}

func compareRegularFile(request *subproto.UpdateRequest, state *state,
	subEntry interface{}, requiredRegularFile *filesystem.RegularFile,
	parentName string) {
	debugFilename := path.Join(parentName, requiredRegularFile.Name)
	if subRegularFile, ok := subEntry.(*filesystem.RegularFile); ok {
		if requiredInode, ok :=
			state.subInodeToRequiredInode[subRegularFile.InodeNumber]; ok {
			if requiredInode == requiredRegularFile.InodeNumber {
				//
				fmt.Printf("Different links: %s...\n", debugFilename) // HACK
			}
		} else {
			state.subInodeToRequiredInode[subRegularFile.InodeNumber] =
				requiredRegularFile.InodeNumber
		}
		sameMetadata := filesystem.CompareRegularInodesMetadata(
			subRegularFile.Inode(), requiredRegularFile.Inode(),
			os.Stdout)
		sameData := filesystem.CompareRegularInodesData(subRegularFile.Inode(),
			requiredRegularFile.Inode(), os.Stdout)
		if sameMetadata && sameData {
			return
		}
		fmt.Printf("Different rfile: %s...\n", debugFilename) // HACK
	} else {
		fmt.Printf("Add rfile: %s...\n", debugFilename) // HACK
	}
	// TODO(rgooch): Delete entry and replace.
}

func compareSymlink(request *subproto.UpdateRequest, state *state,
	subEntry interface{}, requiredSymlink *filesystem.Symlink,
	parentName string) {
	debugFilename := path.Join(parentName, requiredSymlink.Name)
	if subSymlink, ok := subEntry.(*filesystem.Symlink); ok {
		if requiredInode, ok :=
			state.subInodeToRequiredInode[subSymlink.InodeNumber]; ok {
			if requiredInode != requiredSymlink.InodeNumber {
				fmt.Printf("Different links: %s...\n", debugFilename) // HACK
			}
		} else {
			state.subInodeToRequiredInode[subSymlink.InodeNumber] =
				requiredSymlink.InodeNumber
		}
		if filesystem.CompareSymlinkInodes(subSymlink.Inode(),
			requiredSymlink.Inode(), os.Stdout) {
			return
		}
		fmt.Printf("Different symlink: %s...\n", debugFilename) // HACK
	} else {
		fmt.Printf("Add symlink: %s...\n", debugFilename) // HACK
	}
	// TODO(rgooch): Delete entry and replace.
}

func compareFile(request *subproto.UpdateRequest, state *state,
	subEntry interface{}, requiredFile *filesystem.File,
	parentName string) {
	debugFilename := path.Join(parentName, requiredFile.Name)
	if subFile, ok := subEntry.(*filesystem.File); ok {
		if requiredInode, ok :=
			state.subInodeToRequiredInode[subFile.InodeNumber]; ok {
			if requiredInode != requiredFile.InodeNumber {
				fmt.Printf("Different links: %s...\n", debugFilename) // HACK
			}
		} else {
			state.subInodeToRequiredInode[subFile.InodeNumber] =
				requiredFile.InodeNumber
		}
		if filesystem.CompareInodes(subFile.Inode(), requiredFile.Inode(),
			os.Stdout) {
			return
		}
		fmt.Printf("Different file: %s...\n", debugFilename) // HACK
	} else {
		fmt.Printf("Add file: %s...\n", debugFilename) // HACK
	}
	// TODO(rgooch): Delete entry and replace.
}

func compareDirectory(request *subproto.UpdateRequest, state *state,
	subEntry interface{}, requiredDirectory *filesystem.Directory,
	parentName string, filter *filter.Filter) {
	if subDirectory, ok := subEntry.(*filesystem.Directory); ok {
		compareDirectories(request, state, subDirectory, requiredDirectory,
			parentName, filter)
	} else {
		compareDirectories(request, state, nil, requiredDirectory, parentName,
			filter)
	}
}
<|endoftext|>