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

// compat provides the ability to read and write XMILE files that
// correspond to the implementation in isee systems STELLA and iThink
// version 10 products.
package compat

import (
	"encoding/xml"
	"fmt"
	"github.com/bpowers/go-xmile/xmile"
	"log"
	"reflect"
)

// An XML node
type Node interface {
	node()
}

func (*File) node()      {}
func (*IseePrefs) node() {}
func (*Model) node()     {}
func (*Variable) node()  {}

// File represents the entire contents of a XMILE document as
// implemented by STELLA & iThink version ~10.0.3
type File struct {
	XMLName    xml.Name           `xml:"http://www.systemdynamics.org/XMILE xmile"`
	Version    string             `xml:"version,attr"`
	Level      int                `xml:"level,attr"`
	IseeHack   string             `xml:"xmlns:isee,attr"` // FIXME(bp) workaround for, I think go issue w.r.t. namespaces
	Header     xmile.Header       `xml:"header"`
	SimSpec    xmile.SimSpec      `xml:"sim_specs"`
	Dimensions []*xmile.Dimension `xml:"dimensions>dim,omitempty"`
	ModelUnits xmile.ModelUnits   `xml:"model_units"`
	IseePrefs  IseePrefs          `xml:"prefs"`
	EqnPrefs   xmile.EqnPrefs     `xml:"equation_prefs"`
	Models     []*Model           `xml:"model,omitempty"`
}

// IseePrefs contains preferences used by STELLA and iThink
type IseePrefs struct {
	XMLName                xml.Name
	Layer                  string         `xml:"layer,attr"`
	GridWidth              string         `xml:"grid_width,attr"`
	GridHeight             string         `xml:"grid_height,attr"`
	DivByZeroAlert         bool           `xml:"divide_by_zero_alert,attr"`
	ShowModPrefix          bool           `xml:"show_module_prefix,attr"`
	HideTransparentButtons bool           `xml:"hide_transparent_buttons,attr"`
	Window                 xmile.Window   `xml:"window"`
	Security               xmile.Security `xml:"security"`
	PrintSetup             xmile.Window   `xml:"print_setup"`
}

// Model represents a container for both the computational definition
// of a system dynamics model, as well as the visual representations
// of that model.
type Model struct {
	XMLName   xml.Name    `xml:"model"`
	Name      string      `xml:"name,attr,omitempty"`
	Variables []*Variable `xml:",any,omitempty"`
	Display   xmile.View  `xml:"display"`
	Interface xmile.View  `xml:"interface"`
}

// Variable is the definition of a model entity.  Some fields, such as
// Inflows and Outflows are only applicable for certain variable
// types.  The type is determined by the tag name and is stored in
// XMLName.Name.
type Variable struct {
	XMLName xml.Name
	xmile.Variable
	Display *xmile.Display `xml:"display"`
}

// NewFile returns a new File object of the given XMILE compliance
// level and name, along with a new UUID.  If you have a file on disk
// you are looking to process, please see ReadFile.
func NewFile(level int, name string) *File {
	id, err := xmile.UUIDv4()
	if err != nil {
		// this is pretty frowned upon, but I don't want
		// NewFile's interface to potentially fail, and if
		// rand.Read fails we have bigger issues.
		panic(err)
	}

	f := &File{Version: "1.0", Level: level}
	f.Header = xmile.Header{
		Name:   name,
		UUID:   id,
		Vendor: "XMILE TC",
		Product: xmile.Product{
			Name:    "go-xmile",
			Version: "0.1",
			Lang:    "en",
		},
	}
	return f
}

// there is a slight impedence mismatch between the spec & the go xml
// marshaler.  cleanIseeDisplayTag works to clean up artifacts related
// to this when reading in <display> tags.
func cleanIseeDisplayTag(d *xmile.Display, inNS bool) {
	d.XMLName.Space = ""
	switch d.XMLName.Local {
	case "text_box", "item":
		// only items with valid content
	case "story":
		inNS = true
		fallthrough
	default:
		d.Content = ""
	}

	if inNS && d.XMLName.Local != "text_box" {
		d.XMLName.Space = "isee"
	}

	for _, c := range d.Children {
		cleanIseeDisplayTag(c, inNS)
	}
}

// ReadFile takes a block of xml content that represents a XMILE file,
// as implemented by iThink/STELLA v10.0.3, and returns a File
// structure, or an error.  The hope is that iThink files rountripped
// through this function will remain readable by iThink.  If not,
// please report it.
func ReadFile(contents []byte) (*File, error) {
	f := new(File)
	if err := xml.Unmarshal(contents, f); err != nil {
		return nil, fmt.Errorf("xml.Unmarshal: %s", err)
	}

	// this bit is cleaning up some weird interactions the go
	// reflection-based code has without isee xmlnamespace.

	// BUG(bp) when we read in a tag with a variable tag name, the
	// XMILE namespace gets propagated to that tag.
	f.IseeHack = "http://iseesystems.com/XMILE"
	f.IseePrefs.XMLName.Space = "isee"
	f.IseePrefs.Window.XMLName.Space = "isee"
	f.IseePrefs.Security.XMLName.Space = "isee"
	f.IseePrefs.PrintSetup.XMLName.Space = "isee"
	f.EqnPrefs.XMLName.Space = "isee"
	for _, m := range f.Models {
		m.Display.XMLName.Space = ""
		m.Interface.XMLName.Space = ""
		for _, v := range m.Variables {
			v.XMLName.Space = ""
			cleanIseeDisplayTag(v.Display, false)
		}
		for _, v := range m.Display.Ents {
			cleanIseeDisplayTag(v, false)
		}
		for _, v := range m.Interface.Ents {
			cleanIseeDisplayTag(v, false)
		}
	}

	return f, nil
}

func ConvertToIsee(f *xmile.File) (*File, error) {
	return nil, fmt.Errorf("not implemented")
}

func convertFromIseeField(fin reflect.Value, stripVendorTags bool) (fout reflect.Value, err error) {
	vendorField, ok := fin.Interface().(Node)
	if !ok {
		return fin, nil
	}

	var xfin xmile.Node
	xfin, err = ConvertFromIsee(vendorField, stripVendorTags)
	if err != nil {
		err = fmt.Errorf("ConvertFromIsee(%#v): %s", vendorField, err)
	}
	return reflect.ValueOf(xfin).Elem(), nil
}

func convertFromIseeSlice(fin reflect.Value, stripVendorTags bool) (fout reflect.Value, err error) {
	if fin.Len() == 0 || fin.IsNil() {
		return fin, nil
	}
	e0 := fin.Index(0)

	var slice interface{}

	switch e0.Interface().(type) {
	case *Model:
		slice = make([]*xmile.Model, fin.Len())
	case *Variable:
		slice = make([]*xmile.Variable, fin.Len())
	default:
		log.Printf("slice type not supported: %s", e0.Type())
		return reflect.ValueOf([]interface{}{}), nil
	}

	for i := 0; i < fin.Len(); i++ {
		m, _ := fin.Index(i).Interface().(Node)
		var xm xmile.Node
		xm, err = ConvertFromIsee(m, stripVendorTags)
		if err != nil {
			return
		}
		switch sl := slice.(type) {
		case []*xmile.Model:
			sl[i] = xm.(*xmile.Model)
		case []*xmile.Variable:
			sl[i] = xm.(*xmile.Variable)
		}
	}
	return reflect.ValueOf(slice), nil

}

type valProvider func() reflect.Value

// TODO(bp) f is an interface{} so that any tag can be passed, and the
// corresponding TC xmile tag returned.  Currently, only the root File
// tag is supported.
//
// TODO(bp) implement stripVendorTags
//
// ConvertFromIsee takes an isee tag and converts it to the current TC
// draft XMILE spec.  If stripVendorTags is true, isee-namespaced tags
// and attributes that would otherwise have been passed through will
// be removed.
func ConvertFromIsee(in Node, stripVendorTags bool) (out xmile.Node, err error) {
	switch n := in.(type) {
	case *File:
		out = new(xmile.File)
	case *Model:
		xm := new(xmile.Model)
		xm.Views = &[]*xmile.View{new(xmile.View), new(xmile.View)}
		*(*xm.Views)[0] = n.Display
		*(*xm.Views)[1] = n.Interface
		(*xm.Views)[0].XMLName.Local = "view"
		(*xm.Views)[1].XMLName.Local = "view"
		(*xm.Views)[1].Name = "interface"
		for _, v := range n.Variables {
			nd := new(xmile.Display)
			*nd = *v.Display
			nd.XMLName.Local = v.XMLName.Local
			nd.Name = v.Name
			(*xm.Views)[0].Ents = append((*xm.Views)[0].Ents, nd)
		}
		out = xm
	case *Variable:
		xv := new(xmile.Variable)
		*xv = n.Variable
		xv.XMLName = n.XMLName
		out = xv
		return
	default:
		return nil, fmt.Errorf("value (%#v) not convertable", in)
	}

	vin := reflect.ValueOf(in).Elem()
	vout := reflect.ValueOf(out).Elem()
	nfield := vin.NumField()
	for i := 0; i < nfield; i++ {
		fmt.Printf("\tfield: %s\n", vin.Type().Field(i).Name)
		fin := vin.Field(i)
		foutty, ok := vout.Type().FieldByName(vin.Type().Field(i).Name)
		if !ok {
			log.Printf("field %s not found on TC struct, skipping",
				vin.Type().Field(i).Name)
			continue
		}
		fout := vout.FieldByName(foutty.Name)
		if fin, err = convertFromIseeField(fin, stripVendorTags); err != nil {
			return nil, fmt.Errorf("convertFromVendorTag: %s", err)
		}

		isInd := false
		outVal := fout
		if fout.Kind() == reflect.Ptr {
			isInd = true
			outVal = fout.Elem()
		}

		switch outVal.Kind() {
		case reflect.Slice:
			fin, err = convertFromIseeSlice(fin, stripVendorTags)
			if err != nil {
				log.Printf("convertFromIseeSlice: %s", err)
				continue
			}
			if fin.Len() == 0 || fin.IsNil() {
				continue
			}
			fallthrough
		default:
			if isInd {
				fout.Set(fin.Addr())
			} else {
				fout.Set(fin)
			}
		}
	}

	return out, nil
}
<commit_msg>compat: comment out debug statements<commit_after>// Copyright 2013 Bobby Powers. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// compat provides the ability to read and write XMILE files that
// correspond to the implementation in isee systems STELLA and iThink
// version 10 products.
package compat

import (
	"encoding/xml"
	"fmt"
	"github.com/bpowers/go-xmile/xmile"
	"log"
	"reflect"
)

// An XML node
type Node interface {
	node()
}

func (*File) node()      {}
func (*IseePrefs) node() {}
func (*Model) node()     {}
func (*Variable) node()  {}

// File represents the entire contents of a XMILE document as
// implemented by STELLA & iThink version ~10.0.3
type File struct {
	XMLName    xml.Name           `xml:"http://www.systemdynamics.org/XMILE xmile"`
	Version    string             `xml:"version,attr"`
	Level      int                `xml:"level,attr"`
	IseeHack   string             `xml:"xmlns:isee,attr"` // FIXME(bp) workaround for, I think go issue w.r.t. namespaces
	Header     xmile.Header       `xml:"header"`
	SimSpec    xmile.SimSpec      `xml:"sim_specs"`
	Dimensions []*xmile.Dimension `xml:"dimensions>dim,omitempty"`
	ModelUnits xmile.ModelUnits   `xml:"model_units"`
	IseePrefs  IseePrefs          `xml:"prefs"`
	EqnPrefs   xmile.EqnPrefs     `xml:"equation_prefs"`
	Models     []*Model           `xml:"model,omitempty"`
}

