<commit_before>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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 chart

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/kubernetes/helm/pkg/log"
)

// ChartfileName is the default Chart file name.
const ChartfileName string = "Chart.yaml"

const (
	preTemplates string = "templates/"
	preHooks     string = "hooks/"
	preDocs      string = "docs/"
	preIcon      string = "icon.svg"
)

var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=")

// Chart represents a complete chart.
//
// A chart consists of the following parts:
//
// 	- Chart.yaml: In code, we refer to this as the Chartfile
// 	- templates/*: The template directory
// 	- README.md: Optional README file
// 	- LICENSE: Optional license file
// 	- hooks/: Optional hooks registry
//	- docs/: Optional docs directory
//
// Packed charts are stored in gzipped tar archives (.tgz). Unpackaged charts
// are directories where the directory name is the Chartfile.Name.
//
// Optionally, a chart might also locate a provenance (.prov) file that it
// can use for cryptographic signing.
type Chart struct {
	loader chartLoader
}

// Close the chart.
//
// Charts should always be closed when no longer needed.
func (c *Chart) Close() error {
	return c.loader.close()
}

// Chartfile gets the Chartfile (Chart.yaml) for this chart.
func (c *Chart) Chartfile() *Chartfile {
	return c.loader.chartfile()
}

// Dir returns the directory where the charts are located.
func (c *Chart) Dir() string {
	return c.loader.dir()
}

// DocsDir returns the directory where the chart's documentation is stored.
func (c *Chart) DocsDir() string {
	return filepath.Join(c.loader.dir(), preDocs)
}

// HooksDir returns the directory where the hooks are stored.
func (c *Chart) HooksDir() string {
	return filepath.Join(c.loader.dir(), preHooks)
}

// TemplatesDir returns the directory where the templates are stored.
func (c *Chart) TemplatesDir() string {
	return filepath.Join(c.loader.dir(), preTemplates)
}

// Icon returns the path to the icon.svg file.
//
// If an icon is not found in the chart, this will return an error.
func (c *Chart) Icon() (string, error) {
	i := filepath.Join(c.Dir(), preIcon)
	_, err := os.Stat(i)
	return i, err
}

// chartLoader provides load, close, and save implementations for a chart.
type chartLoader interface {
	// Chartfile resturns a *Chartfile for this chart.
	chartfile() *Chartfile
	// Dir returns a directory where the chart can be accessed.
	dir() string

	// Close cleans up a chart.
	close() error
}

type dirChart struct {
	chartyaml *Chartfile
	chartdir  string
}

func (d *dirChart) chartfile() *Chartfile {
	return d.chartyaml
}

func (d *dirChart) dir() string {
	return d.chartdir
}

func (d *dirChart) close() error {
	return nil
}

type tarChart struct {
	chartyaml *Chartfile
	tmpDir    string
}

func (t *tarChart) chartfile() *Chartfile {
	return t.chartyaml
}

func (t *tarChart) dir() string {
	return t.tmpDir
}

func (t *tarChart) close() error {
	// Remove the temp directory.
	return os.RemoveAll(t.tmpDir)
}

// Create creates a new chart in a directory.
//
// Inside of dir, this will create a directory based on the name of
// chartfile.Name. It will then write the Chart.yaml into this directory and
// create the (empty) appropriate directories.
//
// The returned *Chart will point to the newly created directory.
//
// If dir does not exist, this will return an error.
// If Chart.yaml or any directories cannot be created, this will return an
// error. In such a case, this will attempt to clean up by removing the
// new chart directory.
func Create(chartfile *Chartfile, dir string) (*Chart, error) {
	path, err := filepath.Abs(dir)
	if err != nil {
		return nil, err
	}

	if fi, err := os.Stat(path); err != nil {
		return nil, err
	} else if !fi.IsDir() {
		return nil, fmt.Errorf("no such directory %s", path)
	}

	n := fname(chartfile.Name)
	cdir := filepath.Join(path, n)
	if _, err := os.Stat(cdir); err == nil {
		return nil, fmt.Errorf("directory already exists: %s", cdir)
	}
	if err := os.MkdirAll(cdir, 0755); err != nil {
		return nil, err
	}

	rollback := func() {
		// TODO: Should we log failures here?
		os.RemoveAll(cdir)
	}

	if err := chartfile.Save(filepath.Join(cdir, ChartfileName)); err != nil {
		rollback()
		return nil, err
	}

	for _, d := range []string{preHooks, preDocs, preTemplates} {
		if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil {
			rollback()
			return nil, err
		}
	}

	return &Chart{
		loader: &dirChart{chartyaml: chartfile, chartdir: cdir},
	}, nil
}

// fname prepares names for the filesystem
func fname(name string) string {
	// Right now, we don't do anything. Do we need to encode any particular
	// characters? What characters are legal in a chart name, but not in file
	// names on Windows, Linux, or OSX.
	return name
}

// LoadDir loads an entire chart from a directory.
//
// This includes the Chart.yaml (*Chartfile) and all of the manifests.
//
// If you are just reading the Chart.yaml file, it is substantially more
// performant to use LoadChartfile.
func LoadDir(chart string) (*Chart, error) {
	if fi, err := os.Stat(chart); err != nil {
		return nil, err
	} else if !fi.IsDir() {
		return nil, fmt.Errorf("Chart %s is not a directory.", chart)
	}

	cf, err := LoadChartfile(filepath.Join(chart, "Chart.yaml"))
	if err != nil {
		return nil, err
	}

	cl := &dirChart{
		chartyaml: cf,
		chartdir:  chart,
	}

	return &Chart{
		loader: cl,
	}, nil
}

// LoadData loads a chart from data, where data is a []byte containing a gzipped tar file.
func LoadData(data []byte) (*Chart, error) {
	return LoadDataFromReader(bytes.NewBuffer(data))
}

// Load loads a chart from a chart archive.
//
// A chart archive is a gzipped tar archive that follows the Chart format
// specification.
func Load(archive string) (*Chart, error) {
	if fi, err := os.Stat(archive); err != nil {
		return nil, err
	} else if fi.IsDir() {
		return nil, errors.New("cannot load a directory with chart.Load()")
	}

	raw, err := os.Open(archive)
	if err != nil {
		return nil, err
	}
	defer raw.Close()

	return LoadDataFromReader(raw)
}

// LoadDataFromReader loads a chart a reader
func LoadDataFromReader(r io.Reader) (*Chart, error) {
	unzipped, err := gzip.NewReader(r)
	if err != nil {
		return nil, err
	}
	defer unzipped.Close()

	untarred := tar.NewReader(unzipped)
	c, err := loadTar(untarred)
	if err != nil {
		return nil, err
	}

	cf, err := LoadChartfile(filepath.Join(c.tmpDir, ChartfileName))
	if err != nil {
		return nil, err
	}
	c.chartyaml = cf
	return &Chart{loader: c}, nil
}

func loadTar(r *tar.Reader) (*tarChart, error) {
	td, err := ioutil.TempDir("", "chart-")
	if err != nil {
		return nil, err
	}
	c := &tarChart{
		chartyaml: &Chartfile{},
		tmpDir:    td,
	}

	firstDir := ""

	hdr, err := r.Next()
	for err == nil {
		log.Debug("Reading %s", hdr.Name)

		// This is to prevent malformed tar attacks.
		hdr.Name = filepath.Clean(hdr.Name)

		if firstDir == "" {
			fi := hdr.FileInfo()
			if fi.IsDir() {
				log.Debug("Discovered app named %s", hdr.Name)
				firstDir = hdr.Name
			} else {
				log.Warn("Unexpected file at root of archive: %s", hdr.Name)
			}
		} else if strings.HasPrefix(hdr.Name, firstDir) {
			log.Debug("Extracting %s to %s", hdr.Name, c.tmpDir)

			// We know this has the prefix, so we know there won't be an error.
			rel, _ := filepath.Rel(firstDir, hdr.Name)

			// If tar record is a directory, create one in the tmpdir and return.
			if hdr.FileInfo().IsDir() {
				os.MkdirAll(filepath.Join(c.tmpDir, rel), 0755)
				hdr, err = r.Next()
				continue
			}

			dest := filepath.Join(c.tmpDir, rel)
			f, err := os.Create(filepath.Join(c.tmpDir, rel))
			if err != nil {
				log.Warn("Could not create %s: %s", dest, err)
				hdr, err = r.Next()
				continue
			}
			if _, err := io.Copy(f, r); err != nil {
				log.Warn("Failed to copy %s: %s", dest, err)
			}
			f.Close()
		} else {
			log.Warn("Unexpected file outside of chart: %s", hdr.Name)
		}
		hdr, err = r.Next()
	}

	if err != nil && err != io.EOF {
		log.Warn("Unexpected error reading tar: %s", err)
		c.close()
		return c, err
	}
	log.Info("Reached end of Tar file")

	return c, nil
}

// Member is a file in a chart.
type Member struct {
	Path    string `json:"path"`    // Path from the root of the chart.
	Content []byte `json:"content"` // Base64 encoded content.
}

// LoadTemplates loads the members of TemplatesDir().
func (c *Chart) LoadTemplates() ([]*Member, error) {
	dir := c.TemplatesDir()
	return c.loadDirectory(dir)
}

// loadDirectory loads the members of a directory.
func (c *Chart) loadDirectory(dir string) ([]*Member, error) {
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, err
	}

	members := []*Member{}
	for _, file := range files {
		filename := filepath.Join(dir, file.Name())
		if !file.IsDir() {
			addition, err := c.loadMember(filename)
			if err != nil {
				return nil, err
			}

			members = append(members, addition)
		} else {
			additions, err := c.loadDirectory(filename)
			if err != nil {
				return nil, err
			}

			members = append(members, additions...)
		}
	}

	return members, nil
}

// LoadMember loads a chart member from a given path where path is the root of the chart.
func (c *Chart) LoadMember(path string) (*Member, error) {
	filename := filepath.Join(c.loader.dir(), path)
	return c.loadMember(filename)
}

// loadMember loads and base 64 encodes a file.
func (c *Chart) loadMember(filename string) (*Member, error) {
	dir := c.Dir()
	if !strings.HasPrefix(filename, dir) {
		err := fmt.Errorf("File %s is outside chart directory %s", filename, dir)
		return nil, err
	}

	b, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	path := strings.TrimPrefix(filename, dir)
	content := base64.StdEncoding.EncodeToString(b)
	result := &Member{
		Path:    path,
		Content: []byte(content),
	}

	return result, nil
}

//ChartContent is abstraction for the contents of a chart
type ChartContent struct {
	Chartfile *Chartfile `json:"chartfile"`
	Members   []*Member  `json:"members"`
}

// LoadContent loads contents of a chart directory into ChartContent
func (c *Chart) LoadContent() (*ChartContent, error) {
	ms, err := c.loadDirectory(c.Dir())
	if err != nil {
		return nil, err
	}

	cc := &ChartContent{
		Chartfile: c.Chartfile(),
		Members:   ms,
	}

	return cc, nil
}
<commit_msg>ref(chart): ChartContent -> chartContent<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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 chart

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/kubernetes/helm/pkg/log"
)

// ChartfileName is the default Chart file name.
const ChartfileName string = "Chart.yaml"

const (
	preTemplates string = "templates/"
	preHooks     string = "hooks/"
	preDocs      string = "docs/"
	preIcon      string = "icon.svg"
)

var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=")

// Chart represents a complete chart.
//
// A chart consists of the following parts:
//
// 	- Chart.yaml: In code, we refer to this as the Chartfile
// 	- templates/*: The template directory
// 	- README.md: Optional README file
// 	- LICENSE: Optional license file
// 	- hooks/: Optional hooks registry
//	- docs/: Optional docs directory
//
// Packed charts are stored in gzipped tar archives (.tgz). Unpackaged charts
// are directories where the directory name is the Chartfile.Name.
//
// Optionally, a chart might also locate a provenance (.prov) file that it
// can use for cryptographic signing.
type Chart struct {
	loader chartLoader
}

// Close the chart.
//
// Charts should always be closed when no longer needed.
func (c *Chart) Close() error {
	return c.loader.close()
}

// Chartfile gets the Chartfile (Chart.yaml) for this chart.
func (c *Chart) Chartfile() *Chartfile {
	return c.loader.chartfile()
}

// Dir returns the directory where the charts are located.
func (c *Chart) Dir() string {
	return c.loader.dir()
}

// DocsDir returns the directory where the chart's documentation is stored.
func (c *Chart) DocsDir() string {
	return filepath.Join(c.loader.dir(), preDocs)
}

// HooksDir returns the directory where the hooks are stored.
func (c *Chart) HooksDir() string {
	return filepath.Join(c.loader.dir(), preHooks)
}

// TemplatesDir returns the directory where the templates are stored.
func (c *Chart) TemplatesDir() string {
	return filepath.Join(c.loader.dir(), preTemplates)
}