// IseePrefs contains preferences used by STELLA and iThink
type IseePrefs struct {
	XMLName                xml.Name
	Layer                  string         `xml:"layer,attr"`
	GridWidth              string         `xml:"grid_width,attr"`
	GridHeight             string         `xml:"grid_height,attr"`
	DivByZeroAlert         bool           `xml:"divide_by_zero_alert,attr"`
	ShowModPrefix          bool           `xml:"show_module_prefix,attr"`
	HideTransparentButtons bool           `xml:"hide_transparent_buttons,attr"`
	Window                 xmile.Window   `xml:"window"`
	Security               xmile.Security `xml:"security"`
	PrintSetup             xmile.Window   `xml:"print_setup"`
}

// Model represents a container for both the computational definition
// of a system dynamics model, as well as the visual representations
// of that model.
type Model struct {
	XMLName   xml.Name    `xml:"model"`
	Name      string      `xml:"name,attr,omitempty"`
	Variables []*Variable `xml:",any,omitempty"`
	Display   xmile.View  `xml:"display"`
	Interface xmile.View  `xml:"interface"`
}

// Variable is the definition of a model entity.  Some fields, such as
// Inflows and Outflows are only applicable for certain variable
// types.  The type is determined by the tag name and is stored in
// XMLName.Name.
type Variable struct {
	XMLName xml.Name
	xmile.Variable
	Display *xmile.Display `xml:"display"`
}

// NewFile returns a new File object of the given XMILE compliance
// level and name, along with a new UUID.  If you have a file on disk
// you are looking to process, please see ReadFile.
func NewFile(level int, name string) *File {
	id, err := xmile.UUIDv4()
	if err != nil {
		// this is pretty frowned upon, but I don't want
		// NewFile's interface to potentially fail, and if
		// rand.Read fails we have bigger issues.
		panic(err)
	}

	f := &File{Version: "1.0", Level: level}
	f.Header = xmile.Header{
		Name:   name,
		UUID:   id,
		Vendor: "XMILE TC",
		Product: xmile.Product{
			Name:    "go-xmile",
			Version: "0.1",
			Lang:    "en",
		},
	}
	return f
}

// there is a slight impedence mismatch between the spec & the go xml
// marshaler.  cleanIseeDisplayTag works to clean up artifacts related
// to this when reading in <display> tags.
func cleanIseeDisplayTag(d *xmile.Display, inNS bool) {
	d.XMLName.Space = ""
	switch d.XMLName.Local {
	case "text_box", "item":
		// only items with valid content
	case "story":
		inNS = true
		fallthrough
	default:
		d.Content = ""
	}

	if inNS && d.XMLName.Local != "text_box" {
		d.XMLName.Space = "isee"
	}

	for _, c := range d.Children {
		cleanIseeDisplayTag(c, inNS)
	}
}

// ReadFile takes a block of xml content that represents a XMILE file,
// as implemented by iThink/STELLA v10.0.3, and returns a File
// structure, or an error.  The hope is that iThink files rountripped
// through this function will remain readable by iThink.  If not,
// please report it.
func ReadFile(contents []byte) (*File, error) {
	f := new(File)
	if err := xml.Unmarshal(contents, f); err != nil {
		return nil, fmt.Errorf("xml.Unmarshal: %s", err)
	}

	// this bit is cleaning up some weird interactions the go
	// reflection-based code has without isee xmlnamespace.

	// BUG(bp) when we read in a tag with a variable tag name, the
	// XMILE namespace gets propagated to that tag.
	f.IseeHack = "http://iseesystems.com/XMILE"
	f.IseePrefs.XMLName.Space = "isee"
	f.IseePrefs.Window.XMLName.Space = "isee"
	f.IseePrefs.Security.XMLName.Space = "isee"
	f.IseePrefs.PrintSetup.XMLName.Space = "isee"
	f.EqnPrefs.XMLName.Space = "isee"
	for _, m := range f.Models {
		m.Display.XMLName.Space = ""
		m.Interface.XMLName.Space = ""
		for _, v := range m.Variables {
			v.XMLName.Space = ""
			cleanIseeDisplayTag(v.Display, false)
		}
		for _, v := range m.Display.Ents {
			cleanIseeDisplayTag(v, false)
		}
		for _, v := range m.Interface.Ents {
			cleanIseeDisplayTag(v, false)
		}
	}

	return f, nil
}

func ConvertToIsee(f *xmile.File) (*File, error) {
	return nil, fmt.Errorf("not implemented")
}

func convertFromIseeField(fin reflect.Value, stripVendorTags bool) (fout reflect.Value, err error) {
	vendorField, ok := fin.Interface().(Node)
	if !ok {
		return fin, nil
	}

	var xfin xmile.Node
	xfin, err = ConvertFromIsee(vendorField, stripVendorTags)
	if err != nil {
		err = fmt.Errorf("ConvertFromIsee(%#v): %s", vendorField, err)
	}
	return reflect.ValueOf(xfin).Elem(), nil
}

func convertFromIseeSlice(fin reflect.Value, stripVendorTags bool) (fout reflect.Value, err error) {
	if fin.Len() == 0 || fin.IsNil() {
		return fin, nil
	}
	e0 := fin.Index(0)

	var slice interface{}

	switch e0.Interface().(type) {
	case *Model:
		slice = make([]*xmile.Model, fin.Len())
	case *Variable:
		slice = make([]*xmile.Variable, fin.Len())
	default:
		log.Printf("slice type not supported: %s", e0.Type())
		return reflect.ValueOf([]interface{}{}), nil
	}

	for i := 0; i < fin.Len(); i++ {
		m, _ := fin.Index(i).Interface().(Node)
		var xm xmile.Node
		xm, err = ConvertFromIsee(m, stripVendorTags)
		if err != nil {
			return
		}
		switch sl := slice.(type) {
		case []*xmile.Model:
			sl[i] = xm.(*xmile.Model)
		case []*xmile.Variable:
			sl[i] = xm.(*xmile.Variable)
		}
	}
	return reflect.ValueOf(slice), nil

}

type valProvider func() reflect.Value

// TODO(bp) f is an interface{} so that any tag can be passed, and the
// corresponding TC xmile tag returned.  Currently, only the root File
// tag is supported.
//
// TODO(bp) implement stripVendorTags
//
// ConvertFromIsee takes an isee tag and converts it to the current TC
// draft XMILE spec.  If stripVendorTags is true, isee-namespaced tags
// and attributes that would otherwise have been passed through will
// be removed.
func ConvertFromIsee(in Node, stripVendorTags bool) (out xmile.Node, err error) {
	switch n := in.(type) {
	case *File:
		out = new(xmile.File)
	case *Model:
		xm := new(xmile.Model)
		xm.Views = &[]*xmile.View{new(xmile.View), new(xmile.View)}
		*(*xm.Views)[0] = n.Display
		*(*xm.Views)[1] = n.Interface
		(*xm.Views)[0].XMLName.Local = "view"
		(*xm.Views)[1].XMLName.Local = "view"
		(*xm.Views)[1].Name = "interface"
		for _, v := range n.Variables {
			nd := new(xmile.Display)
			*nd = *v.Display
			nd.XMLName.Local = v.XMLName.Local
			nd.Name = v.Name
			(*xm.Views)[0].Ents = append((*xm.Views)[0].Ents, nd)
		}
		out = xm
	case *Variable:
		xv := new(xmile.Variable)
		*xv = n.Variable
		xv.XMLName = n.XMLName
		out = xv
		return
	default:
		return nil, fmt.Errorf("value (%#v) not convertable", in)
	}

	vin := reflect.ValueOf(in).Elem()
	vout := reflect.ValueOf(out).Elem()
	nfield := vin.NumField()
	for i := 0; i < nfield; i++ {
		//log.Printf("\tfield: %s\n", vin.Type().Field(i).Name)
		fin := vin.Field(i)
		foutty, ok := vout.Type().FieldByName(vin.Type().Field(i).Name)
		if !ok {
			//log.Printf("field %s not found on TC struct, skipping",
			//	vin.Type().Field(i).Name)
			continue
		}
		fout := vout.FieldByName(foutty.Name)
		if fin, err = convertFromIseeField(fin, stripVendorTags); err != nil {
			return nil, fmt.Errorf("convertFromVendorTag: %s", err)
		}

		isInd := false
		outVal := fout
		if fout.Kind() == reflect.Ptr {
			isInd = true
			outVal = fout.Elem()
		}

		switch outVal.Kind() {
		case reflect.Slice:
			fin, err = convertFromIseeSlice(fin, stripVendorTags)
			if err != nil {
				log.Printf("convertFromIseeSlice: %s", err)
				continue
			}
			if fin.Len() == 0 || fin.IsNil() {
				continue
			}
			fallthrough
		default:
			if isInd {
				fout.Set(fin.Addr())
			} else {
				fout.Set(fin)
			}
		}
	}

	return out, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"errors"
	"fmt"
	"html/template"
	"os"
	"runtime"
)

const (
	lclCompileCommandline = `"{{.fpc}}" -T{{.platform}} -P{{.arch}} -MObjFPC -Scghi -CX -Cg -O4 -XX{{if eq .platform "darwin"}} -k-framework -kCocoa{{end}} -l -vewnhibq -Fi{{.objFileDir}}/lib/{{.arch}}-{{.platform}} -Fu{{.lazarusDir}}/components/printers/lib/{{.arch}}-{{.platform}}/{{.lclType}} -Fu{{.lazarusDir}}/components/lazcontrols/lib/{{.arch}}-{{.platform}}/{{.lclType}} -Fu{{.lazarusDir}}/components/cairocanvas/lib/{{.arch}}-{{.platform}}/{{.lclType}} -Fu{{.lazarusDir}}/lcl/units/{{.arch}}-{{.platform}}/{{.lclType}} -Fu{{.lazarusDir}}/components/datetimectrls/lib/{{.arch}}-{{.platform}}/{{.lclType}} -Fu{{.lazarusDir}}/lcl/units/{{.arch}}-{{.platform}} -Fu{{.lazarusDir}}/components/lazutils/lib/{{.arch}}-{{.platform}} -Fu{{.lazarusDir}}/packager/units/{{.arch}}-{{.platform}} -Fu{{.objFileDir}}/ -FU{{.objFileDir}}/lib/{{.arch}}-{{.platform}}/ -FE{{.binFileDir}}/ -o{{.binFileDir}}/liblcl.{{.ext}} -dLCL -dLCL{{.lclType}} -dUSED_BY_LAZLOGGER_DUMMY "{{.projectFileName}}"`
)

func buildLCL(arch, binFileDir string) error {

	lazarusDir, fpcExe := GetLazarusDir()
	projectFileName := GetLibProjectFile("/liblcl/lcl.lpr")

	// lcl.lpi 这里面要修改版本号之类的
	// ProductVersion="1.2.8.0"

	if !checkFileExists(lazarusDir) {
		return errors.New("未找到lazarus目录。")
	}
	if !checkFileExists(projectFileName) {
		return errors.New("liblcl工程文件未找到。")
	}
	if arch != "i386" && arch != "x86_64" {
		return errors.New("arch错误，只能为i386或者x86_64")
	}

	lazarusDir = fixDirName(lazarusDir)
	objFileDir := fixDirName(GetObjFileDir("liblcl"))
	binFileDir = fixDirName(binFileDir)

	fmt.Println("lazarusDir:", lazarusDir)
	fmt.Println("projectFileName: ", projectFileName)
	fmt.Println("objFileDir: ", objFileDir)
	fmt.Println("binFileDir: ", binFileDir)

	extName := ""
	lclType := ""
	platform := ""
	switch runtime.GOOS {
	case "linux":
		extName = "so"
		lclType = "gtk2"
		platform = "linux"

	case "darwin":
		extName = "dylib"
		lclType = "cocoa"
		//if arch == "i386" {
		//	lclType = "carbon"
		//}
		platform = "darwin"

	default:
		extName = "dll"
		lclType = "win32"
		if arch == "i386" {
			platform = "win32"
		} else {
			platform = "win64"
		}
	}

	var buf bytes.Buffer
	tmp := template.New("lcl")
	tmp.Parse(lclCompileCommandline)
	tmp.Execute(&buf, map[string]interface{}{
		"fpc":             fpcExe,
		"arch":            arch,     // i386 x86_64
		"platform":        platform, // win32 win64 darwin linux
		"lazarusDir":      lazarusDir,
		"lclType":         lclType, // win32 cocoa carbon gtk2 gtk3
		"ext":             extName, // dll so dylib
		"projectFileName": projectFileName,
		"objFileDir":      objFileDir, // 编译时中间文件目录
		"binFileDir":      binFileDir,
	})

	// liblcl二进制输出目录不存在，则创建
	if !checkFileExists(binFileDir) {
		if err := os.MkdirAll(binFileDir, 0775); err != nil {
			return err
		}
	}

	// 中间文件输出
	libOutDir := objFileDir + "/lib/" + arch + "-" + platform
	if !checkFileExists(libOutDir) {
		if err := os.MkdirAll(libOutDir, 0775); err != nil {
			return err
		}
	}

	fmt.Println(buf.String())
	return execCmd(objFileDir, buf.String())
}
<commit_msg>Fix project name.<commit_after>package main