// Icon returns the path to the icon.svg file.
//
// If an icon is not found in the chart, this will return an error.
func (c *Chart) Icon() (string, error) {
	i := filepath.Join(c.Dir(), preIcon)
	_, err := os.Stat(i)
	return i, err
}

// chartLoader provides load, close, and save implementations for a chart.
type chartLoader interface {
	// Chartfile resturns a *Chartfile for this chart.
	chartfile() *Chartfile
	// Dir returns a directory where the chart can be accessed.
	dir() string

	// Close cleans up a chart.
	close() error
}

type dirChart struct {
	chartyaml *Chartfile
	chartdir  string
}

func (d *dirChart) chartfile() *Chartfile {
	return d.chartyaml
}

func (d *dirChart) dir() string {
	return d.chartdir
}

func (d *dirChart) close() error {
	return nil
}

type tarChart struct {
	chartyaml *Chartfile
	tmpDir    string
}

func (t *tarChart) chartfile() *Chartfile {
	return t.chartyaml
}

func (t *tarChart) dir() string {
	return t.tmpDir
}

func (t *tarChart) close() error {
	// Remove the temp directory.
	return os.RemoveAll(t.tmpDir)
}

// Create creates a new chart in a directory.
//
// Inside of dir, this will create a directory based on the name of
// chartfile.Name. It will then write the Chart.yaml into this directory and
// create the (empty) appropriate directories.
//
// The returned *Chart will point to the newly created directory.
//
// If dir does not exist, this will return an error.
// If Chart.yaml or any directories cannot be created, this will return an
// error. In such a case, this will attempt to clean up by removing the
// new chart directory.
func Create(chartfile *Chartfile, dir string) (*Chart, error) {
	path, err := filepath.Abs(dir)
	if err != nil {
		return nil, err
	}

	if fi, err := os.Stat(path); err != nil {
		return nil, err
	} else if !fi.IsDir() {
		return nil, fmt.Errorf("no such directory %s", path)
	}

	n := fname(chartfile.Name)
	cdir := filepath.Join(path, n)
	if _, err := os.Stat(cdir); err == nil {
		return nil, fmt.Errorf("directory already exists: %s", cdir)
	}
	if err := os.MkdirAll(cdir, 0755); err != nil {
		return nil, err
	}

	rollback := func() {
		// TODO: Should we log failures here?
		os.RemoveAll(cdir)
	}

	if err := chartfile.Save(filepath.Join(cdir, ChartfileName)); err != nil {
		rollback()
		return nil, err
	}

	for _, d := range []string{preHooks, preDocs, preTemplates} {
		if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil {
			rollback()
			return nil, err
		}
	}

	return &Chart{
		loader: &dirChart{chartyaml: chartfile, chartdir: cdir},
	}, nil
}

// fname prepares names for the filesystem
func fname(name string) string {
	// Right now, we don't do anything. Do we need to encode any particular
	// characters? What characters are legal in a chart name, but not in file
	// names on Windows, Linux, or OSX.
	return name
}

// LoadDir loads an entire chart from a directory.
//
// This includes the Chart.yaml (*Chartfile) and all of the manifests.
//
// If you are just reading the Chart.yaml file, it is substantially more
// performant to use LoadChartfile.
func LoadDir(chart string) (*Chart, error) {
	if fi, err := os.Stat(chart); err != nil {
		return nil, err
	} else if !fi.IsDir() {
		return nil, fmt.Errorf("Chart %s is not a directory.", chart)
	}

	cf, err := LoadChartfile(filepath.Join(chart, "Chart.yaml"))
	if err != nil {
		return nil, err
	}

	cl := &dirChart{
		chartyaml: cf,
		chartdir:  chart,
	}

	return &Chart{
		loader: cl,
	}, nil
}

// LoadData loads a chart from data, where data is a []byte containing a gzipped tar file.
func LoadData(data []byte) (*Chart, error) {
	return LoadDataFromReader(bytes.NewBuffer(data))
}

// Load loads a chart from a chart archive.
//
// A chart archive is a gzipped tar archive that follows the Chart format
// specification.
func Load(archive string) (*Chart, error) {
	if fi, err := os.Stat(archive); err != nil {
		return nil, err
	} else if fi.IsDir() {
		return nil, errors.New("cannot load a directory with chart.Load()")
	}

	raw, err := os.Open(archive)
	if err != nil {
		return nil, err
	}
	defer raw.Close()

	return LoadDataFromReader(raw)
}

// LoadDataFromReader loads a chart a reader
func LoadDataFromReader(r io.Reader) (*Chart, error) {
	unzipped, err := gzip.NewReader(r)
	if err != nil {
		return nil, err
	}
	defer unzipped.Close()

	untarred := tar.NewReader(unzipped)
	c, err := loadTar(untarred)
	if err != nil {
		return nil, err
	}

	cf, err := LoadChartfile(filepath.Join(c.tmpDir, ChartfileName))
	if err != nil {
		return nil, err
	}
	c.chartyaml = cf
	return &Chart{loader: c}, nil
}

func loadTar(r *tar.Reader) (*tarChart, error) {
	td, err := ioutil.TempDir("", "chart-")
	if err != nil {
		return nil, err
	}
	c := &tarChart{
		chartyaml: &Chartfile{},
		tmpDir:    td,
	}

	firstDir := ""

	hdr, err := r.Next()
	for err == nil {
		log.Debug("Reading %s", hdr.Name)

		// This is to prevent malformed tar attacks.
		hdr.Name = filepath.Clean(hdr.Name)

		if firstDir == "" {
			fi := hdr.FileInfo()
			if fi.IsDir() {
				log.Debug("Discovered app named %s", hdr.Name)
				firstDir = hdr.Name
			} else {
				log.Warn("Unexpected file at root of archive: %s", hdr.Name)
			}
		} else if strings.HasPrefix(hdr.Name, firstDir) {
			log.Debug("Extracting %s to %s", hdr.Name, c.tmpDir)

			// We know this has the prefix, so we know there won't be an error.
			rel, _ := filepath.Rel(firstDir, hdr.Name)

			// If tar record is a directory, create one in the tmpdir and return.
			if hdr.FileInfo().IsDir() {
				os.MkdirAll(filepath.Join(c.tmpDir, rel), 0755)
				hdr, err = r.Next()
				continue
			}

			dest := filepath.Join(c.tmpDir, rel)
			f, err := os.Create(filepath.Join(c.tmpDir, rel))
			if err != nil {
				log.Warn("Could not create %s: %s", dest, err)
				hdr, err = r.Next()
				continue
			}
			if _, err := io.Copy(f, r); err != nil {
				log.Warn("Failed to copy %s: %s", dest, err)
			}
			f.Close()
		} else {
			log.Warn("Unexpected file outside of chart: %s", hdr.Name)
		}
		hdr, err = r.Next()
	}

	if err != nil && err != io.EOF {
		log.Warn("Unexpected error reading tar: %s", err)
		c.close()
		return c, err
	}
	log.Info("Reached end of Tar file")

	return c, nil
}

// Member is a file in a chart.
type Member struct {
	Path    string `json:"path"`    // Path from the root of the chart.
	Content []byte `json:"content"` // Base64 encoded content.
}

// LoadTemplates loads the members of TemplatesDir().
func (c *Chart) LoadTemplates() ([]*Member, error) {
	dir := c.TemplatesDir()
	return c.loadDirectory(dir)
}

// loadDirectory loads the members of a directory.
func (c *Chart) loadDirectory(dir string) ([]*Member, error) {
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, err
	}

	members := []*Member{}
	for _, file := range files {
		filename := filepath.Join(dir, file.Name())
		if !file.IsDir() {
			addition, err := c.loadMember(filename)
			if err != nil {
				return nil, err
			}

			members = append(members, addition)
		} else {
			additions, err := c.loadDirectory(filename)
			if err != nil {
				return nil, err
			}

			members = append(members, additions...)
		}
	}

	return members, nil
}

// LoadMember loads a chart member from a given path where path is the root of the chart.
func (c *Chart) LoadMember(path string) (*Member, error) {
	filename := filepath.Join(c.loader.dir(), path)
	return c.loadMember(filename)
}

// loadMember loads and base 64 encodes a file.
func (c *Chart) loadMember(filename string) (*Member, error) {
	dir := c.Dir()
	if !strings.HasPrefix(filename, dir) {
		err := fmt.Errorf("File %s is outside chart directory %s", filename, dir)
		return nil, err
	}

	b, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	path := strings.TrimPrefix(filename, dir)
	content := base64.StdEncoding.EncodeToString(b)
	result := &Member{
		Path:    path,
		Content: []byte(content),
	}

	return result, nil
}

//chartContent is abstraction for the contents of a chart
type chartContent struct {
	Chartfile *Chartfile `json:"chartfile"`
	Members   []*Member  `json:"members"`
}

// loadContent loads contents of a chart directory into chartContent
func (c *Chart) loadContent() (*chartContent, error) {
	ms, err := c.loadDirectory(c.Dir())
	if err != nil {
		return nil, err
	}

	cc := &chartContent{
		Chartfile: c.Chartfile(),
		Members:   ms,
	}

	return cc, nil
}
<|endoftext|>


<commit_before>package envoy

import (
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/cilium/cilium/pkg/completion"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/policy"

	"github.com/golang/protobuf/proto"
	"github.com/sirupsen/logrus"
)

var log = logging.DefaultLogger

var (
	// envoyLevelMap maps logrus.Level values to Envoy (spdlog) log levels.
	envoyLevelMap = map[logrus.Level]string{
		logrus.PanicLevel: "off",
		logrus.FatalLevel: "critical",
		logrus.ErrorLevel: "error",
		logrus.WarnLevel:  "warning",
		logrus.InfoLevel:  "info",
		logrus.DebugLevel: "debug",
		// spdlog "trace" not mapped
	}
)

type admin struct {
	adminURL string
	level    string
}

func (a *admin) transact(query string) error {
	resp, err := http.Get(a.adminURL + query)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	ret := strings.Replace(string(body), "\r", "", -1)
	log.Debug("Envoy admin response to " + query + ": " + ret)
	return nil
}

func (a *admin) changeLogLevel(level logrus.Level) error {
	envoyLevel := envoyLevelMap[level]
	if envoyLevel != a.level {
		err := a.transact("logging?level=" + envoyLevel)
		if err != nil {
			log.WithError(err).Warn("Envoy: Failed setting log level: ", envoyLevel)
		} else {
			a.level = envoyLevel
		}
		return err
	}
	log.Debug("Envoy log level is already set as: " + envoyLevel)
	return nil
}

func (a *admin) quit() error {
	return a.transact("quitquitquit")
}

// Envoy manages a running Envoy proxy instance via the
// ListenerDiscoveryService and RouteDiscoveryService gRPC APIs.
type Envoy struct {
	cmd               *exec.Cmd
	LogPath           string
	AccessLogPath     string
	accessLogListener *net.UnixListener
	ldsSock           string
	lds               *LDSServer
	rdsSock           string
	rds               *RDSServer
	admin             *admin
}

// Logger is used to feed access log entires from Envoy to cilium access log.
type Logger interface {
	Log(entry *HttpLogEntry)
}

// GetEnvoyVersion returns the envoy binary version string
func GetEnvoyVersion() string {
	out, err := exec.Command("cilium-envoy", "--version").Output()
	if err != nil {
		log.WithError(err).Fatal(`Envoy binary "cilium-envoy" cannot be executed`)
	}
	return strings.TrimSpace(string(out))
}