import (
	"bytes"
	"errors"
	"fmt"
	"html/template"
	"os"
	"runtime"
)

const (
	lclCompileCommandline = `"{{.fpc}}" -T{{.platform}} -P{{.arch}} -MObjFPC -Scghi -CX -Cg -O4 -XX{{if eq .platform "darwin"}} -k-framework -kCocoa{{end}} -l -vewnhibq -Fi{{.objFileDir}}/lib/{{.arch}}-{{.platform}} -Fu{{.lazarusDir}}/components/printers/lib/{{.arch}}-{{.platform}}/{{.lclType}} -Fu{{.lazarusDir}}/components/lazcontrols/lib/{{.arch}}-{{.platform}}/{{.lclType}} -Fu{{.lazarusDir}}/components/cairocanvas/lib/{{.arch}}-{{.platform}}/{{.lclType}} -Fu{{.lazarusDir}}/lcl/units/{{.arch}}-{{.platform}}/{{.lclType}} -Fu{{.lazarusDir}}/components/datetimectrls/lib/{{.arch}}-{{.platform}}/{{.lclType}} -Fu{{.lazarusDir}}/lcl/units/{{.arch}}-{{.platform}} -Fu{{.lazarusDir}}/components/lazutils/lib/{{.arch}}-{{.platform}} -Fu{{.lazarusDir}}/packager/units/{{.arch}}-{{.platform}} -Fu{{.objFileDir}}/ -FU{{.objFileDir}}/lib/{{.arch}}-{{.platform}}/ -FE{{.binFileDir}}/ -o{{.binFileDir}}/liblcl.{{.ext}} -dLCL -dLCL{{.lclType}} -dUSED_BY_LAZLOGGER_DUMMY "{{.projectFileName}}"`
)

func buildLCL(arch, binFileDir string) error {

	lazarusDir, fpcExe := GetLazarusDir()
	projectFileName := GetLibProjectFile("/liblcl/liblcl.lpr")

	// lcl.lpi 这里面要修改版本号之类的
	// ProductVersion="1.2.8.0"

	if !checkFileExists(lazarusDir) {
		return errors.New("未找到lazarus目录。")
	}
	if !checkFileExists(projectFileName) {
		return errors.New("liblcl工程文件未找到。")
	}
	if arch != "i386" && arch != "x86_64" {
		return errors.New("arch错误，只能为i386或者x86_64")
	}

	lazarusDir = fixDirName(lazarusDir)
	objFileDir := fixDirName(GetObjFileDir("liblcl"))
	binFileDir = fixDirName(binFileDir)

	fmt.Println("lazarusDir:", lazarusDir)
	fmt.Println("projectFileName: ", projectFileName)
	fmt.Println("objFileDir: ", objFileDir)
	fmt.Println("binFileDir: ", binFileDir)

	extName := ""
	lclType := ""
	platform := ""
	switch runtime.GOOS {
	case "linux":
		extName = "so"
		lclType = "gtk2"
		platform = "linux"

	case "darwin":
		extName = "dylib"
		lclType = "cocoa"
		//if arch == "i386" {
		//	lclType = "carbon"
		//}
		platform = "darwin"

	default:
		extName = "dll"
		lclType = "win32"
		if arch == "i386" {
			platform = "win32"
		} else {
			platform = "win64"
		}
	}

	var buf bytes.Buffer
	tmp := template.New("lcl")
	tmp.Parse(lclCompileCommandline)
	tmp.Execute(&buf, map[string]interface{}{
		"fpc":             fpcExe,
		"arch":            arch,     // i386 x86_64
		"platform":        platform, // win32 win64 darwin linux
		"lazarusDir":      lazarusDir,
		"lclType":         lclType, // win32 cocoa carbon gtk2 gtk3
		"ext":             extName, // dll so dylib
		"projectFileName": projectFileName,
		"objFileDir":      objFileDir, // 编译时中间文件目录
		"binFileDir":      binFileDir,
	})

	// liblcl二进制输出目录不存在，则创建
	if !checkFileExists(binFileDir) {
		if err := os.MkdirAll(binFileDir, 0775); err != nil {
			return err
		}
	}

	// 中间文件输出
	libOutDir := objFileDir + "/lib/" + arch + "-" + platform
	if !checkFileExists(libOutDir) {
		if err := os.MkdirAll(libOutDir, 0775); err != nil {
			return err
		}
	}

	fmt.Println(buf.String())
	return execCmd(objFileDir, buf.String())
}
<|endoftext|>


<commit_before>package syncmap

import (
	"errors"
	. "github.com/yanjinzh6/flowkey/tools"
	"sync"
)

type storage struct {
	m       SyncMap
	name    string
	mapType int
}

type Storage interface {
	MapType() (t int)
}

type storageManage struct {
	mainMap Storage
	readMap []Storage
}

type StorageManage interface {
}

var sManage StorageManage
var lock *sync.Mutex

func NewStorage(name string, mapType int) Storage {
	return &storage{
		m:       NewSyncMapEnt(),
		name:    name,
		mapType: mapType,
	}
}

func NewStorageManage(name string, mapType int) StorageManage {
	lock.Lock()
	if sManage == nil {
		sManage = storageManage{
			mainMap: NewStorage(name, mapType),
			readMap: make([]Storage),
		}
	}
	lock.Unlock()
	return &sManage
}
<commit_msg>storageManage.go add a storage interface.<commit_after>package syncmap

import (
	"errors"
	. "github.com/yanjinzh6/flowkey/tools"
	"sync"
)

var (
	RepeatNameError     = errors.New("add repeat name")
	StorageNotFindError = errors.New("can not find the name of storage list")
)

type storage struct {
	M       SyncMap
	Name    string
	MapType int
	Size    int
	Key     string
	Rule    string
}

type storageInt interface {
	ReSize()
	Add(key, value interface{})
	Del(key interface{})
}

type storageManage struct {
	mainMap storageInt
	readMap []storageInt
}

type StorageManage interface {
	Get(key interface{}) (val interface{}, err error)
	Put(key, value interface{}, d time.Duration) (val interface{}, err error)
	PutSimple(key, value interface{}) (val interface{}, err error)
	PutNormal(key, value interface{}) (val interface{}, err error)
	PutIfAbsent(key, value interface{}, d time.Duration) (b bool, err error)
	PutAll(child map[interface{}]interface{}, d time.Duration) (err error)
	Remove(key interface{}) (val interface{}, err error)
	RemoveEntry(key, value interface{}) (b bool, err error)
	Update(key, value interface{}) (b bool, err error)
	IsEmpty() (b bool)
	Clear() (err error)
	ClearUp() (err error)
	Size() (size int)
	SyncM(key interface{}) (err error)
	AddStorage(ns storage) (err error)
	DelStorage(name string) (err error)
}

var sManage StorageManage
var lock *sync.Mutex

func NewStorage(m SyncMap, name string, mapType, size int, key, rule string) storageInt {
	return &storage{
		M:       m,
		Name:    name,
		MapType: mapType,
		Size:    size,
		Key:     key,
		Rule:    rule,
	}
}

func (s *storage) ReSize() {
	if s.M.Size() > s.Size {
		s.M.Clear()
	}
}

func (s *storage) Add(key, value interface{}) {
	s.M.Put(key, value, 0)
}

func (s *storage) Del(key interface{}) {
	s.M.Remove(key)
}

func NewStorageManage() StorageManage {
	lock.Lock()
	if sManage == nil {
		sManage = storageManage{
			mainMap: NewStorage(NewSyncMapEnt(), "mainMap", STORAGE_MAIN_MAP, 0, nil, nil),
			readMap: make([]Storage, 1),
		}
		ns := NewStorage(NewSyncMap(), "recentMap", STORAGE_RECENT_USER, STORAGE_DEFAULT_SIZE, nil, nil)
		sManage.AddStorage(ns)
	}
	lock.Unlock()
	return &sManage
}

func (s *storageManage) Get(key interface{}) (val interface{}, err error) {
	if s.readMap != nil && len(s.readMap) > 0 {
		for i, v := range s.readMap {
			val, err = v.M.Get(key)
			if val != nil {
				err = s.mainMap.M.Sync(key)
				break
			}
		}
	}
	if val == nil {
		val, err = s.mainMap.M.Get(key)
	}
}

func (s *storageManage) Put(key, value interface{}, d time.Duration) (val interface{}, err error) {
	return
}

func (s *storageManage) PutSimple(key, value interface{}) (val interface{}, err error) {
	return
}

func (s *storageManage) PutNormal(key, value interface{}) (val interface{}, err error) {
	return
}

func (s *storageManage) PutIfAbsent(key, value interface{}, d time.Duration) (b bool, err error) {
	return
}

func (s *storageManage) PutAll(child map[interface{}]interface{}, d time.Duration) (err error) {
	return
}

func (s *storageManage) Remove(key interface{}) (val interface{}, err error) {
	return
}

func (s *storageManage) RemoveEntry(key, value interface{}) (b bool, err error) {
	return
}

func (s *storageManage) Update(key, value interface{}) (b bool, err error) {
	return
}

func (s *storageManage) IsEmpty() (b bool) {
	return
}

func (s *storageManage) Clear() (err error) {
	return
}

func (s *storageManage) ClearUp() (err error) {
	return
}

func (s *storageManage) Size() (size int) {
	return
}

func (s *storageManage) SyncM(key interface{}) (err error) {
	return
}

func (s *storageManage) AddStorage(ns storage) (err error) {
	lock.Lock()
	s.readMap = append(s.readMap, ns)
	lock.Unlock()
	return
}

func (s *storageManage) DelStorage(name string) (err error) {
	return
}
<|endoftext|>


<commit_before>package delaunay

import (
	"fmt"
	"math"
	"sort"
)

type Triangulation struct {
	Points    []Point
	Triangles []int
	HalfEdges []int
}

func Triangulate(points []Point) (*Triangulation, error) {
	t := newTriangulator(points)
	if err := t.triangulate(); err != nil {
		return nil, err
	}
	return &Triangulation{points, t.triangles, t.halfedges}, nil
}

type triangulator struct {
	points       []Point
	distances    []float64
	ids          []int
	center       Point
	hash         []*node
	triangles    []int
	halfedges    []int
	trianglesLen int
}

func newTriangulator(points []Point) *triangulator {
	return &triangulator{points: points}
}

// sorting a triangulator sorts the `ids` such that the referenced points
// are in order by their distance to `center`
func (a *triangulator) Len() int {
	return len(a.points)
}

func (a *triangulator) Swap(i, j int) {
	a.ids[i], a.ids[j] = a.ids[j], a.ids[i]
}