// StartEnvoy starts an Envoy proxy instance.
func StartEnvoy(adminPort uint32, stateDir, logDir string, baseID uint64) *Envoy {
	bootstrapPath := filepath.Join(stateDir, "bootstrap.pb")
	logPath := filepath.Join(logDir, "cilium-envoy.log")
	adminAddress := "127.0.0.1:" + strconv.FormatUint(uint64(adminPort), 10)
	ldsPath := filepath.Join(stateDir, "lds.sock")
	rdsPath := filepath.Join(stateDir, "rds.sock")
	accessLogPath := filepath.Join(stateDir, "access_log.sock")

	e := &Envoy{
		LogPath:       logPath,
		AccessLogPath: accessLogPath,
		ldsSock:       ldsPath,
		rdsSock:       rdsPath,
		admin:         &admin{adminURL: "http://" + adminAddress + "/"},
	}

	// Create static configuration
	createBootstrap(bootstrapPath, "envoy1", "cluster1", "version1",
		"ldsCluster", ldsPath, "rdsCluster", rdsPath, "cluster1", adminPort)

	e.startAccesslogServer(accessLogPath)

	log.Debug("Envoy: Starting ", *e)

	e.lds = createLDSServer(ldsPath, accessLogPath)
	e.rds = createRDSServer(rdsPath, e.lds)
	e.rds.run()
	e.lds.run(e.rds)

	// make it a buffered channel so we can not only
	// read the written value but also skip it in
	// case no one reader reads it.
	started := make(chan bool, 1)
	go func() {
		var logFile *os.File
		var err error
		for {
			// Open log file
			// TODO: log rotation!
			if logFile == nil {
				logFile, err = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
				if err != nil {
					log.WithError(err).Warn("Envoy: Can not open log file ", logPath)
				}
			}

			name := "cilium-envoy"
			logLevel := envoyLevelMap[log.Level]
			if logLevel == "" {
				logLevel = envoyLevelMap[logrus.InfoLevel]
			}
			e.cmd = exec.Command(name, "-l", logLevel, "-c", bootstrapPath, "--base-id", strconv.FormatUint(baseID, 10))
			e.cmd.Stderr = logFile
			e.cmd.Stdout = logFile

			err = e.cmd.Start()
			if err != nil {
				log.WithError(err).Warn("Envoy: failed to start.")
				select {
				case started <- false:
				default:
				}
				return
			}
			log.Debugf("Envoy: Started.")
			select {
			case started <- true:
			default:
			}

			log.Info("Envoy: Process started at pid ", e.cmd.Process.Pid)

			// We do not return after a successful start, but watch the Envoy process
			// and restart it if it crashes.
			err = e.cmd.Wait()
			if err != nil {
				log.WithError(err).Warn("Envoy: Execution failed.")
			}
			if logFile != nil {
				logFile.Close()
				logFile = nil
			}
			// start again after a short wait. If Cilium exits this should be enough
			// time to not start Envoy again in that case.
			log.WithError(err).Info("Envoy: Sleeping for 100ms before respawning.")
			time.Sleep(100 * time.Millisecond)
		}
	}()

	if <-started {
		return e
	}

	return nil
}

// isEOF returns true if the error message ends in "EOF". ReadMsgUnix returns extra info in the beginning.
func isEOF(err error) bool {
	strerr := err.Error()
	errlen := len(strerr)
	return errlen >= 3 && strerr[errlen-3:] == io.EOF.Error()
}

func (e *Envoy) startAccesslogServer(accessLogPath string) {
	// Create the access log listener
	os.Remove(accessLogPath) // Remove/Unlink the old unix domain socket, if any.
	var err error
	e.accessLogListener, err = net.ListenUnix("unixpacket", &net.UnixAddr{Name: accessLogPath, Net: "unixpacket"})
	if err != nil {
		log.WithError(err).Fatal("Envoy: Failed to listen at ", accessLogPath)
	}
	e.accessLogListener.SetUnlinkOnClose(true)

	go func() {
		for {
			// Each Envoy listener opens a new connection over the Unix domain socket.
			// Multiple worker threads serving the listener share that same connection
			uc, err := e.accessLogListener.AcceptUnix()
			if err != nil {
				// These errors are expected when we are closing down
				if !strings.Contains(err.Error(), "closed network connection") &&
					!strings.Contains(err.Error(), "invalid argument") {
					log.WithError(err).Error("AcceptUnix failed")
				}
				break
			}
			log.Info("Envoy: Access log connection opened")
			e.accessLogger(uc)
		}
	}()
}

func (e *Envoy) accessLogger(conn *net.UnixConn) {
	defer func() {
		log.Info("Envoy: Access log closing")
		conn.Close()
	}()

	buf := make([]byte, 4096)
	for {
		n, _, flags, _, err := conn.ReadMsgUnix(buf, nil)
		if err != nil {
			if !isEOF(err) {
				log.WithError(err).Error("Envoy: Access log read error")
			}
			break
		}
		if flags&syscall.MSG_TRUNC != 0 {
			log.Warning("Envoy: Truncated access log message discarded.")
			continue
		}
		pblog := HttpLogEntry{}
		err = proto.Unmarshal(buf[:n], &pblog)
		if err != nil {
			log.WithError(err).Warning("Envoy: Invalid accesslog.proto HttpLogEntry message.")
			continue
		}

		// Correlate the log entry with a listener
		l := e.lds.findListener(pblog.CiliumResourceName)

		// Call the logger.
		if l != nil {
			l.logger.Log(&pblog)
		} else {
			log.Infof("Envoy: Orphan Access log message for %s: %s", pblog.CiliumResourceName, pblog.String())
		}
	}
}

// StopEnvoy kills the Envoy process started with StartEnvoy. The gRPC API streams are terminated
// first.
func (e *Envoy) StopEnvoy() error {
	// Close the access log server
	if e.accessLogListener != nil {
		e.accessLogListener.Close()
		e.accessLogListener = nil
	}
	log.Info("Envoy: Stopping process ", e.cmd.Process.Pid)
	e.rds.stop()
	e.lds.stop()
	err := e.admin.quit()
	if err != nil {
		log.WithError(err).Fatal("Envoy: Admin quit failed, killing process ", e.cmd.Process.Pid)
		err := e.cmd.Process.Kill()
		if err != nil {
			log.WithError(err).Fatal("Envoy: Stopping failed")
			return err
		}
	}
	return nil
}

// AddListener adds a listener to a running Envoy proxy.
func (e *Envoy) AddListener(name string, port uint16, l7rules policy.L7DataMap, isIngress bool, logger Logger, wg *completion.WaitGroup) {
	e.lds.addListener(name, port, l7rules, isIngress, logger, wg)
}

// UpdateListener changes to the L7 rules of an existing Envoy Listener.
func (e *Envoy) UpdateListener(name string, l7rules policy.L7DataMap, wg *completion.WaitGroup) {
	e.lds.updateListener(name, l7rules, wg)
}

// RemoveListener removes an existing Envoy Listener.
func (e *Envoy) RemoveListener(name string, wg *completion.WaitGroup) {
	e.lds.removeListener(name, wg)
}

// ChangeLogLevel changes Envoy log level to correspond to the logrus log level 'level'.
func (e *Envoy) ChangeLogLevel(level logrus.Level) {
	e.admin.changeLogLevel(level)
}
<commit_msg>pkg/envoy: Move all operations on the cmd to StartEnvoy<commit_after>package envoy

import (
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/cilium/cilium/pkg/completion"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/policy"

	"github.com/golang/protobuf/proto"
	"github.com/sirupsen/logrus"
)

var log = logging.DefaultLogger

var (
	// envoyLevelMap maps logrus.Level values to Envoy (spdlog) log levels.
	envoyLevelMap = map[logrus.Level]string{
		logrus.PanicLevel: "off",
		logrus.FatalLevel: "critical",
		logrus.ErrorLevel: "error",
		logrus.WarnLevel:  "warning",
		logrus.InfoLevel:  "info",
		logrus.DebugLevel: "debug",
		// spdlog "trace" not mapped
	}
)

type admin struct {
	adminURL string
	level    string
}

func (a *admin) transact(query string) error {
	resp, err := http.Get(a.adminURL + query)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	ret := strings.Replace(string(body), "\r", "", -1)
	log.Debug("Envoy admin response to " + query + ": " + ret)
	return nil
}

func (a *admin) changeLogLevel(level logrus.Level) error {
	envoyLevel := envoyLevelMap[level]
	if envoyLevel != a.level {
		err := a.transact("logging?level=" + envoyLevel)
		if err != nil {
			log.WithError(err).Warn("Envoy: Failed setting log level: ", envoyLevel)
		} else {
			a.level = envoyLevel
		}
		return err
	}
	log.Debug("Envoy log level is already set as: " + envoyLevel)
	return nil
}

func (a *admin) quit() error {
	return a.transact("quitquitquit")
}

// Envoy manages a running Envoy proxy instance via the
// ListenerDiscoveryService and RouteDiscoveryService gRPC APIs.
type Envoy struct {
	stopCh            chan struct{}
	errCh             chan error
	LogPath           string
	AccessLogPath     string
	accessLogListener *net.UnixListener
	ldsSock           string
	lds               *LDSServer
	rdsSock           string
	rds               *RDSServer
	admin             *admin
}

// Logger is used to feed access log entires from Envoy to cilium access log.
type Logger interface {
	Log(entry *HttpLogEntry)
}

// GetEnvoyVersion returns the envoy binary version string
func GetEnvoyVersion() string {
	out, err := exec.Command("cilium-envoy", "--version").Output()
	if err != nil {
		log.WithError(err).Fatal(`Envoy binary "cilium-envoy" cannot be executed`)
	}
	return strings.TrimSpace(string(out))
}

// StartEnvoy starts an Envoy proxy instance.
func StartEnvoy(adminPort uint32, stateDir, logDir string, baseID uint64) *Envoy {
	bootstrapPath := filepath.Join(stateDir, "bootstrap.pb")
	logPath := filepath.Join(logDir, "cilium-envoy.log")
	adminAddress := "127.0.0.1:" + strconv.FormatUint(uint64(adminPort), 10)
	ldsPath := filepath.Join(stateDir, "lds.sock")
	rdsPath := filepath.Join(stateDir, "rds.sock")
	accessLogPath := filepath.Join(stateDir, "access_log.sock")

	e := &Envoy{
		stopCh:        make(chan struct{}),
		errCh:         make(chan error, 1),
		LogPath:       logPath,
		AccessLogPath: accessLogPath,
		ldsSock:       ldsPath,
		rdsSock:       rdsPath,
		admin:         &admin{adminURL: "http://" + adminAddress + "/"},
	}

	// Create static configuration
	createBootstrap(bootstrapPath, "envoy1", "cluster1", "version1",
		"ldsCluster", ldsPath, "rdsCluster", rdsPath, "cluster1", adminPort)

	e.startAccesslogServer(accessLogPath)

	log.Debug("Envoy: Starting ", *e)

	e.lds = createLDSServer(ldsPath, accessLogPath)
	e.rds = createRDSServer(rdsPath, e.lds)
	e.rds.run()
	e.lds.run(e.rds)

	// make it a buffered channel so we can not only
	// read the written value but also skip it in
	// case no one reader reads it.
	started := make(chan bool, 1)
	go func() {
		var logFile *os.File
		var err error
		for {
			// Open log file
			// TODO: log rotation!
			if logFile == nil {
				logFile, err = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
				if err != nil {
					log.WithError(err).Warn("Envoy: Can not open log file ", logPath)
				}
			}

			name := "cilium-envoy"
			logLevel := envoyLevelMap[log.Level]
			if logLevel == "" {
				logLevel = envoyLevelMap[logrus.InfoLevel]
			}

			cmd := exec.Command(name, "-l", logLevel, "-c", bootstrapPath, "--base-id", strconv.FormatUint(baseID, 10))
			cmd.Stderr = logFile
			cmd.Stdout = logFile

			if err := cmd.Start(); err != nil {
				log.WithError(err).Warn("Envoy: failed to start.")
				select {
				case started <- false:
				default:
				}
				return
			}
			log.Debugf("Envoy: Started.")
			select {
			case started <- true:
			default:
			}

			log.Info("Envoy: Process started at pid ", cmd.Process.Pid)

			// We do not return after a successful start, but watch the Envoy process
			// and restart it if it crashes.
			// Waiting for the process execution is done in the goroutime.
			// The purpose of the "crash channel" is to inform the loop about their
			// Envoy process crash - after closing that channel by the goroutime,
			// the loop continues, the channel is recreated and the new process
			// is watched again.
			crashCh := make(chan struct{})
			go func() {
				if err := cmd.Wait(); err != nil {
					log.WithError(err).Warn("Envoy: Execution failed: ", err)
				}
				close(crashCh)
			}()

			if logFile != nil {
				logFile.Close()
				logFile = nil
			}
			// start again after a short wait. If Cilium exits this should be enough
			// time to not start Envoy again in that case.
			log.WithError(err).Info("Envoy: Sleeping for 100ms before respawning.")
			time.Sleep(100 * time.Millisecond)

			select {
			case <-crashCh:
				// Start Envoy again
				continue
			case <-e.stopCh:
				// Close the access log server
				if e.accessLogListener != nil {
					e.accessLogListener.Close()
					e.accessLogListener = nil
				}
				log.Info("Envoy: Stopping process ", cmd.Process.Pid)
				e.rds.stop()
				e.lds.stop()
				if err := e.admin.quit(); err != nil {
					log.WithError(err).Fatal("Envoy: Admin quit failed, killing process ", cmd.Process.Pid)

					if err := cmd.Process.Kill(); err != nil {
						log.WithError(err).Fatal("Envoy: Stopping failed")
						e.errCh <- err
					}
				}
				close(e.errCh)
				return
			}
		}
	}()

	if <-started {
		return e
	}

	return nil
}

// isEOF returns true if the error message ends in "EOF". ReadMsgUnix returns extra info in the beginning.
func isEOF(err error) bool {
	strerr := err.Error()
	errlen := len(strerr)
	return errlen >= 3 && strerr[errlen-3:] == io.EOF.Error()
}