func (a *triangulator) Less(i, j int) bool {
	return a.distances[a.ids[i]] < a.distances[a.ids[j]]
}

func (tri *triangulator) triangulate() error {
	points := tri.points

	n := len(points)
	if n == 0 {
		return nil
	}

	tri.ids = make([]int, n)

	// compute bounds
	x0 := points[0].X
	y0 := points[0].Y
	x1 := points[0].X
	y1 := points[0].Y
	for i, p := range points {
		if p.X < x0 {
			x0 = p.X
		}
		if p.X > x1 {
			x1 = p.X
		}
		if p.Y < y0 {
			y0 = p.Y
		}
		if p.Y > y1 {
			y1 = p.Y
		}
		tri.ids[i] = i
	}

	var i0, i1, i2 int

	// pick a seed point close to midpoint
	m := Point{(x0 + x1) / 2, (y0 + y1) / 2}
	minDist := infinity
	for i, p := range points {
		d := p.distance(m)
		if d < minDist {
			i0 = i
			minDist = d
		}
	}

	// find point closest to seed point
	minDist = infinity
	for i, p := range points {
		if i == i0 {
			continue
		}
		d := p.distance(points[i0])
		if d > 0 && d < minDist {
			i1 = i
			minDist = d
		}
	}

	// find the third point which forms the smallest circumcircle
	minRadius := infinity
	for i, p := range points {
		if i == i0 || i == i1 {
			continue
		}
		r := circumradius(points[i0], points[i1], p)
		if r < minRadius {
			i2 = i
			minRadius = r
		}
	}
	if minRadius == infinity {
		return fmt.Errorf("No Delaunay triangulation exists for this input.")
	}

	// swap the order of the seed points for counter-clockwise orientation
	if area(points[i0], points[i1], points[i2]) < 0 {
		i1, i2 = i2, i1
	}

	tri.center = circumcenter(points[i0], points[i1], points[i2])

	// sort the points by distance from the seed triangle circumcenter
	tri.distances = make([]float64, n)
	for i, p := range points {
		tri.distances[i] = p.distance(tri.center)
	}
	sort.Sort(tri)

	// initialize a hash table for storing edges of the advancing convex hull
	hashSize := int(math.Ceil(math.Sqrt(float64(n))))
	tri.hash = make([]*node, hashSize)

	// initialize a circular doubly-linked list that will hold an advancing convex hull
	e := newNode(points[i0], i0, nil)
	e.t = 0
	tri.hashEdge(e)

	e = newNode(points[i1], i1, e)
	e.t = 1
	tri.hashEdge(e)

	e = newNode(points[i2], i2, e)
	e.t = 2
	tri.hashEdge(e)

	maxTriangles := 2*n - 5
	tri.trianglesLen = 0
	tri.triangles = make([]int, maxTriangles*3)
	tri.halfedges = make([]int, maxTriangles*3)

	tri.addTriangle(i0, i1, i2, -1, -1, -1)

	pp := Point{infinity, infinity}
	for k := 0; k < n; k++ {
		i := tri.ids[k]
		p := points[i]

		// skip duplicate points
		if p == pp {
			continue
		}
		pp = p

		// skip seed triangle points
		if i == i0 || i == i1 || i == i2 {
			continue
		}

		// find a visible edge on the convex hull using edge hash
		var start *node
		key := tri.hashKey(p)
		for j := 0; j < len(tri.hash); j++ {
			start = tri.hash[key]
			if start != nil && !start.removed {
				break
			}
			key++
			if key >= len(tri.hash) {
				key = 0
			}
		}

		e := start
		for area(p, e.p, e.next.p) >= 0 {
			e = e.next
			if e == start {
				return fmt.Errorf("Something is wrong with the input points.")
			}
		}

		walkBack := e == start

		// add the first triangle from the point
		t := tri.addTriangle(e.i, i, e.next.i, -1, -1, e.t)
		e.t = t // keep track of boundary triangles on the hull
		e = newNode(points[i], i, e)

		// recursively flip triangles from the point until they satisfy the Delaunay condition
		e.t = tri.legalize(t + 2)
		if e.prev.prev.t == tri.halfedges[t+1] {
			e.prev.prev.t = t + 2
		}

		// walk forward through the hull, adding more triangles and flipping recursively
		q := e.next
		for area(p, q.p, q.next.p) < 0 {
			t = tri.addTriangle(q.i, i, q.next.i, q.prev.t, -1, q.t)
			q.prev.t = tri.legalize(t + 2)
			q.remove()
			q = q.next
		}

		if walkBack {
			// walk backward from the other side, adding more triangles and flipping
			q := e.prev
			for area(p, q.prev.p, q.p) < 0 {
				t = tri.addTriangle(q.prev.i, i, q.i, -1, q.t, q.prev.t)
				tri.legalize(t + 2)
				q.prev.t = t
				q.remove()
				q = q.prev
			}
		}

		// save the two new edges in the hash table
		tri.hashEdge(e)
		tri.hashEdge(e.prev)
	}

	tri.triangles = tri.triangles[:tri.trianglesLen]
	tri.halfedges = tri.halfedges[:tri.trianglesLen]

	return nil
}

func (t *triangulator) hashKey(point Point) int {
	dx := point.X - t.center.X
	dy := point.Y - t.center.Y
	// use pseudo-angle: a measure that monotonically increases
	// with real angle, but doesn't require expensive trigonometry
	p := 1 - dx/(math.Abs(dx)+math.Abs(dy))
	if dy < 0 {
		p = -p
	}
	return int(math.Floor((2 + p) / 4 * float64(len(t.hash))))
}

func (t *triangulator) hashEdge(e *node) {
	t.hash[t.hashKey(e.p)] = e
}

func (t *triangulator) addTriangle(i0, i1, i2, a, b, c int) int {
	i := t.trianglesLen
	t.triangles[i] = i0
	t.triangles[i+1] = i1
	t.triangles[i+2] = i2
	t.link(i, a)
	t.link(i+1, b)
	t.link(i+2, c)
	t.trianglesLen += 3
	return i
}

func (t *triangulator) link(a, b int) {
	t.halfedges[a] = b
	if b >= 0 {
		t.halfedges[b] = a
	}
}

func (t *triangulator) legalize(a int) int {
	b := t.halfedges[a]

	a0 := a - a%3
	b0 := b - b%3

	al := a0 + (a+1)%3
	ar := a0 + (a+2)%3
	bl := b0 + (b+2)%3

	p0 := t.triangles[ar]
	pr := t.triangles[a]
	pl := t.triangles[al]
	p1 := t.triangles[bl]

	illegal := inCircle(t.points[p0], t.points[pr], t.points[pl], t.points[p1])

	if illegal {
		t.triangles[a] = p1
		t.triangles[b] = p0

		t.link(a, t.halfedges[bl])
		t.link(b, t.halfedges[ar])
		t.link(ar, bl)

		br := b0 + (b+1)%3

		t.legalize(a)
		return t.legalize(br)
	}

	return ar
}
<commit_msg>HalfEdges => Halfedges<commit_after>package delaunay

import (
	"fmt"
	"math"
	"sort"
)

type Triangulation struct {
	Points    []Point
	Triangles []int
	Halfedges []int
}

func Triangulate(points []Point) (*Triangulation, error) {
	t := newTriangulator(points)
	if err := t.triangulate(); err != nil {
		return nil, err
	}
	return &Triangulation{points, t.triangles, t.halfedges}, nil
}

type triangulator struct {
	points       []Point
	distances    []float64
	ids          []int
	center       Point
	hash         []*node
	triangles    []int
	halfedges    []int
	trianglesLen int
}

func newTriangulator(points []Point) *triangulator {
	return &triangulator{points: points}
}

// sorting a triangulator sorts the `ids` such that the referenced points
// are in order by their distance to `center`
func (a *triangulator) Len() int {
	return len(a.points)
}

func (a *triangulator) Swap(i, j int) {
	a.ids[i], a.ids[j] = a.ids[j], a.ids[i]
}

func (a *triangulator) Less(i, j int) bool {
	return a.distances[a.ids[i]] < a.distances[a.ids[j]]
}

func (tri *triangulator) triangulate() error {
	points := tri.points

	n := len(points)
	if n == 0 {
		return nil
	}

	tri.ids = make([]int, n)

	// compute bounds
	x0 := points[0].X
	y0 := points[0].Y
	x1 := points[0].X
	y1 := points[0].Y
	for i, p := range points {
		if p.X < x0 {
			x0 = p.X
		}
		if p.X > x1 {
			x1 = p.X
		}
		if p.Y < y0 {
			y0 = p.Y
		}
		if p.Y > y1 {
			y1 = p.Y
		}
		tri.ids[i] = i
	}

	var i0, i1, i2 int

	// pick a seed point close to midpoint
	m := Point{(x0 + x1) / 2, (y0 + y1) / 2}
	minDist := infinity
	for i, p := range points {
		d := p.distance(m)
		if d < minDist {
			i0 = i
			minDist = d
		}
	}

	// find point closest to seed point
	minDist = infinity
	for i, p := range points {
		if i == i0 {
			continue
		}
		d := p.distance(points[i0])
		if d > 0 && d < minDist {
			i1 = i
			minDist = d
		}
	}

	// find the third point which forms the smallest circumcircle
	minRadius := infinity
	for i, p := range points {
		if i == i0 || i == i1 {
			continue
		}
		r := circumradius(points[i0], points[i1], p)
		if r < minRadius {
			i2 = i
			minRadius = r
		}
	}
	if minRadius == infinity {
		return fmt.Errorf("No Delaunay triangulation exists for this input.")
	}

	// swap the order of the seed points for counter-clockwise orientation
	if area(points[i0], points[i1], points[i2]) < 0 {
		i1, i2 = i2, i1
	}

	tri.center = circumcenter(points[i0], points[i1], points[i2])

	// sort the points by distance from the seed triangle circumcenter
	tri.distances = make([]float64, n)
	for i, p := range points {
		tri.distances[i] = p.distance(tri.center)
	}
	sort.Sort(tri)

	// initialize a hash table for storing edges of the advancing convex hull
	hashSize := int(math.Ceil(math.Sqrt(float64(n))))
	tri.hash = make([]*node, hashSize)

	// initialize a circular doubly-linked list that will hold an advancing convex hull
	e := newNode(points[i0], i0, nil)
	e.t = 0
	tri.hashEdge(e)

	e = newNode(points[i1], i1, e)
	e.t = 1
	tri.hashEdge(e)

	e = newNode(points[i2], i2, e)
	e.t = 2
	tri.hashEdge(e)

	maxTriangles := 2*n - 5
	tri.trianglesLen = 0
	tri.triangles = make([]int, maxTriangles*3)
	tri.halfedges = make([]int, maxTriangles*3)

	tri.addTriangle(i0, i1, i2, -1, -1, -1)

	pp := Point{infinity, infinity}
	for k := 0; k < n; k++ {
		i := tri.ids[k]
		p := points[i]

		// skip duplicate points
		if p == pp {
			continue
		}
		pp = p

		// skip seed triangle points
		if i == i0 || i == i1 || i == i2 {
			continue
		}

		// find a visible edge on the convex hull using edge hash
		var start *node
		key := tri.hashKey(p)
		for j := 0; j < len(tri.hash); j++ {
			start = tri.hash[key]
			if start != nil && !start.removed {
				break
			}
			key++
			if key >= len(tri.hash) {
				key = 0
			}
		}

		e := start
		for area(p, e.p, e.next.p) >= 0 {
			e = e.next
			if e == start {
				return fmt.Errorf("Something is wrong with the input points.")
			}
		}

		walkBack := e == start

		// add the first triangle from the point
		t := tri.addTriangle(e.i, i, e.next.i, -1, -1, e.t)
		e.t = t // keep track of boundary triangles on the hull
		e = newNode(points[i], i, e)

		// recursively flip triangles from the point until they satisfy the Delaunay condition
		e.t = tri.legalize(t + 2)
		if e.prev.prev.t == tri.halfedges[t+1] {
			e.prev.prev.t = t + 2
		}

		// walk forward through the hull, adding more triangles and flipping recursively
		q := e.next
		for area(p, q.p, q.next.p) < 0 {
			t = tri.addTriangle(q.i, i, q.next.i, q.prev.t, -1, q.t)
			q.prev.t = tri.legalize(t + 2)
			q.remove()
			q = q.next
		}

		if walkBack {
			// walk backward from the other side, adding more triangles and flipping
			q := e.prev
			for area(p, q.prev.p, q.p) < 0 {
				t = tri.addTriangle(q.prev.i, i, q.i, -1, q.t, q.prev.t)
				tri.legalize(t + 2)
				q.prev.t = t
				q.remove()
				q = q.prev
			}
		}

		// save the two new edges in the hash table
		tri.hashEdge(e)
		tri.hashEdge(e.prev)
	}

	tri.triangles = tri.triangles[:tri.trianglesLen]
	tri.halfedges = tri.halfedges[:tri.trianglesLen]

	return nil
}

func (t *triangulator) hashKey(point Point) int {
	dx := point.X - t.center.X
	dy := point.Y - t.center.Y
	// use pseudo-angle: a measure that monotonically increases
	// with real angle, but doesn't require expensive trigonometry
	p := 1 - dx/(math.Abs(dx)+math.Abs(dy))
	if dy < 0 {
		p = -p
	}
	return int(math.Floor((2 + p) / 4 * float64(len(t.hash))))
}

func (t *triangulator) hashEdge(e *node) {
	t.hash[t.hashKey(e.p)] = e
}

func (t *triangulator) addTriangle(i0, i1, i2, a, b, c int) int {
	i := t.trianglesLen
	t.triangles[i] = i0
	t.triangles[i+1] = i1
	t.triangles[i+2] = i2
	t.link(i, a)
	t.link(i+1, b)
	t.link(i+2, c)
	t.trianglesLen += 3
	return i
}

func (t *triangulator) link(a, b int) {
	t.halfedges[a] = b
	if b >= 0 {
		t.halfedges[b] = a
	}
}

func (t *triangulator) legalize(a int) int {
	b := t.halfedges[a]

	a0 := a - a%3
	b0 := b - b%3

	al := a0 + (a+1)%3
	ar := a0 + (a+2)%3
	bl := b0 + (b+2)%3

	p0 := t.triangles[ar]
	pr := t.triangles[a]
	pl := t.triangles[al]
	p1 := t.triangles[bl]

	illegal := inCircle(t.points[p0], t.points[pr], t.points[pl], t.points[p1])

	if illegal {
		t.triangles[a] = p1
		t.triangles[b] = p0

		t.link(a, t.halfedges[bl])
		t.link(b, t.halfedges[ar])
		t.link(ar, bl)

		br := b0 + (b+1)%3

		t.legalize(a)
		return t.legalize(br)
	}

	return ar
}
<|endoftext|>


<commit_before>package handler