func (e *Envoy) startAccesslogServer(accessLogPath string) {
	// Create the access log listener
	os.Remove(accessLogPath) // Remove/Unlink the old unix domain socket, if any.
	var err error
	e.accessLogListener, err = net.ListenUnix("unixpacket", &net.UnixAddr{Name: accessLogPath, Net: "unixpacket"})
	if err != nil {
		log.WithError(err).Fatal("Envoy: Failed to listen at ", accessLogPath)
	}
	e.accessLogListener.SetUnlinkOnClose(true)

	go func() {
		for {
			// Each Envoy listener opens a new connection over the Unix domain socket.
			// Multiple worker threads serving the listener share that same connection
			uc, err := e.accessLogListener.AcceptUnix()
			if err != nil {
				// These errors are expected when we are closing down
				if !strings.Contains(err.Error(), "closed network connection") &&
					!strings.Contains(err.Error(), "invalid argument") {
					log.WithError(err).Error("AcceptUnix failed")
				}
				break
			}
			log.Info("Envoy: Access log connection opened")
			e.accessLogger(uc)
		}
	}()
}

func (e *Envoy) accessLogger(conn *net.UnixConn) {
	defer func() {
		log.Info("Envoy: Access log closing")
		conn.Close()
	}()

	buf := make([]byte, 4096)
	for {
		n, _, flags, _, err := conn.ReadMsgUnix(buf, nil)
		if err != nil {
			if !isEOF(err) {
				log.WithError(err).Error("Envoy: Access log read error")
			}
			break
		}
		if flags&syscall.MSG_TRUNC != 0 {
			log.Warning("Envoy: Truncated access log message discarded.")
			continue
		}
		pblog := HttpLogEntry{}
		err = proto.Unmarshal(buf[:n], &pblog)
		if err != nil {
			log.WithError(err).Warning("Envoy: Invalid accesslog.proto HttpLogEntry message.")
			continue
		}

		// Correlate the log entry with a listener
		l := e.lds.findListener(pblog.CiliumResourceName)

		// Call the logger.
		if l != nil {
			l.logger.Log(&pblog)
		} else {
			log.Infof("Envoy: Orphan Access log message for %s: %s", pblog.CiliumResourceName, pblog.String())
		}
	}
}

// StopEnvoy kills the Envoy process started with StartEnvoy. The gRPC API streams are terminated
// first.
func (e *Envoy) StopEnvoy() error {
	close(e.stopCh)
	err, ok := <-e.errCh
	if ok {
		return err
	}
	return nil
}

// AddListener adds a listener to a running Envoy proxy.
func (e *Envoy) AddListener(name string, port uint16, l7rules policy.L7DataMap, isIngress bool, logger Logger, wg *completion.WaitGroup) {
	e.lds.addListener(name, port, l7rules, isIngress, logger, wg)
}

// UpdateListener changes to the L7 rules of an existing Envoy Listener.
func (e *Envoy) UpdateListener(name string, l7rules policy.L7DataMap, wg *completion.WaitGroup) {
	e.lds.updateListener(name, l7rules, wg)
}

// RemoveListener removes an existing Envoy Listener.
func (e *Envoy) RemoveListener(name string, wg *completion.WaitGroup) {
	e.lds.removeListener(name, wg)
}

// ChangeLogLevel changes Envoy log level to correspond to the logrus log level 'level'.
func (e *Envoy) ChangeLogLevel(level logrus.Level) {
	e.admin.changeLogLevel(level)
}
<|endoftext|>


<commit_before>package host

import (
	"path/filepath"
	"strings"
)

// ZipURL returns the URL of the zip archive given a github repository URL.
func ZipURL(url string) string {
	url = strings.TrimSuffix(strings.TrimPrefix(url, "/"), "/")

	if strings.HasSuffix(url, "zip/master") {
		return url
	}

	return "https://codeload.github.com/" + filepath.Join(url, "/zip/master")
}
<commit_msg>Allowing a version/branch string to be parsed in download url (#18)<commit_after>package host

import (
	"path/filepath"
	"regexp"
	"strings"
)

// ZipURL returns the URL of the zip archive given a github repository URL.
func ZipURL(url string) string {
	var version = "master"

	url = strings.TrimSuffix(strings.TrimPrefix(url, "/"), "/")

	zipRegex, _ := regexp.Compile(`zip/(\S+)$`)
	if zipRegex.MatchString(url) {
		return url
	}

	// So this could identify a port number, but since we only support github
	// I don't believe using it as a version modifier is a problem. Though
	// perhaps we should use something else instead?
	if strings.Contains(url, ":") {
		parts := strings.SplitAfter(url, ":")

		url = parts[0]
		version = parts[1]

		url = strings.TrimSuffix(url, ":")
	}

	return "https://codeload.github.com/" + filepath.Join(url, "/zip/"+version)
}
<|endoftext|>


<commit_before>package net

import (
	"context"
	"crypto/rand"
	"crypto/tls"
	"errors"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	igd "github.com/emersion/go-upnp-igd"
	"go.uber.org/zap"

	"nimona.io/internal/log"
	"nimona.io/pkg/crypto"
	"nimona.io/pkg/discovery"
	"nimona.io/pkg/net/peer"
)

var (
	UseUPNP = false
)

func init() {
	UseUPNP, _ = strconv.ParseBool(os.Getenv("UPNP"))
}

// Network interface
type Network interface {
	Dial(ctx context.Context, address string) (*Connection, error)
	Listen(ctx context.Context, addrress string) (chan *Connection, error)

	GetPeerInfo() *peer.PeerInfo
	AttachMandate(m *crypto.Mandate) error
	AddMiddleware(handler MiddlewareHandler)
}

// New creates a new p2p network using an address book
func New(hostname string, discover discovery.Discoverer, local *LocalInfo,
	relayAddresses []string) (Network, error) {

	return &network{
		discoverer:     discover,
		hostname:       hostname,
		relayAddresses: relayAddresses,
		middleware:     []MiddlewareHandler{},
		local:          local,
	}, nil
}

// network allows dialing and listening for p2p connections
type network struct {
	discoverer     discovery.Discoverer
	hostname       string
	relayAddresses []string
	local          *LocalInfo
	middleware     []MiddlewareHandler
}

func (n *network) AddMiddleware(handler MiddlewareHandler) {
	n.middleware = append(n.middleware, handler)
}

// Dial to a peer and return a net.Conn or error
func (n *network) Dial(ctx context.Context, address string) (
	*Connection, error) {
	logger := log.Logger(ctx)

	addressType := strings.Split(address, ":")[0]
	switch addressType {
	case "peer":
		return n.dialPeer(ctx, address)
	case "tcps":
		return n.dialAddress(ctx, address)
	default:
		logger.Info("not sure how to dial",
			zap.String("address", address),
			zap.String("type", addressType))
	}

	return nil, ErrNoAddresses
}

// Listen on an address
// TODO do we need to return a listener?
func (n *network) Listen(ctx context.Context, address string) (chan *Connection, error) {
	logger := log.Logger(ctx).Named("network")
	cert, err := crypto.GenerateCertificate(n.local.GetPeerKey())
	if err != nil {
		return nil, err
	}

	now := time.Now()
	config := tls.Config{
		Certificates: []tls.Certificate{*cert},
	}
	config.NextProtos = []string{"nimona/1"} // TODO(geoah) is this of any actual use?
	config.Time = func() time.Time { return now }
	config.Rand = rand.Reader
	tcpListener, err := tls.Listen("tcp", address, &config)
	if err != nil {
		return nil, err
	}

	port := tcpListener.Addr().(*net.TCPAddr).Port
	logger.Info("Listening and service nimona", zap.Int("port", port))
	addresses := GetAddresses(tcpListener)
	devices := make(chan igd.Device, 10)

	if n.hostname != "" {
		addresses = append(addresses, fmtAddress(n.hostname, port))
	}

	if UseUPNP {
		logger.Info("Trying to find external IP and open port")
		go func() {
			if err := igd.Discover(devices, 2*time.Second); err != nil {
				logger.Error("could not discover devices", zap.Error(err))
			}
		}()
		for device := range devices {
			externalAddress, err := device.GetExternalIPAddress()
			if err != nil {
				logger.Error("could not get external ip", zap.Error(err))
				continue
			}
			desc := "nimona"
			ttl := time.Hour * 24 * 365
			if _, err := device.AddPortMapping(igd.TCP, port, port, desc, ttl); err != nil {
				logger.Error("could not add port mapping", zap.Error(err))
			} else {
				addresses = append(addresses, fmtAddress(externalAddress.String(), port))
			}
		}
	}

	logger.Info("Started listening", zap.Strings("addresses", addresses))
	n.local.AddAddress(addresses...)
	n.local.AddAddress(n.relayAddresses...)

	cconn := make(chan *Connection, 10)
	go func() {

		for {
			tcpConn, err := tcpListener.Accept()
			if err != nil {
				log.DefaultLogger.Warn(
					"could not accept connection", zap.Error(err))
				// TODO close conn?
				return
			}

			conn := &Connection{
				Conn:          tcpConn,
				RemotePeerKey: nil,
				IsIncoming:    true,
			}

			for _, mh := range n.middleware {
				conn, err = mh(ctx, conn)
			if err != nil {
					log.DefaultLogger.Error(
						"middleware failure", zap.Error((err)))
			}
			}

			cconn <- conn
		}
	}()

	return cconn, nil
}

// GetPeerInfo returns the local peer info
func (n *network) GetPeerInfo() *peer.PeerInfo {
	// TODO cache peer info and reuse
	return n.local.GetPeerInfo()
}

func (n *network) AttachMandate(m *crypto.Mandate) error {
	return n.local.AttachMandate(m)
}

func (n *network) dialPeer(ctx context.Context, address string) (
	*Connection, error) {
	logger := log.Logger(ctx)

	peerID := strings.Replace(address, "peer:", "", 1)
	if peerID == n.local.GetPeerKey().GetPublicKey().HashBase58() {
		return nil, errors.New("cannot dial our own peer")
	}
	logger.Debug("dialing peer", zap.String("peer", address))
	q := &peer.PeerInfoRequest{
		SignerKeyHash: peerID,
	}
	ps, err := n.discoverer.Discover(q)
	if err != nil {
		return nil, err
	}

	if len(ps) == 0 || len(ps[0].Addresses) == 0 {
		return nil, ErrNoAddresses
	}

	// TODO we should probably try all results
	for _, addr := range ps[0].Addresses {
		conn, err := n.Dial(ctx, addr)
		if err == nil {
			return conn, nil
		}
	}

	return nil, ErrAllAddressesFailed

}

func (n *network) dialAddress(ctx context.Context, address string) (
	*Connection, error) {

	// find dialer
	// execute middleware
	config := tls.Config{
		InsecureSkipVerify: true,
	}
	addr := strings.Replace(address, "tcps:", "", 1)
	dialer := net.Dialer{Timeout: time.Second}

	tcpConn, err := tls.DialWithDialer(&dialer, "tcp", addr, &config)
	if err != nil {
		return nil, err
	}

	if tcpConn == nil {
		return nil, ErrAllAddressesFailed
	}

	conn := &Connection{
		Conn:          tcpConn,
		RemotePeerKey: nil, // we don't really know who the other side is
	}

	for _, mh := range n.middleware {
		conn, err = mh(ctx, conn)
		if err != nil {
			return nil, err
		}
	}

	return conn, nil
}
<commit_msg>Rename dialAddress to dialTCP<commit_after>package net

import (
	"context"
	"crypto/rand"
	"crypto/tls"
	"errors"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	igd "github.com/emersion/go-upnp-igd"
	"go.uber.org/zap"

	"nimona.io/internal/log"
	"nimona.io/pkg/crypto"
	"nimona.io/pkg/discovery"
	"nimona.io/pkg/net/peer"
)

var (
	UseUPNP = false
)

func init() {
	UseUPNP, _ = strconv.ParseBool(os.Getenv("UPNP"))
}

// Network interface
type Network interface {
	Dial(ctx context.Context, address string) (*Connection, error)
	Listen(ctx context.Context, addrress string) (chan *Connection, error)

	GetPeerInfo() *peer.PeerInfo
	AttachMandate(m *crypto.Mandate) error
	AddMiddleware(handler MiddlewareHandler)
}

// New creates a new p2p network using an address book
func New(hostname string, discover discovery.Discoverer, local *LocalInfo,
	relayAddresses []string) (Network, error) {

	return &network{
		discoverer:     discover,
		hostname:       hostname,
		relayAddresses: relayAddresses,
		middleware:     []MiddlewareHandler{},
		local:          local,
	}, nil
}

// network allows dialing and listening for p2p connections
type network struct {
	discoverer     discovery.Discoverer
	hostname       string
	relayAddresses []string
	local          *LocalInfo
	middleware     []MiddlewareHandler
}

func (n *network) AddMiddleware(handler MiddlewareHandler) {
	n.middleware = append(n.middleware, handler)
}

// Dial to a peer and return a net.Conn or error
func (n *network) Dial(ctx context.Context, address string) (
	*Connection, error) {
	logger := log.Logger(ctx)

	addressType := strings.Split(address, ":")[0]
	switch addressType {
	case "peer":
		return n.dialPeer(ctx, address)
	case "tcps":
		return n.dialTCP(ctx, address)
	default:
		logger.Info("not sure how to dial",
			zap.String("address", address),
			zap.String("type", addressType))
	}

	return nil, ErrNoAddresses
}

// Listen on an address
// TODO do we need to return a listener?
func (n *network) Listen(ctx context.Context, address string) (chan *Connection, error) {
	logger := log.Logger(ctx).Named("network")
	cert, err := crypto.GenerateCertificate(n.local.GetPeerKey())
	if err != nil {
		return nil, err
	}

	now := time.Now()
	config := tls.Config{
		Certificates: []tls.Certificate{*cert},
	}
	config.NextProtos = []string{"nimona/1"} // TODO(geoah) is this of any actual use?
	config.Time = func() time.Time { return now }
	config.Rand = rand.Reader
	tcpListener, err := tls.Listen("tcp", address, &config)
	if err != nil {
		return nil, err
	}

	port := tcpListener.Addr().(*net.TCPAddr).Port
	logger.Info("Listening and service nimona", zap.Int("port", port))
	addresses := GetAddresses(tcpListener)
	devices := make(chan igd.Device, 10)

	if n.hostname != "" {
		addresses = append(addresses, fmtAddress(n.hostname, port))
	}

	if UseUPNP {
		logger.Info("Trying to find external IP and open port")
		go func() {
			if err := igd.Discover(devices, 2*time.Second); err != nil {
				logger.Error("could not discover devices", zap.Error(err))
			}
		}()
		for device := range devices {
			externalAddress, err := device.GetExternalIPAddress()
			if err != nil {
				logger.Error("could not get external ip", zap.Error(err))
				continue
			}
			desc := "nimona"
			ttl := time.Hour * 24 * 365
			if _, err := device.AddPortMapping(igd.TCP, port, port, desc, ttl); err != nil {
				logger.Error("could not add port mapping", zap.Error(err))
			} else {
				addresses = append(addresses, fmtAddress(externalAddress.String(), port))
			}
		}
	}

	logger.Info("Started listening", zap.Strings("addresses", addresses))
	n.local.AddAddress(addresses...)
	n.local.AddAddress(n.relayAddresses...)

	cconn := make(chan *Connection, 10)
	go func() {

		for {
			tcpConn, err := tcpListener.Accept()
			if err != nil {
				log.DefaultLogger.Warn(
					"could not accept connection", zap.Error(err))
				// TODO close conn?
				return
			}

			conn := &Connection{
				Conn:          tcpConn,
				RemotePeerKey: nil,
				IsIncoming:    true,
			}

			for _, mh := range n.middleware {
				conn, err = mh(ctx, conn)
			if err != nil {
					log.DefaultLogger.Error(
						"middleware failure", zap.Error((err)))
			}
			}

			cconn <- conn
		}
	}()

	return cconn, nil
}

// GetPeerInfo returns the local peer info
func (n *network) GetPeerInfo() *peer.PeerInfo {
	// TODO cache peer info and reuse
	return n.local.GetPeerInfo()
}

func (n *network) AttachMandate(m *crypto.Mandate) error {
	return n.local.AttachMandate(m)
}

func (n *network) dialPeer(ctx context.Context, address string) (
	*Connection, error) {
	logger := log.Logger(ctx)

	peerID := strings.Replace(address, "peer:", "", 1)
	if peerID == n.local.GetPeerKey().GetPublicKey().HashBase58() {
		return nil, errors.New("cannot dial our own peer")
	}
	logger.Debug("dialing peer", zap.String("peer", address))
	q := &peer.PeerInfoRequest{
		SignerKeyHash: peerID,
	}
	ps, err := n.discoverer.Discover(q)
	if err != nil {
		return nil, err
	}

	if len(ps) == 0 || len(ps[0].Addresses) == 0 {
		return nil, ErrNoAddresses
	}

	// TODO we should probably try all results
	for _, addr := range ps[0].Addresses {
		conn, err := n.Dial(ctx, addr)
		if err == nil {
			return conn, nil
		}
	}

	return nil, ErrAllAddressesFailed

}

func (n *network) dialTCP(ctx context.Context, address string) (
	*Connection, error) {

	// find dialer
	// execute middleware
	config := tls.Config{
		InsecureSkipVerify: true,
	}
	addr := strings.Replace(address, "tcps:", "", 1)
	dialer := net.Dialer{Timeout: time.Second}

	tcpConn, err := tls.DialWithDialer(&dialer, "tcp", addr, &config)
	if err != nil {
		return nil, err
	}

	if tcpConn == nil {
		return nil, ErrAllAddressesFailed
	}

	conn := &Connection{
		Conn:          tcpConn,
		RemotePeerKey: nil, // we don't really know who the other side is
	}

	for _, mh := range n.middleware {
		conn, err = mh(ctx, conn)
		if err != nil {
			return nil, err
		}
	}

	return conn, nil
}
<|endoftext|>


<commit_before>package proxy

import (
	"math/rand"
	"sync"
	"time"

	"github.com/fagongzi/goetty"
	"github.com/fagongzi/log"
	"github.com/fagongzi/netproxy/pkg/conf"
	"github.com/labstack/echo"
)

// Proxy proxy
type Proxy struct {
	sync.RWMutex
	cnf       *conf.Conf
	apiServer *echo.Echo
	servers   map[string]*TCPServer
}

// NewProxy factory method
func NewProxy(cnf *conf.Conf) *Proxy {
	return &Proxy{
		cnf:       cnf,
		apiServer: echo.New(),
		servers:   make(map[string]*TCPServer),
	}
}

// Start start server
func (p *Proxy) Start() {
	for index, proxyUnit := range p.cnf.Units {
		go func(proxyUnit *conf.ProxyUnit, index int) {
			server := &TCPServer{
				proxyUnit: proxyUnit,
				p:         p,
			}
			p.servers[proxyUnit.Src] = server
			server.start()
		}(proxyUnit, index)
	}

	p.startAPIServer()
}

// Pause pause proxy listen
func (p *Proxy) Pause(addr string) {
	for _, server := range p.servers {
		if addr == server.proxyUnit.Src {
			server.pause()
		}
	}
}

// Resume resume proxy listen
func (p *Proxy) Resume(addr string) {
	for _, server := range p.servers {
		if addr == server.proxyUnit.Src {
			server.resume()
		}
	}
}

// Stop stop server
func (p *Proxy) Stop() {
	for _, server := range p.servers {
		server.stop()
	}
}

// UpdateCtl UpdateCtl
func (p *Proxy) UpdateCtl(ctl *conf.Ctl) {
	p.Lock()
	p.servers[ctl.Address].proxyUnit.Ctl.CopyFrom(ctl)
	p.Unlock()
}

// TCPServer TCPServer
type TCPServer struct {
	sync.RWMutex
	proxyUnit *conf.ProxyUnit
	p         *Proxy
	server    *goetty.Server
	paused    bool
}

func (t *TCPServer) start() {
	log.Infof("proxy <%s> to <%s>", t.proxyUnit.Src, t.proxyUnit.Target)
	t.server = goetty.NewServer(t.proxyUnit.Src, DECODER, ENCODER, goetty.NewInt64IDGenerator())
	t.server.Start(t.doServe)
}

func (t *TCPServer) stop() {
	t.server.Stop()
}

func (t *TCPServer) pause() {
	t.Lock()
	if t.paused {
		t.Unlock()
		return
	}
	t.paused = true
	t.stop()
	t.Unlock()
}

func (t *TCPServer) resume() {
	t.Lock()
	if !t.paused {
		t.Unlock()
		return
	}
	t.paused = false
	go t.start()
	t.Unlock()
}

func (t *TCPServer) doServe(session goetty.IOSession) error {
	var err error

	// client connected, make a connection to target
	conn := goetty.NewConnector(t.createGoettyConf(), DECODER, ENCODER)
	_, err = conn.Connect()

	if err != nil {
		log.InfoErrorf(err, "Connect to <%s> failure.", t.proxyUnit.Target)
		return err
	}

	defer conn.Close()

	// read loop from target
	go func() {
		in := conn.InBuf()

		for {
			_, err := conn.Read()
			if err != nil {
				return
			}

			bytes := in.RawBuf()[in.GetReaderIndex():in.GetWriteIndex()]

			// write bytes to client
			ctl := t.proxyUnit.Ctl

			if 0 == ctl.In.LossRate {
				t.doWriteToClient(bytes, session, ctl.In)
			} else {
				if rand.Intn(100) > ctl.In.LossRate {
					t.doWriteToClient(bytes, session, ctl.In)
				} else {
					log.Infof("Loss write to <%s>", bytes, session.RemoteAddr())
				}
			}

			in.SetReaderIndex(in.GetWriteIndex())
		}
	}()

	in := session.InBuf()
	for {
		_, err = session.Read()
		if err != nil {
			log.InfoErrorf(err, "Read from client<%s> failure.", session.RemoteAddr())
			break
		} else {
			// write to target
			ctl := t.proxyUnit.Ctl
			bytes := in.RawBuf()[in.GetReaderIndex():in.GetWriteIndex()]
			if 0 == ctl.Out.LossRate {
				t.doWrite(bytes, conn, ctl.Out)
			} else {
				if rand.Intn(100) > ctl.Out.LossRate {
					t.doWrite(bytes, conn, ctl.Out)
				} else {
					log.Infof("Loss write <%+v> to <%s>", bytes, t.proxyUnit.Target)
				}
			}
		}

		in.SetReaderIndex(in.GetWriteIndex())
	}

	return err
}

func (t *TCPServer) createGoettyConf() *goetty.Conf {
	return &goetty.Conf{
		Addr: t.proxyUnit.Target,
		TimeoutConnectToServer: time.Second * time.Duration(t.proxyUnit.TimeoutConnect),
	}
}

func (t *TCPServer) doWrite(bytes []byte, conn goetty.IOSession, ctl *conf.CtlUnit) {
	if ctl.DelayMs > 0 {
		log.Infof("Delay <%d>ms write to <%s>", ctl.DelayMs, t.proxyUnit.Target)
		time.Sleep(time.Millisecond * time.Duration(ctl.DelayMs))
	}

	conn.Write(bytes)
}

func (t *TCPServer) doWriteToClient(bytes []byte, conn goetty.IOSession, ctl *conf.CtlUnit) {
	if ctl.DelayMs > 0 {
		log.Infof("Delay <%d>ms write to client<%s>", ctl.DelayMs, conn.RemoteAddr())
		time.Sleep(time.Millisecond * time.Duration(ctl.DelayMs))
	}

	conn.Write(bytes)
}
<commit_msg>fix compile error<commit_after>package proxy

import (
	"math/rand"
	"sync"
	"time"

	"github.com/fagongzi/goetty"
	"github.com/fagongzi/log"
	"github.com/fagongzi/netproxy/pkg/conf"
	"github.com/labstack/echo"
)

// Proxy proxy
type Proxy struct {
	sync.RWMutex
	cnf       *conf.Conf
	apiServer *echo.Echo
	servers   map[string]*TCPServer
}

// NewProxy factory method
func NewProxy(cnf *conf.Conf) *Proxy {
	return &Proxy{
		cnf:       cnf,
		apiServer: echo.New(),
		servers:   make(map[string]*TCPServer),
	}
}

// Start start server
func (p *Proxy) Start() {
	for index, proxyUnit := range p.cnf.Units {
		go func(proxyUnit *conf.ProxyUnit, index int) {
			server := &TCPServer{
				proxyUnit: proxyUnit,
				p:         p,
			}
			p.servers[proxyUnit.Src] = server
			server.start()
		}(proxyUnit, index)
	}

	p.startAPIServer()
}

// Pause pause proxy listen
func (p *Proxy) Pause(addr string) {
	for _, server := range p.servers {
		if addr == server.proxyUnit.Src {
			server.pause()
		}
	}
}

// Resume resume proxy listen
func (p *Proxy) Resume(addr string) {
	for _, server := range p.servers {
		if addr == server.proxyUnit.Src {
			server.resume()
		}
	}
}

// Stop stop server
func (p *Proxy) Stop() {
	for _, server := range p.servers {
		server.stop()
	}
}

// UpdateCtl UpdateCtl
func (p *Proxy) UpdateCtl(ctl *conf.Ctl) {
	p.Lock()
	p.servers[ctl.Address].proxyUnit.Ctl.CopyFrom(ctl)
	p.Unlock()
}

// TCPServer TCPServer
type TCPServer struct {
	sync.RWMutex
	proxyUnit *conf.ProxyUnit
	p         *Proxy
	server    *goetty.Server
	paused    bool
}

func (t *TCPServer) start() {
	log.Infof("proxy <%s> to <%s>", t.proxyUnit.Src, t.proxyUnit.Target)
	t.server = goetty.NewServer(t.proxyUnit.Src, DECODER, ENCODER, goetty.NewInt64IDGenerator())
	t.server.Start(t.doServe)
}

func (t *TCPServer) stop() {
	t.server.Stop()
}

func (t *TCPServer) pause() {
	t.Lock()
	if t.paused {
		t.Unlock()
		return
	}
	t.paused = true
	t.stop()
	t.Unlock()
}

func (t *TCPServer) resume() {
	t.Lock()
	if !t.paused {
		t.Unlock()
		return
	}
	t.paused = false
	go t.start()
	t.Unlock()
}

func (t *TCPServer) doServe(session goetty.IOSession) error {
	var err error

	// client connected, make a connection to target
	conn := goetty.NewConnector(t.createGoettyConf(), DECODER, ENCODER)
	_, err = conn.Connect()

	if err != nil {
		log.Errorf("Connect to <%s> failure. err=%+v", t.proxyUnit.Target, err)
		return err
	}

	defer conn.Close()

	// read loop from target
	go func() {
		in := conn.InBuf()

		for {
			_, err := conn.Read()
			if err != nil {
				return
			}

			bytes := in.RawBuf()[in.GetReaderIndex():in.GetWriteIndex()]

			// write bytes to client
			ctl := t.proxyUnit.Ctl

			if 0 == ctl.In.LossRate {
				t.doWriteToClient(bytes, session, ctl.In)
			} else {
				if rand.Intn(100) > ctl.In.LossRate {
					t.doWriteToClient(bytes, session, ctl.In)
				} else {
					log.Infof("Loss write to <%s>", bytes, session.RemoteAddr())
				}
			}

			in.SetReaderIndex(in.GetWriteIndex())
		}
	}()

	in := session.InBuf()
	for {
		_, err = session.Read()
		if err != nil {
			log.Infof("Read from client<%s> failure.err=%+v", session.RemoteAddr(), err)
			break
		} else {
			// write to target
			ctl := t.proxyUnit.Ctl
			bytes := in.RawBuf()[in.GetReaderIndex():in.GetWriteIndex()]
			if 0 == ctl.Out.LossRate {
				t.doWrite(bytes, conn, ctl.Out)
			} else {
				if rand.Intn(100) > ctl.Out.LossRate {
					t.doWrite(bytes, conn, ctl.Out)
				} else {
					log.Infof("Loss write <%+v> to <%s>", bytes, t.proxyUnit.Target)
				}
			}
		}

		in.SetReaderIndex(in.GetWriteIndex())
	}

	return err
}

func (t *TCPServer) createGoettyConf() *goetty.Conf {
	return &goetty.Conf{
		Addr: t.proxyUnit.Target,
		TimeoutConnectToServer: time.Second * time.Duration(t.proxyUnit.TimeoutConnect),
	}
}

func (t *TCPServer) doWrite(bytes []byte, conn goetty.IOSession, ctl *conf.CtlUnit) {
	if ctl.DelayMs > 0 {
		log.Infof("Delay <%d>ms write to <%s>", ctl.DelayMs, t.proxyUnit.Target)
		time.Sleep(time.Millisecond * time.Duration(ctl.DelayMs))
	}

	conn.Write(bytes)
}

func (t *TCPServer) doWriteToClient(bytes []byte, conn goetty.IOSession, ctl *conf.CtlUnit) {
	if ctl.DelayMs > 0 {
		log.Infof("Delay <%d>ms write to client<%s>", ctl.DelayMs, conn.RemoteAddr())
		time.Sleep(time.Millisecond * time.Duration(ctl.DelayMs))
	}

	conn.Write(bytes)
}
<|endoftext|>


<commit_before>package store

import (
	"log"
	"os"
	"path"
	"strings"
)

type Event struct {
	Type uint
	Seqn uint64
	Path string
	Body string
}

const (
	Nop = 0
	Set = uint(1<<iota)
	Del
	Add
	Rem
)

var conj = map[uint]uint{Set:Add, Del:Rem}

var (
	BadPathError = os.NewError("bad path")
)

// This structure should be kept immutable.
type node struct {
	v string
	ds map[string]*node
}

var emptyNode = node{v:"", ds:make(map[string]*node)}

type Store struct {
	applyCh chan apply
	reqCh chan req
	watchCh chan watch
	watches map[string][]watch
	todo map[uint64]apply
	logger *log.Logger
}

type apply struct {
	seqn uint64
	op uint
	k string
	v string
}

type req struct {
	k string
	ch chan reply
}

type reply struct {
	v string
	ok bool
}

type watch struct {
	ch chan Event
	mask uint
	k string
}

func New(logger *log.Logger) *Store {
	s := &Store{
		applyCh: make(chan apply),
		reqCh: make(chan req),
		watchCh: make(chan watch),
		todo: make(map[uint64]apply),
		watches: make(map[string][]watch),
		logger: logger,
	}
	go s.process()
	return s
}

func (n node) readdir() string {
	names := make([]string, len(n.ds))
	i := 0
	for name, _ := range n.ds {
		names[i] = name + "\n"
		i++
	}
	return strings.Join(names, "")
}

func (n node) get(parts []string) (string, bool) {
	switch len(parts) {
	case 0:
		if len(n.ds) > 0 {
			return n.readdir(), true
		} else {
			return n.v, true
		}
	default:
		if m, ok := n.ds[parts[0]]; ok {
			return m.get(parts[1:])
		}
		return "", false
	}
	panic("can't happen")
}

func split(path string) []string {
	if path == "/" {
		return []string{}
	}
	return strings.Split(path[1:], "/", -1)
}

func join(parts []string) string {
	return "/" + strings.Join(parts, "/")
}

func (n node) getp(path string) (string, bool) {
	if err := checkPath(path); err != nil {
		return "", false
	}

	return n.get(split(path))
}

// Return value: y = replacement node; c = how many levels were changed.
func (n node) set(parts []string, v string, keep bool) (y *node, c int) {
	switch len(parts) {
	case 0:
		n.v = v
		y = &node{v:v, ds:n.ds}
	default:
		d := 0
		m, ok := n.ds[parts[0]]
		if m == nil {
			m = &emptyNode
		}
		m, d = m.set(parts[1:], v, keep)
		ds := make(map[string]*node)
		for k,v := range n.ds {
			ds[k] = v
		}
		ds[parts[0]] = m, m != nil
		if ok != (m != nil) {
			c = 1
		}
		y = &node{v:n.v, ds:ds}
		c += d
	}
	if !keep && len(y.ds) == 0 {
		y = nil
	}
	return
}

func (n node) setp(k, v string, keep bool) (y node, ps []string) {
	if err := checkPath(k); err != nil {
		return n, []string{}
	}

	r, c := n.set(split(k), v, keep)
	ps = make([]string, c)
	for i := 0; i < c; i++ {
		ps[i] = k
		d, _ := path.Split(k)
		k = d[0:len(d) - 1]
	}

	if r == nil {
		return emptyNode, ps
	}
	return *r, ps
}

func checkPath(k string) os.Error {
	switch {
	case len(k) < 1,
	     k[0] != '/',
	     len(k) > 1 && k[len(k) - 1] == '/',
	     strings.Count(k, "=") > 0,
	     strings.Count(k, " ") > 0:
		return BadPathError
	}
	return nil
}

func EncodeSet(path, body string) (mutation string, err os.Error) {
	if err = checkPath(path); err != nil {
		return
	}
	return path + "=" + body, nil
}

func EncodeDel(path string) (mutation string, err os.Error) {
	if err := checkPath(path); err != nil {
		return
	}
	return path, nil
}

func decode(mutation string) (op uint, path, v string, err os.Error) {
	parts := strings.Split(mutation, "=", 2)
	if err = checkPath(parts[0]); err != nil {
		return
	}
	switch len(parts) {
	case 1:
		return Del, parts[0], "", nil
	case 2:
		return Set, parts[0], parts[1], nil
	}
	panic("can't happen")
}

func (s *Store) notify(ev uint, seqn uint64, k, v string) {
	for _, w := range s.watches[k] {
		if w.mask & ev != 0 {
			w.ch <- Event{ev, seqn, k, v}
		}
	}
}

func append(ws *[]watch, w watch) {
	l := len(*ws)
	if l + 1 > cap(*ws) {
		ns := make([]watch, (l + 1)*2)
		copy(ns, *ws)
		*ws = ns
	}
	*ws = (*ws)[0:l + 1]
	(*ws)[l] = w
}

func (s *Store) process() {
	next := uint64(1)
	values := emptyNode
	for {
		select {
		case a := <-s.applyCh:
			if a.seqn >= next {
				s.todo[a.seqn] = a
			}
			for t, ok := s.todo[next]; ok; t, ok = s.todo[next] {
				if t.op != Nop {
					var changed []string
					go s.notify(t.op, t.seqn, t.k, t.v)
					values, changed = values.setp(t.k, t.v, t.op == Set)
					s.logger.Logf("applied %v", t)
					for _, p := range changed {
						dirname, basename := path.Split(p)
						if dirname != "/" {
							dirname = dirname[0:len(dirname) - 1] // strip slash
						}
						go s.notify(conj[t.op], t.seqn, dirname, basename)
					}
				}
				s.todo[next] = apply{}, false
				next++
			}
		case r := <-s.reqCh:
			v, ok := values.getp(r.k)
			r.ch <- reply{v, ok}
		case w := <-s.watchCh:
			watches := s.watches[w.k]
			append(&watches, w)
			s.watches[w.k] = watches
		}
	}
}

func (s *Store) Apply(seqn uint64, mutation string) {
	op, path, v, err := decode(mutation)
	if err != nil {
		s.applyCh <- apply{seqn:seqn} // nop
	} else {
		s.applyCh <- apply{seqn, op, path, v}
	}
}

// For a missing path, `ok == false`. Otherwise, it is `true`.
func (s *Store) Lookup(path string) (body string, ok bool) {
	ch := make(chan reply)
	s.reqCh <- req{path, ch}
	rep := <-ch
	return rep.v, rep.ok
}

// `mask` is one or more of `Set`, `Del`, `Add`, and `Rem`, bitwise OR-ed
// together.
func (s *Store) Watch(path string, mask uint) (events chan Event) {
	ch := make(chan Event)
	s.watchCh <- watch{ch, mask, path}
	return ch
}
<commit_msg>more reliable<commit_after>package store

import (
	"log"
	"os"
	"path"
	"strings"
)

type Event struct {
	Type uint
	Seqn uint64
	Path string
	Body string
}

const (
	Nop = 0
	Set = uint(1<<iota)
	Del
	Add
	Rem
)

var conj = map[uint]uint{Set:Add, Del:Rem}

var (
	BadPathError = os.NewError("bad path")
)

// This structure should be kept immutable.
type node struct {
	v string
	ds map[string]*node
}

var emptyNode = node{v:"", ds:make(map[string]*node)}

type Store struct {
	applyCh chan apply
	reqCh chan req
	watchCh chan watch
	watches map[string][]watch
	todo map[uint64]apply
	logger *log.Logger
}

type apply struct {
	seqn uint64
	op uint
	k string
	v string
}

type req struct {
	k string
	ch chan reply
}

type reply struct {
	v string
	ok bool
}

type watch struct {
	ch chan Event
	mask uint
	k string
	ready chan int
}

func New(logger *log.Logger) *Store {
	s := &Store{
		applyCh: make(chan apply),
		reqCh: make(chan req),
		watchCh: make(chan watch),
		todo: make(map[uint64]apply),
		watches: make(map[string][]watch),
		logger: logger,
	}
	go s.process()
	return s
}

func (n node) readdir() string {
	names := make([]string, len(n.ds))
	i := 0
	for name, _ := range n.ds {
		names[i] = name + "\n"
		i++
	}
	return strings.Join(names, "")
}

func (n node) get(parts []string) (string, bool) {
	switch len(parts) {
	case 0:
		if len(n.ds) > 0 {
			return n.readdir(), true
		} else {
			return n.v, true
		}
	default:
		if m, ok := n.ds[parts[0]]; ok {
			return m.get(parts[1:])
		}
		return "", false
	}
	panic("can't happen")
}

func split(path string) []string {
	if path == "/" {
		return []string{}
	}
	return strings.Split(path[1:], "/", -1)
}

func join(parts []string) string {
	return "/" + strings.Join(parts, "/")
}

func (n node) getp(path string) (string, bool) {
	if err := checkPath(path); err != nil {
		return "", false
	}

	return n.get(split(path))
}

// Return value: y = replacement node; c = how many levels were changed.
func (n node) set(parts []string, v string, keep bool) (y *node, c int) {
	switch len(parts) {
	case 0:
		n.v = v
		y = &node{v:v, ds:n.ds}
	default:
		d := 0
		m, ok := n.ds[parts[0]]
		if m == nil {
			m = &emptyNode
		}
		m, d = m.set(parts[1:], v, keep)
		ds := make(map[string]*node)
		for k,v := range n.ds {
			ds[k] = v
		}
		ds[parts[0]] = m, m != nil
		if ok != (m != nil) {
			c = 1
		}
		y = &node{v:n.v, ds:ds}
		c += d
	}
	if !keep && len(y.ds) == 0 {
		y = nil
	}
	return
}

func (n node) setp(k, v string, keep bool) (y node, ps []string) {
	if err := checkPath(k); err != nil {
		return n, []string{}
	}

	r, c := n.set(split(k), v, keep)
	ps = make([]string, c)
	for i := 0; i < c; i++ {
		ps[i] = k
		d, _ := path.Split(k)
		k = d[0:len(d) - 1]
	}

	if r == nil {
		return emptyNode, ps
	}
	return *r, ps
}

func checkPath(k string) os.Error {
	switch {
	case len(k) < 1,
	     k[0] != '/',
	     len(k) > 1 && k[len(k) - 1] == '/',
	     strings.Count(k, "=") > 0,
	     strings.Count(k, " ") > 0:
		return BadPathError
	}
	return nil
}

func EncodeSet(path, body string) (mutation string, err os.Error) {
	if err = checkPath(path); err != nil {
		return
	}
	return path + "=" + body, nil
}

func EncodeDel(path string) (mutation string, err os.Error) {
	if err := checkPath(path); err != nil {
		return
	}
	return path, nil
}

func decode(mutation string) (op uint, path, v string, err os.Error) {
	parts := strings.Split(mutation, "=", 2)
	if err = checkPath(parts[0]); err != nil {
		return
	}
	switch len(parts) {
	case 1:
		return Del, parts[0], "", nil
	case 2:
		return Set, parts[0], parts[1], nil
	}
	panic("can't happen")
}

func (s *Store) notify(t uint, seqn uint64, k, v string) {
	for _, w := range s.watches[k] {
		if w.mask & t != 0 {
			go func(ch chan Event, ev Event) {
				ch <- ev
			}(w.ch, Event{t, seqn, k, v})
		}
	}
}

func append(ws *[]watch, w watch) {
	l := len(*ws)
	if l + 1 > cap(*ws) {
		ns := make([]watch, (l + 1)*2)
		copy(ns, *ws)
		*ws = ns
	}
	*ws = (*ws)[0:l + 1]
	(*ws)[l] = w
}

func (s *Store) process() {
	next := uint64(1)
	values := emptyNode
	for {
		select {
		case a := <-s.applyCh:
			if a.seqn >= next {
				s.todo[a.seqn] = a
			}
			for t, ok := s.todo[next]; ok; t, ok = s.todo[next] {
				if t.op != Nop {
					var changed []string
					s.notify(t.op, t.seqn, t.k, t.v)
					values, changed = values.setp(t.k, t.v, t.op == Set)
					s.logger.Logf("applied %v", t)
					for _, p := range changed {
						dirname, basename := path.Split(p)
						if dirname != "/" {
							dirname = dirname[0:len(dirname) - 1] // strip slash
						}
						s.notify(conj[t.op], t.seqn, dirname, basename)
					}
				}
				s.todo[next] = apply{}, false
				next++
			}
		case r := <-s.reqCh:
			v, ok := values.getp(r.k)
			r.ch <- reply{v, ok}
		case w := <-s.watchCh:
			watches := s.watches[w.k]
			append(&watches, w)
			s.watches[w.k] = watches
			w.ready <- 1
		}
	}
}

func (s *Store) Apply(seqn uint64, mutation string) {
	op, path, v, err := decode(mutation)
	if err != nil {
		s.applyCh <- apply{seqn:seqn} // nop
	} else {
		s.applyCh <- apply{seqn, op, path, v}
	}
}

// For a missing path, `ok == false`. Otherwise, it is `true`.
func (s *Store) Lookup(path string) (body string, ok bool) {
	ch := make(chan reply)
	s.reqCh <- req{path, ch}
	rep := <-ch
	return rep.v, rep.ok
}

// `mask` is one or more of `Set`, `Del`, `Add`, and `Rem`, bitwise OR-ed
// together.
func (s *Store) Watch(path string, mask uint) (events chan Event) {
	ch := make(chan Event)
	ready := make(chan int)
	s.watchCh <- watch{ch, mask, path, ready}
	<-ready
	return ch
}
<|endoftext|>


<commit_before>package observers

import (
	"github.com/sirupsen/logrus"

	"github.com/ivan1993spb/snake-server/engine"
	"github.com/ivan1993spb/snake-server/objects/wall"
	"github.com/ivan1993spb/snake-server/world"
)

const ruinsFactor = 0.15

var dotsMaskOne = engine.NewDotsMask([][]uint8{{1}})

var ruins = []*engine.DotsMask{
	dotsMaskOne,
	engine.DotsMaskSquare2x2,
	engine.DotsMaskTank,
	engine.DotsMaskHome1,
	engine.DotsMaskHome2,
	engine.DotsMaskCross,
	engine.DotsMaskDiagonal,
	engine.DotsMaskCrossSmall,
	engine.DotsMaskDiagonalSmall,
	engine.DotsMaskLabyrinth,
	engine.DotsMaskTunnel1,
	engine.DotsMaskTunnel2,
	engine.DotsMaskBigHome,
}

func calcRuinsCount(size uint16) uint16 {
	return uint16(float32(size) * ruinsFactor)
}

type WallObserver struct {
	world  world.Interface
	logger logrus.FieldLogger
}

func NewWallObserver(w world.Interface, logger logrus.FieldLogger) Observer {
	return &WallObserver{
		world:  w,
		logger: logger,
	}
}

func (wo *WallObserver) Observe(stop <-chan struct{}) {
	go func() {
		area, err := engine.NewArea(wo.world.Width(), wo.world.Height())
		if err != nil {
			wo.logger.WithError(err).Error("cannot create area in wall observer")
			return
		}

		size := area.Size()
		ruinsCount := calcRuinsCount(size)
		var counter uint16

		for counter < ruinsCount {
			for i := 0; i < len(ruins); i++ {
				mask := ruins[i].TurnRandom()

				if area.Width() >= mask.Width() && area.Height() >= mask.Height() {
					rect, err := area.NewRandomRect(mask.Width(), mask.Height(), 0, 0)
					if err != nil {
						continue
					}

					location := mask.Location(rect.X(), rect.Y())
					if location.DotCount() > ruinsCount-counter {
						location = location[:ruinsCount-counter]
					}

					if wo.world.LocationOccupied(location) {
						continue
					}

					if _, err := wall.NewWallLocation(wo.world, location); err != nil {
						wo.logger.WithError(err).Error("error on wall creation")
					} else {
						counter += location.DotCount()
					}
				}
			}
		}
	}()
}
<commit_msg>Refactor WallObserver<commit_after>package observers

import (
	"fmt"

	"github.com/sirupsen/logrus"

	"github.com/ivan1993spb/snake-server/engine"
	"github.com/ivan1993spb/snake-server/objects/wall"
	"github.com/ivan1993spb/snake-server/world"
)

const ruinsFactor = 0.15

var dotsMaskOne = engine.NewDotsMask([][]uint8{{1}})

var ruins = []*engine.DotsMask{
	dotsMaskOne,
	engine.DotsMaskSquare2x2,
	engine.DotsMaskTank,
	engine.DotsMaskHome1,
	engine.DotsMaskHome2,
	engine.DotsMaskCross,
	engine.DotsMaskDiagonal,
	engine.DotsMaskCrossSmall,
	engine.DotsMaskDiagonalSmall,
	engine.DotsMaskLabyrinth,
	engine.DotsMaskTunnel1,
	engine.DotsMaskTunnel2,
	engine.DotsMaskBigHome,
}

type WallObserver struct {
	world  world.Interface
	logger logrus.FieldLogger
	area   engine.Area

	ruinsCount uint16
}

func NewWallObserver(w world.Interface, logger logrus.FieldLogger) Observer {
	return &WallObserver{
		world:  w,
		logger: logger,
	}
}

func (wo *WallObserver) Observe(stop <-chan struct{}) {
	go wo.run(stop)
}

func (wo *WallObserver) run(stop <-chan struct{}) {
	if err := wo.init(); err != nil {
		wo.logger.WithError(err).Error("error in wall observer")
		return
	}

	wo.addWalls()
}

func (wo *WallObserver) init() error {
	area, err := engine.NewArea(wo.world.Width(), wo.world.Height())
	if err != nil {
		return fmt.Errorf("cannot create area: %s", err)
	}

	wo.area = area
	wo.ruinsCount = wo.calcRuinsCount(wo.area.Size())

	return nil
}

func (wo *WallObserver) calcRuinsCount(size uint16) uint16 {
	return uint16(float32(size) * ruinsFactor)
}

func (wo *WallObserver) addWalls() {
	var counter uint16

	for counter < wo.ruinsCount {
		for i := 0; i < len(ruins); i++ {
			// Pass one of the ruins and maximum dots count to occupy by new wall
			counter += wo.addWallFromMask(ruins[i], wo.ruinsCount-counter)
		}
	}
}

func (wo *WallObserver) addWallFromMask(mask *engine.DotsMask, dotsLimit uint16) (dotsResult uint16) {
	mask = mask.TurnRandom()

	if wo.area.Width() < mask.Width() || wo.area.Height() < mask.Height() {
		return
	}

	rect, err := wo.area.NewRandomRect(mask.Width(), mask.Height(), 0, 0)
	if err != nil {
		return
	}

	location := mask.Location(rect.X(), rect.Y())
	if location.DotCount() > dotsLimit {
		location = location[:dotsLimit]
	}

	if wo.world.LocationOccupied(location) {
		return
	}

	if _, err := wall.NewWallLocation(wo.world, location); err != nil {
		wo.logger.WithError(err).Error("error on wall creation")
		return
	}

	dotsResult = location.DotCount()

	return
}
<|endoftext|>


<commit_before>/*
Package plotter abstracts the details of other chart packages into a simple
interface composed of Figures, Charts, and Data
*/
package plotter

import (
	"image"
	"image/color"
	"image/png"
	"io"
	"os"
	"path"
	"time"

	// third-party
	"code.google.com/p/freetype-go/freetype"
	"code.google.com/p/freetype-go/freetype/truetype"
	//"github.com/vdobler/chart"
	//"github.com/vdobler/chart/imgg"
	//"github.com/vdobler/chart/svgg"
	"github.com/stephen-soltesz/chart"
	"github.com/stephen-soltesz/chart/imgg"
	"github.com/stephen-soltesz/chart/svgg"

	"github.com/ajstarks/svgo"
)

type Style chart.Style

type Figure struct {
	Charts   []*Chart
	fontName string
	usetime  bool
}

var fontCache map[string]*truetype.Font

type Chart struct {
	chart.ScatterChart
}

type Data struct {
	chart.ScatterChartData
}

func autoStyle(s int) func() Style {
	i := s
	l := len(chart.StandardColors)
	return func() Style {
		i = (i + 1) % l
		return Style{Symbol: '.',
			SymbolSize:  1,
			SymbolColor: chart.StandardColors[i],
			LineColor:   chart.StandardColors[i],
			LineStyle:   chart.SolidLine,
			LineWidth:   2,
			FillColor:   chart.StandardColors[i]}
	}
}

var NextStyle = autoStyle(0)

func loadFont(fontfile string) *truetype.Font {
	var font *truetype.Font
	var ok bool
	var err error
	var data []byte

	// Read the font data once.
	if fontCache == nil {
		fontCache = make(map[string]*truetype.Font)
	}
	fontpath := "res/" + fontfile
	if font, ok = fontCache[fontpath]; !ok {
		data, err = Asset(fontpath)
		if err != nil {
			return nil
		}
		font, err = freetype.ParseFont(data)
		if err != nil {
			return nil
		}
		fontCache[fontpath] = font
	}
	return font
}

func NewFigure(usetime bool) *Figure {
	f := Figure{}
	f.fontName = "FreeSans.ttf"
	f.usetime = usetime
	return &f
}

func (f *Figure) RenderFile(filename string, width, height int) error {
	var err error
	var imgFile *os.File
	dir, file := path.Split(filename)
	tmpname := dir + "/.tmp_" + file

	imgFile, err = os.Create(tmpname)
	if err != nil {
		return err
	}
	err = f.Render(imgFile, width, height)
	if err != nil {
		return err
	}
	err = imgFile.Close()
	if err != nil {
		return err
	}
	err = os.Rename(tmpname, filename)
	if err != nil {
		return err
	}
	return nil
}

func (f *Figure) RenderSVG(writer io.Writer, width, height int) error {
	whiteBG := color.RGBA{0xee, 0xee, 0xee, 0xff}
	count := len(f.Charts)

	svgdata := svg.New(writer)
	svgdata.Start(width+10, height+10)
	svgdata.Rect(0, 0, width, height, "fill: #ffffff")

	for i, ax := range f.Charts {
		igr := svgg.AddTo(svgdata, 0, i*(height/count), width, (height / count), "", 12, whiteBG)
		ax.Plot(igr)
	}

	svgdata.End()
	return nil
}

func (f *Figure) Render(writer io.Writer, width, height int) error {
	whiteBG := color.RGBA{0xee, 0xee, 0xee, 0xff}
	image := image.NewRGBA(image.Rect(0, 0, width, height))
	count := len(f.Charts)
	font := loadFont(f.fontName)

	for i, ax := range f.Charts {
		igr := imgg.AddTo(image, 0, i*(height/count), width, (height / count), whiteBG, font, nil)
		ax.Plot(igr)
	}

	return png.Encode(writer, image)
}

func (f *Figure) AddChart(title, xlabel, ylabel string, xmin, xmax float64) *Chart {
	axis := Chart{chart.ScatterChart{}}

	axis.Title = title
	axis.XRange.Label = xlabel
	axis.YRange.Label = ylabel
	axis.XRange.ShowZero = false
	axis.XRange.TicSetting.Mirror = chart.MirrorAxisOnly
	axis.YRange.TicSetting.Mirror = chart.MirrorAxisOnly
	axis.XRange.TicSetting.Grid = chart.GridLines
	axis.YRange.TicSetting.Grid = chart.GridLines
	axis.XRange.Time = f.usetime
	if f.usetime {
		axis.XRange.TFixed(time.Unix(int64(xmin), 0), time.Unix(int64(xmax), 0), nil)
	} else {
		axis.XRange.Fixed(xmin, xmax, 0)
	}
	axis.XRange.Init()

	fSmall := chart.Font{Size: chart.SmallFontSize}
	fNormal := chart.Font{Size: chart.NormalFontSize}
	sKey := chart.Style{LineColor: color.Gray{0x88},
	                    LineStyle: chart.SolidLine,
											LineWidth: 1,
	                    FillColor: color.NRGBA{0xf8, 0xf8, 0xf8, 0x66},
	                    Font: fSmall}
	sGrid := chart.Style{LineStyle: chart.DashedLine,
											 LineColor: color.Gray{0xbb},
											 LineWidth: 1}
	sZero := chart.Style{LineStyle: chart.DashedLine,
											 LineColor: color.Gray{0xbb}}
	sMajor := chart.Style{LineColor: color.Gray{0x88},
												LineWidth: 1,
												Font: fNormal}
	sTic := chart.Style{LineWidth: 1,
											LineColor: color.Gray{0x88},
											Font: fNormal}
	sRange := chart.Style{LineWidth: 1, Font: fNormal}
	sTitle := chart.Style{LineWidth: 1, Font: fNormal}

	//sBg := chart.Style{LineColor: color.NRGBA{0xff, 0xff, 0xee, 0x88},
	//                   FillColor: color.NRGBA{0xff, 0xff, 0xee, 0x88}}
	//                   chart.PlotBackgroundElement: sBg,

	axis.Options = chart.PlotOptions{
			chart.GridLineElement:	 sGrid,
			chart.ZeroAxisElement:   sZero,
			chart.MajorAxisElement:  sMajor,
			chart.MinorAxisElement:  sMajor,
			chart.MajorTicElement:   sTic,
			chart.MinorTicElement:   sTic,
			chart.KeyElement:				 sKey,
			chart.RangeLimitElement: sRange,
			chart.TitleElement:      sTitle}
	axis.Key.Cols = 1
	axis.Key.Pos = "itl"

	f.Charts = append(f.Charts, &axis)
	return &axis
}

func (ax *Chart) ShowXAxis(s bool) {
	ax.XRange.TicSetting.HideLabels = !s
	return
}

func (ax *Chart) AddData(name string, x, y []float64, style Style) *Data {
	ax.AddDataPair(name, x, y, chart.PlotStyleLinesPoints, chart.Style(style))
	line := Data{ax.Data[len(ax.Data)-1]}
	return &line
}

//func (ax *Chart) LenDatas() int {
//  return len(ax.Data)
//}

/*
// must be called after a plot
func (ax *Chart) PlotArea() (int, int, int, int) {
  x1 := ax.XRange.Data2Screen(ax.XRange.Min)
  y1 := ax.YRange.Data2Screen(ax.YRange.Min)
  w1 := ax.XRange.Data2Screen(ax.XRange.Max) - ax.XRange.Data2Screen(ax.XRange.Min)
  h1 := ax.YRange.Data2Screen(ax.YRange.Max) - ax.YRange.Data2Screen(ax.YRange.Min)
  return x1, y1, w1, h1
}
*/

//func (l *Data) GetName() string {
//  return l.Name
//}
<commit_msg>add one unit of precision to Y-axis format.<commit_after>/*
Package plotter abstracts the details of other chart packages into a simple
interface composed of Figures, Charts, and Data
*/
package plotter

import (
	"image"
	"image/color"
	"image/png"
	"io"
	"fmt"
	"os"
	"path"
	"time"

	// third-party
	"code.google.com/p/freetype-go/freetype"
	"code.google.com/p/freetype-go/freetype/truetype"
	//"github.com/vdobler/chart"
	//"github.com/vdobler/chart/imgg"
	//"github.com/vdobler/chart/svgg"
	"github.com/stephen-soltesz/chart"
	"github.com/stephen-soltesz/chart/imgg"
	"github.com/stephen-soltesz/chart/svgg"

	"github.com/ajstarks/svgo"
)

type Style chart.Style

type Figure struct {
	Charts   []*Chart
	fontName string
	usetime  bool
}

var fontCache map[string]*truetype.Font

type Chart struct {
	chart.ScatterChart
}

type Data struct {
	chart.ScatterChartData
}

func autoStyle(s int) func() Style {
	i := s
	l := len(chart.StandardColors)
	return func() Style {
		i = (i + 1) % l
		return Style{Symbol: '.',
			SymbolSize:  1,
			SymbolColor: chart.StandardColors[i],
			LineColor:   chart.StandardColors[i],
			LineStyle:   chart.SolidLine,
			LineWidth:   2,
			FillColor:   chart.StandardColors[i]}
	}
}

var NextStyle = autoStyle(0)

func loadFont(fontfile string) *truetype.Font {
	var font *truetype.Font
	var ok bool
	var err error
	var data []byte

	// Read the font data once.
	if fontCache == nil {
		fontCache = make(map[string]*truetype.Font)
	}
	fontpath := "res/" + fontfile
	if font, ok = fontCache[fontpath]; !ok {
		data, err = Asset(fontpath)
		if err != nil {
			return nil
		}
		font, err = freetype.ParseFont(data)
		if err != nil {
			return nil
		}
		fontCache[fontpath] = font
	}
	return font
}

func NewFigure(usetime bool) *Figure {
	f := Figure{}
	f.fontName = "FreeSans.ttf"
	f.usetime = usetime
	return &f
}

func (f *Figure) RenderFile(filename string, width, height int) error {
	var err error
	var imgFile *os.File
	dir, file := path.Split(filename)
	tmpname := dir + "/.tmp_" + file

	imgFile, err = os.Create(tmpname)
	if err != nil {
		return err
	}
	err = f.Render(imgFile, width, height)
	if err != nil {
		return err
	}
	err = imgFile.Close()
	if err != nil {
		return err
	}
	err = os.Rename(tmpname, filename)
	if err != nil {
		return err
	}
	return nil
}

func (f *Figure) RenderSVG(writer io.Writer, width, height int) error {
	whiteBG := color.RGBA{0xee, 0xee, 0xee, 0xff}
	count := len(f.Charts)

	svgdata := svg.New(writer)
	svgdata.Start(width+10, height+10)
	svgdata.Rect(0, 0, width, height, "fill: #ffffff")

	for i, ax := range f.Charts {
		igr := svgg.AddTo(svgdata, 0, i*(height/count), width, (height / count), "", 12, whiteBG)
		ax.Plot(igr)
	}

	svgdata.End()
	return nil
}

func (f *Figure) Render(writer io.Writer, width, height int) error {
	whiteBG := color.RGBA{0xee, 0xee, 0xee, 0xff}
	image := image.NewRGBA(image.Rect(0, 0, width, height))
	count := len(f.Charts)
	font := loadFont(f.fontName)

	for i, ax := range f.Charts {
		igr := imgg.AddTo(image, 0, i*(height/count), width, (height / count), whiteBG, font, nil)
		ax.Plot(igr)
	}

	return png.Encode(writer, image)
}

func (f *Figure) AddChart(title, xlabel, ylabel string, xmin, xmax float64) *Chart {
	axis := Chart{chart.ScatterChart{}}

	axis.Title = title
	axis.XRange.Label = xlabel
	axis.YRange.Label = ylabel
	axis.XRange.ShowZero = false
	axis.XRange.TicSetting.Mirror = chart.MirrorAxisOnly
	axis.YRange.TicSetting.Mirror = chart.MirrorAxisOnly
	axis.XRange.TicSetting.Grid = chart.GridLines
	axis.YRange.TicSetting.Grid = chart.GridLines
	axis.YRange.TicSetting.Format = func (val float64) string {
		return fmt.Sprintf("%.1f", val)
	}
	axis.XRange.Time = f.usetime
	if f.usetime {
		axis.XRange.TFixed(time.Unix(int64(xmin), 0), time.Unix(int64(xmax), 0), nil)
	} else {
		axis.XRange.Fixed(xmin, xmax, 0)
	}
	axis.XRange.Init()

	fSmall := chart.Font{Size: chart.SmallFontSize}
	fNormal := chart.Font{Size: chart.NormalFontSize}
	sKey := chart.Style{LineColor: color.Gray{0x88},
	                    LineStyle: chart.SolidLine,
											LineWidth: 1,
	                    FillColor: color.NRGBA{0xf8, 0xf8, 0xf8, 0x66},
	                    Font: fSmall}
	sGrid := chart.Style{LineStyle: chart.DashedLine,
											 LineColor: color.Gray{0xbb},
											 LineWidth: 1}
	sZero := chart.Style{LineStyle: chart.DashedLine,
											 LineColor: color.Gray{0xbb}}
	sMajor := chart.Style{LineColor: color.Gray{0x88},
												LineWidth: 1,
												Font: fNormal}
	sTic := chart.Style{LineWidth: 1,
											LineColor: color.Gray{0x88},
											Font: fNormal}
	sRange := chart.Style{LineWidth: 1, Font: fNormal}
	sTitle := chart.Style{LineWidth: 1, Font: fNormal}

	//sBg := chart.Style{LineColor: color.NRGBA{0xff, 0xff, 0xee, 0x88},
	//                   FillColor: color.NRGBA{0xff, 0xff, 0xee, 0x88}}
	//                   chart.PlotBackgroundElement: sBg,

	axis.Options = chart.PlotOptions{
			chart.GridLineElement:	 sGrid,
			chart.ZeroAxisElement:   sZero,
			chart.MajorAxisElement:  sMajor,
			chart.MinorAxisElement:  sMajor,
			chart.MajorTicElement:   sTic,
			chart.MinorTicElement:   sTic,
			chart.KeyElement:				 sKey,
			chart.RangeLimitElement: sRange,
			chart.TitleElement:      sTitle}
	axis.Key.Cols = 1
	axis.Key.Pos = "itl"

	f.Charts = append(f.Charts, &axis)
	return &axis
}

func (ax *Chart) ShowXAxis(s bool) {
	ax.XRange.TicSetting.HideLabels = !s
	return
}

func (ax *Chart) AddData(name string, x, y []float64, style Style) *Data {
	ax.AddDataPair(name, x, y, chart.PlotStyleLinesPoints, chart.Style(style))
	line := Data{ax.Data[len(ax.Data)-1]}
	return &line
}

//func (ax *Chart) LenDatas() int {
//  return len(ax.Data)
//}

/*
// must be called after a plot
func (ax *Chart) PlotArea() (int, int, int, int) {
  x1 := ax.XRange.Data2Screen(ax.XRange.Min)
  y1 := ax.YRange.Data2Screen(ax.YRange.Min)
  w1 := ax.XRange.Data2Screen(ax.XRange.Max) - ax.XRange.Data2Screen(ax.XRange.Min)
  h1 := ax.YRange.Data2Screen(ax.YRange.Max) - ax.YRange.Data2Screen(ax.YRange.Min)
  return x1, y1, w1, h1
}
*/

//func (l *Data) GetName() string {
//  return l.Name
//}
<|endoftext|>