import (
	"encoding/json"
	"errors"
	"fmt"
	"koding/db/models"
	"koding/db/mongodb"
	"koding/db/mongodb/modelhelper"
	"koding/kontrol/kontrolhelper"
	"koding/tools/config"
	"koding/tools/logger"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/streadway/amqp"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

const HEARTBEAT_INTERVAL = time.Second * 10
const HEARTBEAT_DELAY = time.Second * 5

type WorkerResponse struct {
	Name    string `json:"name"`
	Uuid    string `json:"uuid"`
	Command string `json:"command"`
	Log     string `json:"log"`
}

func NewWorkerResponse(name, uuid, command, log string) *WorkerResponse {
	return &WorkerResponse{
		Name:    name,
		Uuid:    uuid,
		Command: command,
		Log:     log,
	}
}

type IncomingMessage struct {
	Worker  *models.Worker
	Monitor *models.Monitor
}

var producer *kontrolhelper.Producer
var kontrolDB *mongodb.MongoDB
var log = logger.New("kontroldaemon")
var proxyMu sync.Mutex

const (
	WorkersCollection = "jKontrolWorkers"
	WorkersDB         = "kontrol"
)

func Startup(conf *config.Config) {
	var err error
	producer, err = kontrolhelper.CreateProducer(conf, "worker")
	if err != nil {
		log.Error(err.Error())
	}

	err = producer.Channel.ExchangeDeclare("clientExchange", "fanout", true, false, false, false, nil)
	if err != nil {
		log.Error("clientExchange exchange.declare: %s", err)
	}

	kontrolDB = mongodb.NewMongoDB(conf.MongoKontrol)
	modelhelper.KontrolWorkersInit(conf.MongoKontrol)

	go heartBeatChecker()
	go deploymentCleaner()

	log.Info("handler is initialized")
}

// WorkerMessage is handling messages coming from the workerExchange
func WorkerMessage(data []byte) {
	var msg IncomingMessage
	err := json.Unmarshal(data, &msg)
	if err != nil {
		log.Error("bad json incoming msg: %s err: %s", string(data), err)
	}

	if msg.Monitor != nil {
		err := handleMonitorData(msg.Monitor)
		if err != nil {
			log.Error(err.Error())
		}
		return
	}

	if msg.Worker != nil {
		err = handleCommand(msg.Worker.Message.Command, *msg.Worker)
		if err != nil {
			log.Error(err.Error())
		}
		return
	}

	log.Warning("incoming message is in wrong format %v", msg)
}

func handleMonitorData(data *models.Monitor) error {
	worker, err := modelhelper.GetWorker(data.Uuid)
	if err != nil {
		return fmt.Errorf("monitor data error '%s'", err)
	}

	worker.Monitor.Mem = *data.Mem
	worker.Monitor.Uptime = data.Uptime
	modelhelper.UpdateWorker(worker)
	return nil
}

// handleCommand is used to handle messages coming from workers.
func handleCommand(command string, worker models.Worker) error {
	if worker.Uuid == "" {
		fmt.Errorf("worker %s does have an empty uuid", worker.Name)
	}

	switch command {
	case "add", "addWithProxy":
		// This is a large and complex process, handle it separately.
		// "res" will be send to the worker, it contains the permission result
		res, err := handleAddCommand(worker)
		if err != nil {
			return err
		}
		go deliver(res)

		// rest is proxy related
		if command != "addWithProxy" {
			return nil
		}

		if worker.Port == 0 { // zero port is useless for proxy
			return fmt.Errorf("register to kontrol proxy not possible. port number is '0' for %s", worker.Name)
		}

		proxyName := worker.Name
		if worker.ProxyName != "" {
			proxyName = worker.ProxyName
		}

		port := strconv.Itoa(worker.Port)
		key := strconv.Itoa(worker.Version)

		proxyMu.Lock()
		err = modelhelper.UpsertKey(
			"koding",  // username
			proxyName, // servicename
			key,       // version (build number)
			worker.Hostname+":"+port, // host
			worker.Environment,       // hostdata, pass environment
			true,                     // enable keyData to be used with proxy immediately
		)
		proxyMu.Unlock()
		if err != nil {
			return fmt.Errorf("register to kontrol proxy not possible: %s", err.Error())
		}
	case "ack":
		query := func(c *mgo.Collection) error {
			return c.Update(
				bson.M{"uuid": worker.Uuid},
				bson.M{"$set": bson.M{
					"timestamp": time.Now().Add(HEARTBEAT_INTERVAL),
					"status":    models.Started,
				}},
			)
		}

		err := kontrolDB.RunOnDatabase(WorkersDB, WorkersCollection, query)
		if err == mgo.ErrNotFound {
			worker.Status = models.Started
			worker.ObjectId = bson.NewObjectId()
			worker.Timestamp = time.Now().Add(HEARTBEAT_INTERVAL)
			workerLog("NOT REGISTERED, ADDING AGAIN", worker)
			return modelhelper.UpsertWorker(worker)
		}

		// workerLog("PING", worker)
		return err
	case "update":
		//  Update kontrold worker information with our newly created pid.
		query := func(c *mgo.Collection) error {
			return c.Update(
				bson.M{"uuid": worker.Uuid},
				bson.M{"$set": bson.M{
					"timestamp": time.Now().Add(HEARTBEAT_INTERVAL),
					"status":    models.Started,
					"pid":       worker.Pid,
					"version":   worker.Version,
				}},
			)
		}

		workerLog("UPDATE", worker)

		return kontrolDB.RunOnDatabase(WorkersDB, WorkersCollection, query)
	default:
		return fmt.Errorf(" command not recognized: %s", command)
	}

	return nil
}

func workerLog(msg string, worker models.Worker) string {
	msgLog := fmt.Sprintf("%7s ▶ %s  [version: %d hostname: %s uuid: %s pid: %d]",
		msg,
		worker.Name,
		worker.Version,
		worker.Hostname,
		worker.Uuid,
		worker.Pid,
	)

	log.Info(msgLog)
	return msgLog
}

// handleAddCommand is a router that does different things according to the
// workers' start mode. Each mode is handled via a seperate function.
func handleAddCommand(worker models.Worker) (*WorkerResponse, error) {
	switch worker.Message.Option {
	case "one", "version":
		return handleExclusiveOption(worker)
	case "many":
		return handleManyOption(worker)
	}

	return nil, errors.New("no option specified for add action. aborting add handler...")
}

// handleExclusiveOption starts workers whose are in one and version mode. These
// modes are special where the workers are allowed to be run exclusive, which
// then deny any other workers to be runned.
func handleExclusiveOption(worker models.Worker) (*WorkerResponse, error) {
	option := worker.Message.Option
	query := bson.M{}
	reason := ""

	// one means that only one single instance of the worker can work. For
	// example if we start an emailWorker with the mode "one", another
	// emailWorker don't get the permission to run.
	if option == "one" {
		query = bson.M{
			"name": worker.Name,
		}
		reason = fmt.Sprintf("workers with the same name running: ")
	}

	// version is like one, but it's allow only workers of the same name
	// and version. For example if an authWorker of version 13 starts with
	// the mode "version", than only authWorkers of version 13 can start,
	// any other authworker different than 13 (say, 10, 14, ...) don't get
	// the permission to run.
	if option == "version" {
		query = bson.M{
			"name": bson.RegEx{Pattern: "^" + normalizeName(worker.Name),
				Options: "i"},
			"version": bson.M{"$ne": worker.Version},
		}
		reason = fmt.Sprintf("workers with different name and versions running: ")
	}

	// If the query above for 'one' and 'version' doesn't match anything,
	// then add our new worker. Apply() is atomic and uses findAndModify.
	// Adding it causes no err, therefore the worker get 'start' message.
	// However if the query matches, then the 'upsert' will fail (means
	// that there is some workers that are running).
	worker.ObjectId = bson.NewObjectId()
	worker.Status = models.Started
	worker.Timestamp = time.Now().Add(HEARTBEAT_INTERVAL)
	change := mgo.Change{
		Update: worker,
		Upsert: true,
	}

	resultOfApply := new(models.Worker)

	// this is the worker that matches the query, that means a worker
	// cannot be added in mode one or version because of this worker that
	// is still alive.
	aliveWorker := new(models.Worker)

	err := kontrolDB.RunOnDatabase(WorkersDB, WorkersCollection, func(c *mgo.Collection) error {
		// worst fucking syntax ever I saw in my life that is doing
		// fucking gazillion things with one fucking method called fucking
		// apply. fuck you mgo
		_, err := c.Find(query).Apply(change, resultOfApply)

		// this is needed because of the fucking syntax above that doesn't
		// return the old document even when it MATCHES the fucking query!!!.
		// again fuck you mgo
		c.Find(query).One(aliveWorker)
		return err
	})

	if err == nil {
		startLog := workerLog("START", worker)
		return NewWorkerResponse(worker.Name, worker.Uuid, "start", startLog), nil
	}

	reasonLog := reason + fmt.Sprintf("version: %d (pid: %d) at %s", aliveWorker.Version, aliveWorker.Pid, aliveWorker.Hostname)
	return NewWorkerResponse(worker.Name, worker.Uuid, "noPermission", reasonLog), nil
}

// handleManyOption just starts the worker. That means a worker can be started as
// many times as we wished with this option.
func handleManyOption(worker models.Worker) (*WorkerResponse, error) {
	startLog := workerLog("START", worker)
	worker.ObjectId = bson.NewObjectId()
	worker.Status = models.Started
	worker.Timestamp = time.Now().Add(HEARTBEAT_INTERVAL)
	modelhelper.UpsertWorker(worker)

	return NewWorkerResponse(worker.Name, worker.Uuid, "start", startLog), nil
}

func deliver(res *WorkerResponse) {
	data, err := json.Marshal(res)
	if err != nil {
		log.Error("could not marshall worker: %s", err)
	}

	msg := amqp.Publishing{
		Headers:         amqp.Table{},
		ContentType:     "text/plain",
		ContentEncoding: "",
		Body:            data,
		DeliveryMode:    1, // 1=non-persistent, 2=persistent
		Priority:        0, // 0-9
	}

	if res.Uuid == "" {
		log.Error("can't send to worker. appId is missing")
	}

	workerOut := "output.worker." + res.Uuid
	err = producer.Channel.Publish("workerExchange", workerOut, false, false, msg)
	if err != nil {
		log.Error("error while publishing message: %s", err)
	}
}

// convert foo-1, foo-*, etc to foo
func normalizeName(name string) string {
	if counts := strings.Count(name, "-"); counts > 0 {
		return strings.Split(name, "-")[0]
	}
	return name
}

// heartBeathChecker checks if a worker is alive or not. If it's alive it's
// just continues to the next one until it finds a worker that didn't get an
// hearbeat. If that worker didn't get three heartbeats in a series we are
// removing it from the DB.
func heartBeatChecker() {
	query := func(c *mgo.Collection) error {
		worker := models.Worker{}

		iter := c.Find(nil).Iter()
		for iter.Next(&worker) {
			if time.Now().Before(worker.Timestamp.Add(HEARTBEAT_DELAY)) {
				continue // this one is alive, pick up the next one
			}

			workerLog("DEAD", worker)
			modelhelper.DeleteWorker(worker.Uuid)
		}

		if err := iter.Close(); err != nil {
			return err
		}

		return nil
	}

	for {
		time.Sleep(time.Second * 2)
		kontrolDB.RunOnDatabase(WorkersDB, WorkersCollection, query)
	}
}

// Cleanup dead deployments at intervals. This goroutine will lookup at
// each information if a deployment has running workers. If workers for a
// certain deployment is not running anymore, then it will remove the
// deployment information .
func deploymentCleaner() {
	for {
		log.Info("cleaner started to remove unused deployments and dead workers")
		infos := modelhelper.GetClients()
		for _, info := range infos {
			var numberOfWorkers int

			version, _ := strconv.Atoi(info.BuildNumber)

			query := func(c *mgo.Collection) error {
				numberOfWorkers, _ = c.Find(bson.M{
					"version": version,
					"status":  int(models.Started)},
				).Count()

				return nil
			}

			kontrolDB.RunOnDatabase(WorkersDB, WorkersCollection, query)

			// remove deployment information only if there is no worker alive for that version
			if numberOfWorkers == 0 {
				log.Info("removing deployment info for build number %s", info.BuildNumber)
				err := modelhelper.DeleteClient(info.BuildNumber)
				if err != nil {
					log.Error(err.Error())
				}
			}
		}

		// check 12 hours later again
		time.Sleep(time.Hour * 12)
	}
}
<commit_msg>kontrolproxy: add lock for one and version.<commit_after>package handler

import (
	"encoding/json"
	"errors"
	"fmt"
	"koding/db/models"
	"koding/db/mongodb"
	"koding/db/mongodb/modelhelper"
	"koding/kontrol/kontrolhelper"
	"koding/tools/config"
	"koding/tools/logger"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/streadway/amqp"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

const HEARTBEAT_INTERVAL = time.Second * 10
const HEARTBEAT_DELAY = time.Second * 5

type WorkerResponse struct {
	Name    string `json:"name"`
	Uuid    string `json:"uuid"`
	Command string `json:"command"`
	Log     string `json:"log"`
}

func NewWorkerResponse(name, uuid, command, log string) *WorkerResponse {
	return &WorkerResponse{
		Name:    name,
		Uuid:    uuid,
		Command: command,
		Log:     log,
	}
}

type IncomingMessage struct {
	Worker  *models.Worker
	Monitor *models.Monitor
}

var producer *kontrolhelper.Producer
var kontrolDB *mongodb.MongoDB
var log = logger.New("kontroldaemon")
var proxyMu sync.Mutex
var handlerMu sync.Mutex

const (
	WorkersCollection = "jKontrolWorkers"
	WorkersDB         = "kontrol"
)

func Startup(conf *config.Config) {
	var err error
	producer, err = kontrolhelper.CreateProducer(conf, "worker")
	if err != nil {
		log.Error(err.Error())
	}

	err = producer.Channel.ExchangeDeclare("clientExchange", "fanout", true, false, false, false, nil)
	if err != nil {
		log.Error("clientExchange exchange.declare: %s", err)
	}

	kontrolDB = mongodb.NewMongoDB(conf.MongoKontrol)
	modelhelper.KontrolWorkersInit(conf.MongoKontrol)

	go heartBeatChecker()
	go deploymentCleaner()

	log.Info("handler is initialized")
}

// WorkerMessage is handling messages coming from the workerExchange
func WorkerMessage(data []byte) {
	var msg IncomingMessage
	err := json.Unmarshal(data, &msg)
	if err != nil {
		log.Error("bad json incoming msg: %s err: %s", string(data), err)
	}

	if msg.Monitor != nil {
		err := handleMonitorData(msg.Monitor)
		if err != nil {
			log.Error(err.Error())
		}
		return
	}

	if msg.Worker != nil {
		err = handleCommand(msg.Worker.Message.Command, *msg.Worker)
		if err != nil {
			log.Error(err.Error())
		}
		return
	}

	log.Warning("incoming message is in wrong format %v", msg)
}

func handleMonitorData(data *models.Monitor) error {
	worker, err := modelhelper.GetWorker(data.Uuid)
	if err != nil {
		return fmt.Errorf("monitor data error '%s'", err)
	}

	worker.Monitor.Mem = *data.Mem
	worker.Monitor.Uptime = data.Uptime
	modelhelper.UpdateWorker(worker)
	return nil
}

// handleCommand is used to handle messages coming from workers.
func handleCommand(command string, worker models.Worker) error {
	if worker.Uuid == "" {
		fmt.Errorf("worker %s does have an empty uuid", worker.Name)
	}

	switch command {
	case "add", "addWithProxy":
		// This is a large and complex process, handle it separately.
		// "res" will be send to the worker, it contains the permission result
		res, err := handleAddCommand(worker)
		if err != nil {
			return err
		}
		go deliver(res)

		// rest is proxy related
		if command != "addWithProxy" {
			return nil
		}

		if worker.Port == 0 { // zero port is useless for proxy
			return fmt.Errorf("register to kontrol proxy not possible. port number is '0' for %s", worker.Name)
		}

		proxyName := worker.Name
		if worker.ProxyName != "" {
			proxyName = worker.ProxyName
		}

		port := strconv.Itoa(worker.Port)
		key := strconv.Itoa(worker.Version)

		proxyMu.Lock()
		err = modelhelper.UpsertKey(
			"koding",  // username
			proxyName, // servicename
			key,       // version (build number)
			worker.Hostname+":"+port, // host
			worker.Environment,       // hostdata, pass environment
			true,                     // enable keyData to be used with proxy immediately
		)
		proxyMu.Unlock()
		if err != nil {
			return fmt.Errorf("register to kontrol proxy not possible: %s", err.Error())
		}
	case "ack":
		query := func(c *mgo.Collection) error {
			return c.Update(
				bson.M{"uuid": worker.Uuid},
				bson.M{"$set": bson.M{
					"timestamp": time.Now().Add(HEARTBEAT_INTERVAL),
					"status":    models.Started,
				}},
			)
		}

		err := kontrolDB.RunOnDatabase(WorkersDB, WorkersCollection, query)
		if err == mgo.ErrNotFound {
			worker.Status = models.Started
			worker.ObjectId = bson.NewObjectId()
			worker.Timestamp = time.Now().Add(HEARTBEAT_INTERVAL)
			workerLog("NOT REGISTERED, ADDING AGAIN", worker)
			return modelhelper.UpsertWorker(worker)
		}

		// workerLog("PING", worker)
		return err
	case "update":
		//  Update kontrold worker information with our newly created pid.
		query := func(c *mgo.Collection) error {
			return c.Update(
				bson.M{"uuid": worker.Uuid},
				bson.M{"$set": bson.M{
					"timestamp": time.Now().Add(HEARTBEAT_INTERVAL),
					"status":    models.Started,
					"pid":       worker.Pid,
					"version":   worker.Version,
				}},
			)
		}

		workerLog("UPDATE", worker)

		return kontrolDB.RunOnDatabase(WorkersDB, WorkersCollection, query)
	default:
		return fmt.Errorf(" command not recognized: %s", command)
	}

	return nil
}

func workerLog(msg string, worker models.Worker) string {
	msgLog := fmt.Sprintf("%7s ▶ %s  [version: %d hostname: %s uuid: %s pid: %d]",
		msg,
		worker.Name,
		worker.Version,
		worker.Hostname,
		worker.Uuid,
		worker.Pid,
	)

	log.Info(msgLog)
	return msgLog
}

// handleAddCommand is a router that does different things according to the
// workers' start mode. Each mode is handled via a seperate function.
func handleAddCommand(worker models.Worker) (*WorkerResponse, error) {
	switch worker.Message.Option {
	case "one", "version":
		return handleExclusiveOption(worker)
	case "many":
		return handleManyOption(worker)
	}

	return nil, errors.New("no option specified for add action. aborting add handler...")
}

// handleExclusiveOption starts workers whose are in one and version mode. These
// modes are special where the workers are allowed to be run exclusive, which
// then deny any other workers to be runned.
func handleExclusiveOption(worker models.Worker) (*WorkerResponse, error) {
	handlerMu.Lock()
	defer handlerMu.Unlock()

	option := worker.Message.Option
	query := bson.M{}
	reason := ""

	// one means that only one single instance of the worker can work. For
	// example if we start an emailWorker with the mode "one", another
	// emailWorker don't get the permission to run.
	if option == "one" {
		query = bson.M{
			"name": worker.Name,
		}
		reason = fmt.Sprintf("workers with the same name running: ")
	}

	// version is like one, but it's allow only workers of the same name
	// and version. For example if an authWorker of version 13 starts with
	// the mode "version", than only authWorkers of version 13 can start,
	// any other authworker different than 13 (say, 10, 14, ...) don't get
	// the permission to run.
	if option == "version" {
		query = bson.M{
			"name": bson.RegEx{Pattern: "^" + normalizeName(worker.Name),
				Options: "i"},
			"version": bson.M{"$ne": worker.Version},
		}
		reason = fmt.Sprintf("workers with different name and versions running: ")
	}

	// If the query above for 'one' and 'version' doesn't match anything,
	// then add our new worker. Apply() is atomic and uses findAndModify.
	// Adding it causes no err, therefore the worker get 'start' message.
	// However if the query matches, then the 'upsert' will fail (means
	// that there is some workers that are running).
	worker.ObjectId = bson.NewObjectId()
	worker.Status = models.Started
	worker.Timestamp = time.Now().Add(HEARTBEAT_INTERVAL)
	change := mgo.Change{
		Update: worker,
		Upsert: true,
	}

	resultOfApply := new(models.Worker)

	// this is the worker that matches the query, that means a worker
	// cannot be added in mode one or version because of this worker that
	// is still alive.
	aliveWorker := new(models.Worker)

	err := kontrolDB.RunOnDatabase(WorkersDB, WorkersCollection, func(c *mgo.Collection) error {
		// worst fucking syntax ever I saw in my life that is doing
		// fucking gazillion things with one fucking method called fucking
		// apply. fuck you mgo
		_, err := c.Find(query).Apply(change, resultOfApply)

		// this is needed because of the fucking syntax above that doesn't
		// return the old document even when it MATCHES the fucking query!!!.
		// again fuck you mgo
		c.Find(query).One(aliveWorker)
		return err
	})

	if err == nil {
		startLog := workerLog("START", worker)
		return NewWorkerResponse(worker.Name, worker.Uuid, "start", startLog), nil
	}

	reasonLog := reason + fmt.Sprintf("version: %d (pid: %d) at %s", aliveWorker.Version, aliveWorker.Pid, aliveWorker.Hostname)
	return NewWorkerResponse(worker.Name, worker.Uuid, "noPermission", reasonLog), nil
}

// handleManyOption just starts the worker. That means a worker can be started as
// many times as we wished with this option.
func handleManyOption(worker models.Worker) (*WorkerResponse, error) {
	startLog := workerLog("START", worker)
	worker.ObjectId = bson.NewObjectId()
	worker.Status = models.Started
	worker.Timestamp = time.Now().Add(HEARTBEAT_INTERVAL)
	modelhelper.UpsertWorker(worker)

	return NewWorkerResponse(worker.Name, worker.Uuid, "start", startLog), nil
}

func deliver(res *WorkerResponse) {
	data, err := json.Marshal(res)
	if err != nil {
		log.Error("could not marshall worker: %s", err)
	}

	msg := amqp.Publishing{
		Headers:         amqp.Table{},
		ContentType:     "text/plain",
		ContentEncoding: "",
		Body:            data,
		DeliveryMode:    1, // 1=non-persistent, 2=persistent
		Priority:        0, // 0-9
	}

	if res.Uuid == "" {
		log.Error("can't send to worker. appId is missing")
	}

	workerOut := "output.worker." + res.Uuid
	err = producer.Channel.Publish("workerExchange", workerOut, false, false, msg)
	if err != nil {
		log.Error("error while publishing message: %s", err)
	}
}

// convert foo-1, foo-*, etc to foo
func normalizeName(name string) string {
	if counts := strings.Count(name, "-"); counts > 0 {
		return strings.Split(name, "-")[0]
	}
	return name
}

// heartBeathChecker checks if a worker is alive or not. If it's alive it's
// just continues to the next one until it finds a worker that didn't get an
// hearbeat. If that worker didn't get three heartbeats in a series we are
// removing it from the DB.
func heartBeatChecker() {
	query := func(c *mgo.Collection) error {
		worker := models.Worker{}

		iter := c.Find(nil).Iter()
		for iter.Next(&worker) {
			if time.Now().Before(worker.Timestamp.Add(HEARTBEAT_DELAY)) {
				continue // this one is alive, pick up the next one
			}

			workerLog("DEAD", worker)
			modelhelper.DeleteWorker(worker.Uuid)
		}

		if err := iter.Close(); err != nil {
			return err
		}

		return nil
	}

	for {
		time.Sleep(time.Second * 2)
		kontrolDB.RunOnDatabase(WorkersDB, WorkersCollection, query)
	}
}

// Cleanup dead deployments at intervals. This goroutine will lookup at
// each information if a deployment has running workers. If workers for a
// certain deployment is not running anymore, then it will remove the
// deployment information .
func deploymentCleaner() {
	for {
		log.Info("cleaner started to remove unused deployments and dead workers")
		infos := modelhelper.GetClients()
		for _, info := range infos {
			var numberOfWorkers int

			version, _ := strconv.Atoi(info.BuildNumber)

			query := func(c *mgo.Collection) error {
				numberOfWorkers, _ = c.Find(bson.M{
					"version": version,
					"status":  int(models.Started)},
				).Count()

				return nil
			}

			kontrolDB.RunOnDatabase(WorkersDB, WorkersCollection, query)

			// remove deployment information only if there is no worker alive for that version
			if numberOfWorkers == 0 {
				log.Info("removing deployment info for build number %s", info.BuildNumber)
				err := modelhelper.DeleteClient(info.BuildNumber)
				if err != nil {
					log.Error(err.Error())
				}
			}
		}

		// check 12 hours later again
		time.Sleep(time.Hour * 12)
	}
}
<|endoftext|>


<commit_before>package errorpages

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/containous/traefik/log"
	"github.com/containous/traefik/middlewares"
	"github.com/containous/traefik/types"
	"github.com/vulcand/oxy/forward"
	"github.com/vulcand/oxy/utils"
)

// Compile time validation that the response recorder implements http interfaces correctly.
var _ middlewares.Stateful = &responseRecorderWithCloseNotify{}

// Handler is a middleware that provides the custom error pages
type Handler struct {
	BackendName    string
	backendHandler http.Handler
	httpCodeRanges types.HTTPCodeRanges
	backendURL     string
	backendQuery   string
	FallbackURL    string // Deprecated
}

// NewHandler initializes the utils.ErrorHandler for the custom error pages
func NewHandler(errorPage *types.ErrorPage, backendName string) (*Handler, error) {
	if len(backendName) == 0 {
		return nil, errors.New("error pages: backend name is mandatory ")
	}

	httpCodeRanges, err := types.NewHTTPCodeRanges(errorPage.Status)
	if err != nil {
		return nil, err
	}

	return &Handler{
		BackendName:    backendName,
		httpCodeRanges: httpCodeRanges,
		backendQuery:   errorPage.Query,
		backendURL:     "http://0.0.0.0",
	}, nil
}

// PostLoad adds backend handler if available
func (h *Handler) PostLoad(backendHandler http.Handler) error {
	if backendHandler == nil {
		fwd, err := forward.New()
		if err != nil {
			return err
		}

		h.backendHandler = fwd
		h.backendURL = h.FallbackURL
	} else {
		h.backendHandler = backendHandler
	}

	return nil
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
	if h.backendHandler == nil {
		log.Error("Error pages: no backend handler.")
		next.ServeHTTP(w, req)
		return
	}

	recorder := newResponseRecorder(w)
	next.ServeHTTP(recorder, req)

	// check the recorder code against the configured http status code ranges
	for _, block := range h.httpCodeRanges {
		if recorder.GetCode() >= block[0] && recorder.GetCode() <= block[1] {
			log.Errorf("Caught HTTP Status Code %d, returning error page", recorder.GetCode())

			var query string
			if len(h.backendQuery) > 0 {
				query = "/" + strings.TrimPrefix(h.backendQuery, "/")
				query = strings.Replace(query, "{status}", strconv.Itoa(recorder.GetCode()), -1)
			}

			pageReq, err := newRequest(h.backendURL + query)
			if err != nil {
				log.Error(err)
				w.WriteHeader(recorder.GetCode())
				w.Write([]byte(http.StatusText(recorder.GetCode())))
				return
			}

			utils.CopyHeaders(pageReq.Header, req.Header)
			utils.CopyHeaders(w.Header(), recorder.Header())
			w.WriteHeader(recorder.GetCode())

			h.backendHandler.ServeHTTP(w, pageReq.WithContext(req.Context()))
			return
		}
	}

	// did not catch a configured status code so proceed with the request
	utils.CopyHeaders(w.Header(), recorder.Header())
	w.WriteHeader(recorder.GetCode())
	w.Write(recorder.GetBody().Bytes())
}

func newRequest(baseURL string) (*http.Request, error) {
	u, err := url.Parse(baseURL)
	if err != nil {
		return nil, fmt.Errorf("error pages: error when parse URL: %v", err)
	}

	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		return nil, fmt.Errorf("error pages: error when create query: %v", err)
	}

	req.RequestURI = u.RequestURI()
	return req, nil
}

type responseRecorder interface {
	http.ResponseWriter
	http.Flusher
	GetCode() int
	GetBody() *bytes.Buffer
	IsStreamingResponseStarted() bool
}

// newResponseRecorder returns an initialized responseRecorder.
func newResponseRecorder(rw http.ResponseWriter) responseRecorder {
	recorder := &responseRecorderWithoutCloseNotify{
		HeaderMap:      make(http.Header),
		Body:           new(bytes.Buffer),
		Code:           http.StatusOK,
		responseWriter: rw,
	}
	if _, ok := rw.(http.CloseNotifier); ok {
		return &responseRecorderWithCloseNotify{recorder}
	}
	return recorder
}

// responseRecorderWithoutCloseNotify is an implementation of http.ResponseWriter that
// records its mutations for later inspection.
type responseRecorderWithoutCloseNotify struct {
	Code      int           // the HTTP response code from WriteHeader
	HeaderMap http.Header   // the HTTP response headers
	Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to

	responseWriter           http.ResponseWriter
	err                      error
	streamingResponseStarted bool
}

type responseRecorderWithCloseNotify struct {
	*responseRecorderWithoutCloseNotify
}

// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone away.
func (rw *responseRecorderWithCloseNotify) CloseNotify() <-chan bool {
	return rw.responseWriter.(http.CloseNotifier).CloseNotify()
}

// Header returns the response headers.
func (rw *responseRecorderWithoutCloseNotify) Header() http.Header {
	if rw.HeaderMap == nil {
		rw.HeaderMap = make(http.Header)
	}
	return rw.HeaderMap
}

func (rw *responseRecorderWithoutCloseNotify) GetCode() int {
	return rw.Code
}

func (rw *responseRecorderWithoutCloseNotify) GetBody() *bytes.Buffer {
	return rw.Body
}

func (rw *responseRecorderWithoutCloseNotify) IsStreamingResponseStarted() bool {
	return rw.streamingResponseStarted
}

// Write always succeeds and writes to rw.Body, if not nil.
func (rw *responseRecorderWithoutCloseNotify) Write(buf []byte) (int, error) {
	if rw.err != nil {
		return 0, rw.err
	}
	return rw.Body.Write(buf)
}

// WriteHeader sets rw.Code.
func (rw *responseRecorderWithoutCloseNotify) WriteHeader(code int) {
	rw.Code = code
}

// Hijack hijacks the connection
func (rw *responseRecorderWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return rw.responseWriter.(http.Hijacker).Hijack()
}

// Flush sends any buffered data to the client.
func (rw *responseRecorderWithoutCloseNotify) Flush() {
	if !rw.streamingResponseStarted {
		utils.CopyHeaders(rw.responseWriter.Header(), rw.Header())
		rw.responseWriter.WriteHeader(rw.Code)
		rw.streamingResponseStarted = true
	}

	_, err := rw.responseWriter.Write(rw.Body.Bytes())
	if err != nil {
		log.Errorf("Error writing response in responseRecorder: %s", err)
		rw.err = err
	}
	rw.Body.Reset()

	if flusher, ok := rw.responseWriter.(http.Flusher); ok {
		flusher.Flush()
	}
}
<commit_msg>Fix error pages content. <commit_after>package errorpages

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/containous/traefik/log"
	"github.com/containous/traefik/middlewares"
	"github.com/containous/traefik/types"
	"github.com/vulcand/oxy/forward"
	"github.com/vulcand/oxy/utils"
)

// Compile time validation that the response recorder implements http interfaces correctly.
var _ middlewares.Stateful = &responseRecorderWithCloseNotify{}

// Handler is a middleware that provides the custom error pages
type Handler struct {
	BackendName    string
	backendHandler http.Handler
	httpCodeRanges types.HTTPCodeRanges
	backendURL     string
	backendQuery   string
	FallbackURL    string // Deprecated
}

// NewHandler initializes the utils.ErrorHandler for the custom error pages
func NewHandler(errorPage *types.ErrorPage, backendName string) (*Handler, error) {
	if len(backendName) == 0 {
		return nil, errors.New("error pages: backend name is mandatory ")
	}

	httpCodeRanges, err := types.NewHTTPCodeRanges(errorPage.Status)
	if err != nil {
		return nil, err
	}

	return &Handler{
		BackendName:    backendName,
		httpCodeRanges: httpCodeRanges,
		backendQuery:   errorPage.Query,
		backendURL:     "http://0.0.0.0",
	}, nil
}

// PostLoad adds backend handler if available
func (h *Handler) PostLoad(backendHandler http.Handler) error {
	if backendHandler == nil {
		fwd, err := forward.New()
		if err != nil {
			return err
		}

		h.backendHandler = fwd
		h.backendURL = h.FallbackURL
	} else {
		h.backendHandler = backendHandler
	}

	return nil
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
	if h.backendHandler == nil {
		log.Error("Error pages: no backend handler.")
		next.ServeHTTP(w, req)
		return
	}

	recorder := newResponseRecorder(w)
	next.ServeHTTP(recorder, req)

	// check the recorder code against the configured http status code ranges
	for _, block := range h.httpCodeRanges {
		if recorder.GetCode() >= block[0] && recorder.GetCode() <= block[1] {
			log.Errorf("Caught HTTP Status Code %d, returning error page", recorder.GetCode())

			var query string
			if len(h.backendQuery) > 0 {
				query = "/" + strings.TrimPrefix(h.backendQuery, "/")
				query = strings.Replace(query, "{status}", strconv.Itoa(recorder.GetCode()), -1)
			}

			pageReq, err := newRequest(h.backendURL + query)
			if err != nil {
				log.Error(err)
				w.WriteHeader(recorder.GetCode())
				fmt.Fprint(w, http.StatusText(recorder.GetCode()))
				return
			}

			recorderErrorPage := newResponseRecorder(w)
			utils.CopyHeaders(pageReq.Header, req.Header)

			h.backendHandler.ServeHTTP(recorderErrorPage, pageReq.WithContext(req.Context()))

			utils.CopyHeaders(w.Header(), recorder.Header())
			for key := range recorderErrorPage.Header() {
				w.Header().Del(key)
			}
			utils.CopyHeaders(w.Header(), recorderErrorPage.Header())

			w.WriteHeader(recorder.GetCode())
			w.Write(recorderErrorPage.GetBody().Bytes())
			return
		}
	}

	// did not catch a configured status code so proceed with the request
	utils.CopyHeaders(w.Header(), recorder.Header())
	w.WriteHeader(recorder.GetCode())
	w.Write(recorder.GetBody().Bytes())
}

func newRequest(baseURL string) (*http.Request, error) {
	u, err := url.Parse(baseURL)
	if err != nil {
		return nil, fmt.Errorf("error pages: error when parse URL: %v", err)
	}

	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		return nil, fmt.Errorf("error pages: error when create query: %v", err)
	}

	req.RequestURI = u.RequestURI()
	return req, nil
}

type responseRecorder interface {
	http.ResponseWriter
	http.Flusher
	GetCode() int
	GetBody() *bytes.Buffer
	IsStreamingResponseStarted() bool
}

// newResponseRecorder returns an initialized responseRecorder.
func newResponseRecorder(rw http.ResponseWriter) responseRecorder {
	recorder := &responseRecorderWithoutCloseNotify{
		HeaderMap:      make(http.Header),
		Body:           new(bytes.Buffer),
		Code:           http.StatusOK,
		responseWriter: rw,
	}
	if _, ok := rw.(http.CloseNotifier); ok {
		return &responseRecorderWithCloseNotify{recorder}
	}
	return recorder
}

// responseRecorderWithoutCloseNotify is an implementation of http.ResponseWriter that
// records its mutations for later inspection.
type responseRecorderWithoutCloseNotify struct {
	Code      int           // the HTTP response code from WriteHeader
	HeaderMap http.Header   // the HTTP response headers
	Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to

	responseWriter           http.ResponseWriter
	err                      error
	streamingResponseStarted bool
}

type responseRecorderWithCloseNotify struct {
	*responseRecorderWithoutCloseNotify
}

// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone away.
func (rw *responseRecorderWithCloseNotify) CloseNotify() <-chan bool {
	return rw.responseWriter.(http.CloseNotifier).CloseNotify()
}

// Header returns the response headers.
func (rw *responseRecorderWithoutCloseNotify) Header() http.Header {
	if rw.HeaderMap == nil {
		rw.HeaderMap = make(http.Header)
	}
	return rw.HeaderMap
}

func (rw *responseRecorderWithoutCloseNotify) GetCode() int {
	return rw.Code
}

func (rw *responseRecorderWithoutCloseNotify) GetBody() *bytes.Buffer {
	return rw.Body
}

func (rw *responseRecorderWithoutCloseNotify) IsStreamingResponseStarted() bool {
	return rw.streamingResponseStarted
}

// Write always succeeds and writes to rw.Body, if not nil.
func (rw *responseRecorderWithoutCloseNotify) Write(buf []byte) (int, error) {
	if rw.err != nil {
		return 0, rw.err
	}
	return rw.Body.Write(buf)
}

// WriteHeader sets rw.Code.
func (rw *responseRecorderWithoutCloseNotify) WriteHeader(code int) {
	rw.Code = code
}

// Hijack hijacks the connection
func (rw *responseRecorderWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return rw.responseWriter.(http.Hijacker).Hijack()
}

// Flush sends any buffered data to the client.
func (rw *responseRecorderWithoutCloseNotify) Flush() {
	if !rw.streamingResponseStarted {
		utils.CopyHeaders(rw.responseWriter.Header(), rw.Header())
		rw.responseWriter.WriteHeader(rw.Code)
		rw.streamingResponseStarted = true
	}

	_, err := rw.responseWriter.Write(rw.Body.Bytes())
	if err != nil {
		log.Errorf("Error writing response in responseRecorder: %s", err)
		rw.err = err
	}
	rw.Body.Reset()

	if flusher, ok := rw.responseWriter.(http.Flusher); ok {
		flusher.Flush()
	}
}
<|endoftext|>


<commit_before>package sudoku

import (
	"testing"
)

func TestForcingChains(t *testing.T) {

	//Steps to test this:
	//* In the forcing chain helper, calculate the steps once, then
	//pass them in each time in a list of ~10 calls to solveTechniqueTEstHelper that we know are valid here.
	//* VERIFY MANUALLY that each step that is returned is actually a valid application of forcingchains.

	options := solveTechniqueTestHelperOptions{
		checkAllSteps: true,
		debugPrint:    true,
	}

	grid, solver, steps := humanSolveTechniqueTestHelperStepGenerator(t,
		"forcingchain_test1.sdk", "Forcing Chain", options)

	options.stepsToCheck.grid = grid
	options.stepsToCheck.solver = solver
	options.stepsToCheck.steps = steps

	//OK, now we'll walk through all of the options in a loop and make sure they all show
	//up in the solve steps.

	type loopOptions struct {
		targetCells  []cellRef
		targetNums   IntSlice
		pointerCells []cellRef
		pointerNums  IntSlice
		description  string
	}

	//Tester puzzle: http://www.komoroske.com/sudoku/index.php?puzzle=Q6Ur5iYGINSUFcyocqaY6G91DpttiqYzs

	tests := []loopOptions{
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
			description:  "cell (1,0) only has two options, 1 and 2, and if you put either one in and see the chain of implications it leads to, both ones end up with 7 in cell (0,1), so we can just fill that number in",
		},
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
			//Explicitly don't test description after the first one.
		},
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{7, 8}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		//Skipping 0,1 / 7 / 5,7 / 1,3 because I think implications force it wrong.
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 7}},
			pointerNums:  IntSlice([]int{1, 3}),
		},
		{
			targetCells:  []cellRef{{1, 8}},
			targetNums:   IntSlice([]int{4}),
			pointerCells: []cellRef{{4, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//This next one's particularly long implication chain
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{4, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 1}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 1}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{7, 8}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
	}

	if len(tests) != len(steps) {
		t.Error("We didn't have enough tests for all of the steps that forcing chains returned. Got", len(tests), "expected", len(steps))
	}

	for _, test := range tests {

		options.targetCells = test.targetCells
		options.targetNums = test.targetNums
		options.pointerCells = test.pointerCells
		options.pointerNums = test.pointerNums
		options.description = test.description

		humanSolveTechniqueTestHelper(t, "forcingchain_test1.sdk", "Forcing Chain", options)
	}

	//TODO: test all other valid steps that could be found at this grid state for this technique.

}
<commit_msg>TESTS FAIL. Changed it so we only debugPrint the first time through the loop.<commit_after>package sudoku

import (
	"testing"
)

func TestForcingChains(t *testing.T) {

	//Steps to test this:
	//* In the forcing chain helper, calculate the steps once, then
	//pass them in each time in a list of ~10 calls to solveTechniqueTEstHelper that we know are valid here.
	//* VERIFY MANUALLY that each step that is returned is actually a valid application of forcingchains.

	options := solveTechniqueTestHelperOptions{
		checkAllSteps: true,
	}

	grid, solver, steps := humanSolveTechniqueTestHelperStepGenerator(t,
		"forcingchain_test1.sdk", "Forcing Chain", options)

	options.stepsToCheck.grid = grid
	options.stepsToCheck.solver = solver
	options.stepsToCheck.steps = steps

	//OK, now we'll walk through all of the options in a loop and make sure they all show
	//up in the solve steps.

	type loopOptions struct {
		targetCells  []cellRef
		targetNums   IntSlice
		pointerCells []cellRef
		pointerNums  IntSlice
		description  string
	}

	//Tester puzzle: http://www.komoroske.com/sudoku/index.php?puzzle=Q6Ur5iYGINSUFcyocqaY6G91DpttiqYzs

	tests := []loopOptions{
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
			description:  "cell (1,0) only has two options, 1 and 2, and if you put either one in and see the chain of implications it leads to, both ones end up with 7 in cell (0,1), so we can just fill that number in",
		},
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
			//Explicitly don't test description after the first one.
		},
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{7, 8}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		//Skipping 0,1 / 7 / 5,7 / 1,3 because I think implications force it wrong.
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{5, 7}},
			pointerNums:  IntSlice([]int{1, 3}),
		},
		{
			targetCells:  []cellRef{{1, 8}},
			targetNums:   IntSlice([]int{4}),
			pointerCells: []cellRef{{4, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//This next one's particularly long implication chain
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{4, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 1}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 1}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
		{
			targetCells:  []cellRef{{0, 1}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{1, 0}},
			pointerNums:  IntSlice([]int{1, 2}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{5, 1}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		{
			targetCells:  []cellRef{{1, 0}},
			targetNums:   IntSlice([]int{1}),
			pointerCells: []cellRef{{0, 6}},
			pointerNums:  IntSlice([]int{3, 7}),
		},
		//Another particularly long one
		{
			targetCells:  []cellRef{{8, 3}},
			targetNums:   IntSlice([]int{7}),
			pointerCells: []cellRef{{7, 8}},
			pointerNums:  IntSlice([]int{2, 7}),
		},
	}

	if len(tests) != len(steps) {
		t.Error("We didn't have enough tests for all of the steps that forcing chains returned. Got", len(tests), "expected", len(steps))
	}

	for i, test := range tests {

		if i == 0 {
			options.debugPrint = true
		} else {
			options.debugPrint = false
		}

		options.targetCells = test.targetCells
		options.targetNums = test.targetNums
		options.pointerCells = test.pointerCells
		options.pointerNums = test.pointerNums
		options.description = test.description

		humanSolveTechniqueTestHelper(t, "forcingchain_test1.sdk", "Forcing Chain", options)
	}

	//TODO: test all other valid steps that could be found at this grid state for this technique.

}
<|endoftext|>