<commit_before>package vcs

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/knieriem/hgo"
	"github.com/knieriem/hgo/changelog"
	"github.com/knieriem/hgo/revlog"
	"github.com/knieriem/hgo/store"
	"os"
	"os/exec"
	"sort"
	"strconv"
	"strings"
	"time"
)

type hg struct {
	cmd string
}

func (_ hg) ShortName() string { return "hg" }

var Hg VCS = hg{"hg"}

type hgRepo struct {
	dir string
	hg  *hg
}

func (hg hg) Clone(url, dir string) (Repository, error) {
	r := &hgRepo{dir, &hg}

	cmd := exec.Command("hg", "clone", "--", url, dir)
	if out, err := cmd.CombinedOutput(); err != nil {
		if strings.Contains(string(out), fmt.Sprintf("abort: destination '%s' is not empty", dir)) {
			return nil, os.ErrExist
		}
		return nil, fmt.Errorf("hg %v failed: %s\n%s", cmd.Args, err, out)
	}

	return r, nil
}

func (hg hg) Open(dir string) (Repository, error) {
	// TODO(sqs): check for .hg or bare repo
	if _, err := os.Stat(dir); err == nil {
		return &hgRepo{dir, &hg}, nil
	} else {
		return nil, err
	}
}

func (hg hg) CloneMirror(url, dir string) error {
	cmd := exec.Command("hg", "clone", "-U", "--", url, dir)
	if out, err := cmd.CombinedOutput(); err != nil {
		if strings.Contains(string(out), fmt.Sprintf("abort: destination '%s' is not empty", dir)) {
			return os.ErrExist
		}
		return fmt.Errorf("hg %v failed: %s\n%s", cmd.Args, err, out)
	}
	return nil
}

func (hg hg) UpdateMirror(dir string) error {
	cmd := exec.Command("hg", "pull")
	cmd.Dir = dir
	if out, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("hg %v failed: %s\n%s", cmd.Args, err, out)
	}
	return nil
}

func (r *hgRepo) Dir() (dir string) {
	return r.dir
}

func (r *hgRepo) VCS() VCS {
	return r.hg
}

func (r *hgRepo) Download() error {
	cmd := exec.Command("hg", "pull")
	cmd.Dir = r.dir
	out, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("hg %v failed: %s\n%s", cmd.Args, err, out)
	}
	return nil
}

func (r *hgRepo) CommitLog() ([]*Commit, error) {
	cmd := exec.Command("hg", "log", `--template={node}\n{author|person}\n{author|email}\n{date|rfc3339date}\n\n{desc}\n\x00`)
	cmd.Dir = r.dir
	cmd.Stderr = os.Stderr
	out, err := cmd.Output()
	if err != nil {
		return nil, err
	}

	if len(out) == 0 {
		return nil, nil
	}

	commitEntries := bytes.Split(out, []byte{'\x00'})
	commitEntries = commitEntries[:len(commitEntries)-1] // hg log puts delimiter at end
	commits := make([]*Commit, len(commitEntries))
	for i, e := range commitEntries {
		if len(e) == 0 {
			continue
		}
		commit := new(Commit)
		parts := bytes.SplitN(e, []byte("\n\n"), 2)
		if len(parts) != 2 {
			return nil, fmt.Errorf("unhandled hg commit entry: %q", string(e))
		}
		header, commitMsg := parts[0], parts[1]

		headers := bytes.Split(header, []byte{'\n'})
		commit.ID = string(headers[0])
		commit.AuthorName = string(headers[1])
		commit.AuthorEmail = string(headers[2])

		var err error
		commit.AuthorDate, err = time.Parse(time.RFC3339, string(headers[3]))
		if err != nil {
			return nil, err
		}

		commit.Message = strings.TrimSpace(string(commitMsg))
		commits[i] = commit
	}

	return commits, nil
}

func (r *hgRepo) CheckOut(rev string) (dir string, err error) {
	if rev == "" {
		rev = "default"
	}
	cmd := exec.Command("hg", "update", "-r", rev)
	cmd.Dir = r.dir
	if out, err := cmd.CombinedOutput(); err == nil {
		return r.dir, nil
	} else {
		return "", fmt.Errorf("hg %v failed: %s\n%s", cmd.Args, err, out)
	}
}

// hgo is faster but doesn't always work. try it first.
func (r *hgRepo) ReadFileAtRevision(path string, rev string) ([]byte, FileType, error) {
	data, err := r.readFileAtRevisionHgo(path, rev)
	if err == nil {
		return data, File, err
	}
	return r.readFileAtRevisionHg(path, rev)
}

func (r *hgRepo) readFileAtRevisionHg(path string, rev string) ([]byte, FileType, error) {
	// if a dir, list its contents
	cmd := exec.Command("hg", "locate", "-r", rev, "-I", path)
	cmd.Dir = r.dir
	if out, err := cmd.CombinedOutput(); err == nil {
		if strings.HasPrefix(string(out), path+"/") {
			files := strings.Split(string(out), "\n")
			var filelist []string
			cwd := ""
			for _, f := range files {
				if strings.HasPrefix(f, path+"/") {
					name := f[len(path+"/"):] // remove header
					if strings.Contains(name, "/") {
						if !(cwd != "" && strings.HasPrefix(name, cwd)) { // only add new dir
							name = name[:strings.Index(name, "/")+1]
							cwd = name
							filelist = append(filelist, name)
						}
					} else {
						filelist = append(filelist, name)
					}
				}
			}
			sort.Sort(fileSlice(filelist))
			filestr := strings.Join(filelist, "\n")
			return []byte(filestr), Dir, nil
		}
	}

	// if a file, display the file
	cmd = exec.Command("hg", "cat", "-r", rev, "--", path)
	cmd.Dir = r.dir
	if out, err := cmd.CombinedOutput(); err == nil {
		return out, File, nil
	} else {
		if strings.Contains(string(out), fmt.Sprintf("%s: no such file in rev", path)) {
			return nil, File, os.ErrNotExist
		}
		if strings.Contains(string(out), fmt.Sprintf("abort: unknown revision '%s'!", rev)) {
			return nil, File, os.ErrNotExist
		}
		return nil, File, fmt.Errorf("hg %v failed: %s\n%s", cmd.Args, err, out)
	}
}

func (r *hgRepo) readFileAtRevisionHgo(path string, rev string) ([]byte, error) {
	rp, err := hgo.OpenRepository(r.dir)
	if err != nil {
		return nil, err
	}
	st := rp.NewStore()
	rs := parseRevisionSpec(rp, rev, "tip")

	fileLog, err := st.OpenRevlog(path)
	if err != nil {
		return nil, err
	}

	ra := repoAccess{
		rp: rp,
		fb: revlog.NewFileBuilder(),
		st: st,
	}

	localId, ok := rs.(revlog.FileRevSpec)
	if !ok {
		localId, err = ra.localChangesetId(rs)
		if err != nil {
			return nil, err
		}
	}

	rec, err := lookupFile(fileLog, int(localId), func() (*store.ManifestEnt, error) {
		return ra.manifestEntry(int(localId), path)
	})
	if err != nil {
		return nil, err
	}

	fb := revlog.NewFileBuilder()
	return fb.Build(rec)
}

type repoAccess struct {
	rp        *hgo.Repository
	fb        *revlog.FileBuilder
	st        *store.Store
	changelog *revlog.Index
}

func (ra *repoAccess) localChangesetId(rs revlog.RevisionSpec) (chgId revlog.FileRevSpec, err error) {
	r, err := ra.clRec(rs)
	if err == nil {
		chgId = revlog.FileRevSpec(r.FileRev())
	}
	return
}

func (ra *repoAccess) clRec(rs revlog.RevisionSpec) (r *revlog.Rec, err error) {
	if ra.changelog == nil {
		log, err1 := ra.st.OpenChangeLog()
		if err1 != nil {
			err = err1
			return
		}
		ra.changelog = log
	}
	r, err = rs.Lookup(ra.changelog)
	return
}

func (ra *repoAccess) manifestEntry(chgId int, fileName string) (me *store.ManifestEnt, err error) {
	r, err := ra.clRec(revlog.FileRevSpec(chgId))
	if err != nil {
		return
	}
	c, err := changelog.BuildEntry(r, ra.fb)
	if err != nil {
		return
	}
	m, err := getManifest(ra.rp, int(c.Linkrev), c.ManifestNode, ra.fb)
	if err != nil {
		return
	}
	me = m.Map()[fileName]
	if me == nil {
		err = errors.New("file does not exist in given revision")
	}
	return
}

func getManifest(rp *hgo.Repository, linkrev int, id revlog.NodeId, b *revlog.FileBuilder) (m store.Manifest, err error) {
	st := rp.NewStore()
	mlog, err := st.OpenManifests()
	if err != nil {
		return
	}

	r, err := mlog.LookupRevision(linkrev, id)
	if err != nil {
		return
	}

	m, err = store.BuildManifest(r, b)
	return
}

// Lookup the given revision of a file. The Manifest is consulted only
// if necessary, i.e. if it can''t be told from the filelog whether a file exists yet or not
func lookupFile(fileLog *revlog.Index, chgId int, manifestEntry func() (*store.ManifestEnt, error)) (r *revlog.Rec, err error) {
	r, err = revlog.LinkRevSpec(chgId).Lookup(fileLog)
	if err != nil {
		return
	}
	if r.FileRev() == -1 {
		err = revlog.ErrRevisionNotFound
		return
	}

	if int(r.Linkrev) == chgId {
		// The requested revision matches this record, which can be
		// used as a sign that the file is existent yet.
		return
	}

	if !r.IsLeaf() {
		// There are other records that have the current record as a parent.
		// This means, the file was existent, no need to check the manifest.
		return
	}

	// Check for the file's existence using the manifest.
	ent, err := manifestEntry()
	if err != nil {
		return
	}

	// compare hashes
	wantId, err := ent.Id()
	if err != nil {
		return
	}
	if !wantId.Eq(r.Id()) {
		err = errors.New("manifest node id does not match file id")
	}
	return
}

func parseRevisionSpec(rp *hgo.Repository, s, dflt string) revlog.RevisionSpec {
	if s == "" {
		s = dflt
	}
	if s == "tip" {
		return revlog.TipRevSpec{}
	}
	if s == "null" {
		return revlog.NullRevSpec{}
	}
	_, allTags := rp.Tags()
	if id, ok := allTags.IdByName[s]; ok {
		s = id
	} else if i, err := strconv.Atoi(s); err == nil {
		return revlog.FileRevSpec(i)
	}

	return revlog.NodeIdRevSpec(s)
}

func (r *hgRepo) CurrentCommitID() (string, error) {
	cmd := exec.Command("hg", "identify", "-i")
	cmd.Dir = r.dir
	out, err := cmd.CombinedOutput()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(out)), nil
}
<commit_msg>handle hg root dir<commit_after>package vcs

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/knieriem/hgo"
	"github.com/knieriem/hgo/changelog"
	"github.com/knieriem/hgo/revlog"
	"github.com/knieriem/hgo/store"
	"os"
	"os/exec"
	"sort"
	"strconv"
	"strings"
	"time"
)

type hg struct {
	cmd string
}

func (_ hg) ShortName() string { return "hg" }

var Hg VCS = hg{"hg"}

type hgRepo struct {
	dir string
	hg  *hg
}

func (hg hg) Clone(url, dir string) (Repository, error) {
	r := &hgRepo{dir, &hg}

	cmd := exec.Command("hg", "clone", "--", url, dir)
	if out, err := cmd.CombinedOutput(); err != nil {
		if strings.Contains(string(out), fmt.Sprintf("abort: destination '%s' is not empty", dir)) {
			return nil, os.ErrExist
		}
		return nil, fmt.Errorf("hg %v failed: %s\n%s", cmd.Args, err, out)
	}

	return r, nil
}

func (hg hg) Open(dir string) (Repository, error) {
	// TODO(sqs): check for .hg or bare repo
	if _, err := os.Stat(dir); err == nil {
		return &hgRepo{dir, &hg}, nil
	} else {
		return nil, err
	}
}

func (hg hg) CloneMirror(url, dir string) error {
	cmd := exec.Command("hg", "clone", "-U", "--", url, dir)
	if out, err := cmd.CombinedOutput(); err != nil {
		if strings.Contains(string(out), fmt.Sprintf("abort: destination '%s' is not empty", dir)) {
			return os.ErrExist
		}
		return fmt.Errorf("hg %v failed: %s\n%s", cmd.Args, err, out)
	}
	return nil
}

func (hg hg) UpdateMirror(dir string) error {
	cmd := exec.Command("hg", "pull")
	cmd.Dir = dir
	if out, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("hg %v failed: %s\n%s", cmd.Args, err, out)
	}
	return nil
}

func (r *hgRepo) Dir() (dir string) {
	return r.dir
}

func (r *hgRepo) VCS() VCS {
	return r.hg
}

func (r *hgRepo) Download() error {
	cmd := exec.Command("hg", "pull")
	cmd.Dir = r.dir
	out, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("hg %v failed: %s\n%s", cmd.Args, err, out)
	}
	return nil
}

func (r *hgRepo) CommitLog() ([]*Commit, error) {
	cmd := exec.Command("hg", "log", `--template={node}\n{author|person}\n{author|email}\n{date|rfc3339date}\n\n{desc}\n\x00`)
	cmd.Dir = r.dir
	cmd.Stderr = os.Stderr
	out, err := cmd.Output()
	if err != nil {
		return nil, err
	}

	if len(out) == 0 {
		return nil, nil
	}

	commitEntries := bytes.Split(out, []byte{'\x00'})
	commitEntries = commitEntries[:len(commitEntries)-1] // hg log puts delimiter at end
	commits := make([]*Commit, len(commitEntries))
	for i, e := range commitEntries {
		if len(e) == 0 {
			continue
		}
		commit := new(Commit)
		parts := bytes.SplitN(e, []byte("\n\n"), 2)
		if len(parts) != 2 {
			return nil, fmt.Errorf("unhandled hg commit entry: %q", string(e))
		}
		header, commitMsg := parts[0], parts[1]

		headers := bytes.Split(header, []byte{'\n'})
		commit.ID = string(headers[0])
		commit.AuthorName = string(headers[1])
		commit.AuthorEmail = string(headers[2])

		var err error
		commit.AuthorDate, err = time.Parse(time.RFC3339, string(headers[3]))
		if err != nil {
			return nil, err
		}

		commit.Message = strings.TrimSpace(string(commitMsg))
		commits[i] = commit
	}

	return commits, nil
}

func (r *hgRepo) CheckOut(rev string) (dir string, err error) {
	if rev == "" {
		rev = "default"
	}
	cmd := exec.Command("hg", "update", "-r", rev)
	cmd.Dir = r.dir
	if out, err := cmd.CombinedOutput(); err == nil {
		return r.dir, nil
	} else {
		return "", fmt.Errorf("hg %v failed: %s\n%s", cmd.Args, err, out)
	}
}

// hgo is faster but doesn't always work. try it first.
func (r *hgRepo) ReadFileAtRevision(path string, rev string) ([]byte, FileType, error) {
	data, err := r.readFileAtRevisionHgo(path, rev)
	if err == nil {
		return data, File, err
	}
	return r.readFileAtRevisionHg(path, rev)
}

func (r *hgRepo) readFileAtRevisionHg(path string, rev string) ([]byte, FileType, error) {
	// if a dir, list its contents
	isDir := false
	if path == "" {
		path = "."
		isDir = true
	}
	cmd := exec.Command("hg", "locate", "-r", rev, "-I", path)
	cmd.Dir = r.dir
	if out, err := cmd.CombinedOutput(); err == nil {
		if isDir || strings.HasPrefix(string(out), path+"/") {
			files := strings.Split(string(out), "\n")
			var filelist []string
			cwd := ""
			for _, f := range files {
				name := ""
				if strings.HasPrefix(f, path+"/") {
					name = f[len(path+"/"):] // remove header
				} else if isDir {
					name = f
				}
				if strings.Contains(name, "/") {
					if !(cwd != "" && strings.HasPrefix(name, cwd)) { // only add new dir
						name = name[:strings.Index(name, "/")+1]
						cwd = name
						filelist = append(filelist, name)
					}
				} else if name != "" {
					filelist = append(filelist, name)
				}
			}
			sort.Sort(fileSlice(filelist))
			filestr := strings.Join(filelist, "\n")
			return []byte(filestr), Dir, nil
		}
	}

	// if a file, display the file
	cmd = exec.Command("hg", "cat", "-r", rev, "--", path)
	cmd.Dir = r.dir
	if out, err := cmd.CombinedOutput(); err == nil {
		return out, File, nil
	} else {
		if strings.Contains(string(out), fmt.Sprintf("%s: no such file in rev", path)) {
			return nil, File, os.ErrNotExist
		}
		if strings.Contains(string(out), fmt.Sprintf("abort: unknown revision '%s'!", rev)) {
			return nil, File, os.ErrNotExist
		}
		return nil, File, fmt.Errorf("hg %v failed: %s\n%s", cmd.Args, err, out)
	}
}

func (r *hgRepo) readFileAtRevisionHgo(path string, rev string) ([]byte, error) {
	rp, err := hgo.OpenRepository(r.dir)
	if err != nil {
		return nil, err
	}
	st := rp.NewStore()
	rs := parseRevisionSpec(rp, rev, "tip")

	fileLog, err := st.OpenRevlog(path)
	if err != nil {
		return nil, err
	}

	ra := repoAccess{
		rp: rp,
		fb: revlog.NewFileBuilder(),
		st: st,
	}

	localId, ok := rs.(revlog.FileRevSpec)
	if !ok {
		localId, err = ra.localChangesetId(rs)
		if err != nil {
			return nil, err
		}
	}

	rec, err := lookupFile(fileLog, int(localId), func() (*store.ManifestEnt, error) {
		return ra.manifestEntry(int(localId), path)
	})
	if err != nil {
		return nil, err
	}

	fb := revlog.NewFileBuilder()
	return fb.Build(rec)
}

type repoAccess struct {
	rp        *hgo.Repository
	fb        *revlog.FileBuilder
	st        *store.Store
	changelog *revlog.Index
}

func (ra *repoAccess) localChangesetId(rs revlog.RevisionSpec) (chgId revlog.FileRevSpec, err error) {
	r, err := ra.clRec(rs)
	if err == nil {
		chgId = revlog.FileRevSpec(r.FileRev())
	}
	return
}

func (ra *repoAccess) clRec(rs revlog.RevisionSpec) (r *revlog.Rec, err error) {
	if ra.changelog == nil {
		log, err1 := ra.st.OpenChangeLog()
		if err1 != nil {
			err = err1
			return
		}
		ra.changelog = log
	}
	r, err = rs.Lookup(ra.changelog)
	return
}

func (ra *repoAccess) manifestEntry(chgId int, fileName string) (me *store.ManifestEnt, err error) {
	r, err := ra.clRec(revlog.FileRevSpec(chgId))
	if err != nil {
		return
	}
	c, err := changelog.BuildEntry(r, ra.fb)
	if err != nil {
		return
	}
	m, err := getManifest(ra.rp, int(c.Linkrev), c.ManifestNode, ra.fb)
	if err != nil {
		return
	}
	me = m.Map()[fileName]
	if me == nil {
		err = errors.New("file does not exist in given revision")
	}
	return
}

func getManifest(rp *hgo.Repository, linkrev int, id revlog.NodeId, b *revlog.FileBuilder) (m store.Manifest, err error) {
	st := rp.NewStore()
	mlog, err := st.OpenManifests()
	if err != nil {
		return
	}

	r, err := mlog.LookupRevision(linkrev, id)
	if err != nil {
		return
	}

	m, err = store.BuildManifest(r, b)
	return
}

// Lookup the given revision of a file. The Manifest is consulted only
// if necessary, i.e. if it can''t be told from the filelog whether a file exists yet or not
func lookupFile(fileLog *revlog.Index, chgId int, manifestEntry func() (*store.ManifestEnt, error)) (r *revlog.Rec, err error) {
	r, err = revlog.LinkRevSpec(chgId).Lookup(fileLog)
	if err != nil {
		return
	}
	if r.FileRev() == -1 {
		err = revlog.ErrRevisionNotFound
		return
	}

	if int(r.Linkrev) == chgId {
		// The requested revision matches this record, which can be
		// used as a sign that the file is existent yet.
		return
	}

	if !r.IsLeaf() {
		// There are other records that have the current record as a parent.
		// This means, the file was existent, no need to check the manifest.
		return
	}

	// Check for the file's existence using the manifest.
	ent, err := manifestEntry()
	if err != nil {
		return
	}

	// compare hashes
	wantId, err := ent.Id()
	if err != nil {
		return
	}
	if !wantId.Eq(r.Id()) {
		err = errors.New("manifest node id does not match file id")
	}
	return
}

func parseRevisionSpec(rp *hgo.Repository, s, dflt string) revlog.RevisionSpec {
	if s == "" {
		s = dflt
	}
	if s == "tip" {
		return revlog.TipRevSpec{}
	}
	if s == "null" {
		return revlog.NullRevSpec{}
	}
	_, allTags := rp.Tags()
	if id, ok := allTags.IdByName[s]; ok {
		s = id
	} else if i, err := strconv.Atoi(s); err == nil {
		return revlog.FileRevSpec(i)
	}

	return revlog.NodeIdRevSpec(s)
}

func (r *hgRepo) CurrentCommitID() (string, error) {
	cmd := exec.Command("hg", "identify", "-i")
	cmd.Dir = r.dir
	out, err := cmd.CombinedOutput()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(out)), nil
}
<|endoftext|>


<commit_before>// +build linux

package seccomp // import "github.com/docker/docker/profiles/seccomp"

import (
	"encoding/json"
	"io/ioutil"
	"strings"
	"testing"

	"github.com/opencontainers/runtime-spec/specs-go"
	"gotest.tools/v3/assert"
)

func TestLoadProfile(t *testing.T) {
	f, err := ioutil.ReadFile("fixtures/example.json")
	if err != nil {
		t.Fatal(err)
	}
	rs := createSpec()
	p, err := LoadProfile(string(f), &rs)
	if err != nil {
		t.Fatal(err)
	}
	var expectedErrno uint = 12345
	expected := specs.LinuxSeccomp{
		DefaultAction: "SCMP_ACT_ERRNO",
		Syscalls: []specs.LinuxSyscall{
			{
				Names:  []string{"clone"},
				Action: "SCMP_ACT_ALLOW",
				Args: []specs.LinuxSeccompArg{{
					Index:    0,
					Value:    2114060288,
					ValueTwo: 0,
					Op:       "SCMP_CMP_MASKED_EQ",
				}},
			},
			{

				Names:  []string{"open"},
				Action: "SCMP_ACT_ALLOW",
				Args:   []specs.LinuxSeccompArg{},
			},
			{
				Names:  []string{"close"},
				Action: "SCMP_ACT_ALLOW",
				Args:   []specs.LinuxSeccompArg{},
			},
			{
				Names:    []string{"syslog"},
				Action:   "SCMP_ACT_ERRNO",
				ErrnoRet: &expectedErrno,
				Args:     []specs.LinuxSeccompArg{},
			},
		},
	}

	assert.DeepEqual(t, expected, *p)
}

func TestLoadProfileWithDefaultErrnoRet(t *testing.T) {
	var profile = []byte(`{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 6
}`)
	rs := createSpec()
	p, err := LoadProfile(string(profile), &rs)
	if err != nil {
		t.Fatal(err)
	}

	expectedErrnoRet := uint(6)
	expected := specs.LinuxSeccomp{
		DefaultAction:   "SCMP_ACT_ERRNO",
		DefaultErrnoRet: &expectedErrnoRet,
	}

	assert.DeepEqual(t, expected, *p)
}

func TestLoadProfileWithListenerPath(t *testing.T) {
	var profile = []byte(`{
"defaultAction": "SCMP_ACT_ERRNO",
"listenerPath": "/var/run/seccompaget.sock",
"listenerMetadata": "opaque-metadata"
}`)
	rs := createSpec()
	p, err := LoadProfile(string(profile), &rs)
	if err != nil {
		t.Fatal(err)
	}

	expected := specs.LinuxSeccomp{
		DefaultAction:    "SCMP_ACT_ERRNO",
		ListenerPath:     "/var/run/seccompaget.sock",
		ListenerMetadata: "opaque-metadata",
	}

	assert.DeepEqual(t, expected, *p)
}

// TestLoadLegacyProfile tests loading a seccomp profile in the old format
// (before https://github.com/docker/docker/pull/24510)
func TestLoadLegacyProfile(t *testing.T) {
	f, err := ioutil.ReadFile("fixtures/default-old-format.json")
	if err != nil {
		t.Fatal(err)
	}
	rs := createSpec()
	if _, err := LoadProfile(string(f), &rs); err != nil {
		t.Fatal(err)
	}
}

func TestLoadDefaultProfile(t *testing.T) {
	f, err := ioutil.ReadFile("default.json")
	if err != nil {
		t.Fatal(err)
	}
	rs := createSpec()
	if _, err := LoadProfile(string(f), &rs); err != nil {
		t.Fatal(err)
	}
}

func TestUnmarshalDefaultProfile(t *testing.T) {
	expected := DefaultProfile()
	if expected == nil {
		t.Skip("seccomp not supported")
	}

	f, err := ioutil.ReadFile("default.json")
	if err != nil {
		t.Fatal(err)
	}
	var profile Seccomp
	err = json.Unmarshal(f, &profile)
	if err != nil {
		t.Fatal(err)
	}
	assert.DeepEqual(t, expected.Architectures, profile.Architectures)
	assert.DeepEqual(t, expected.ArchMap, profile.ArchMap)
	assert.DeepEqual(t, expected.DefaultAction, profile.DefaultAction)
	assert.DeepEqual(t, expected.Syscalls, profile.Syscalls)
}

func TestMarshalUnmarshalFilter(t *testing.T) {
	t.Parallel()
	tests := []struct {
		in    string
		out   string
		error bool
	}{
		{in: `{"arches":["s390x"],"minKernel":3}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":3.12}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":true}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":"0.0"}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":"3"}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":".3"}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":"3."}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":"true"}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":"3.12.1\""}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":"4.15abc"}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":null}`, out: `{"arches":["s390x"]}`},
		{in: `{"arches":["s390x"],"minKernel":""}`, out: `{"arches":["s390x"],"minKernel":""}`}, // FIXME: try to fix omitempty for this
		{in: `{"arches":["s390x"],"minKernel":"0.5"}`, out: `{"arches":["s390x"],"minKernel":"0.5"}`},
		{in: `{"arches":["s390x"],"minKernel":"0.50"}`, out: `{"arches":["s390x"],"minKernel":"0.50"}`},
		{in: `{"arches":["s390x"],"minKernel":"5.0"}`, out: `{"arches":["s390x"],"minKernel":"5.0"}`},
		{in: `{"arches":["s390x"],"minKernel":"50.0"}`, out: `{"arches":["s390x"],"minKernel":"50.0"}`},
		{in: `{"arches":["s390x"],"minKernel":"4.15"}`, out: `{"arches":["s390x"],"minKernel":"4.15"}`},
	}
	for _, tc := range tests {
		tc := tc
		t.Run(tc.in, func(t *testing.T) {
			var filter Filter
			err := json.Unmarshal([]byte(tc.in), &filter)
			if tc.error {
				if err == nil {
					t.Fatal("expected an error")
				} else if !strings.Contains(err.Error(), "invalid kernel version") {
					t.Fatal("unexpected error:", err)
				}
				return
			}
			if err != nil {
				t.Fatal(err)
			}
			out, err := json.Marshal(filter)
			if err != nil {
				t.Fatal(err)
			}
			if string(out) != tc.out {
				t.Fatalf("expected %s, got %s", tc.out, string(out))
			}
		})
	}
}

func TestLoadConditional(t *testing.T) {
	f, err := ioutil.ReadFile("fixtures/conditional_include.json")
	if err != nil {
		t.Fatal(err)
	}
	tests := []struct {
		doc      string
		cap      string
		expected []string
	}{
		{doc: "no caps", expected: []string{"chmod", "ptrace"}},
		{doc: "with syslog", cap: "CAP_SYSLOG", expected: []string{"chmod", "syslog", "ptrace"}},
		{doc: "no ptrace", cap: "CAP_SYS_ADMIN", expected: []string{"chmod"}},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.doc, func(t *testing.T) {
			rs := createSpec(tc.cap)
			p, err := LoadProfile(string(f), &rs)
			if err != nil {
				t.Fatal(err)
			}
			if len(p.Syscalls) != len(tc.expected) {
				t.Fatalf("expected %d syscalls in profile, have %d", len(tc.expected), len(p.Syscalls))
			}
			for i, v := range p.Syscalls {
				if v.Names[0] != tc.expected[i] {
					t.Fatalf("expected %s syscall, have %s", tc.expected[i], v.Names[0])
				}
			}
		})
	}
}

// createSpec() creates a minimum spec for testing
func createSpec(caps ...string) specs.Spec {
	rs := specs.Spec{
		Process: &specs.Process{
			Capabilities: &specs.LinuxCapabilities{},
		},
	}
	if caps != nil {
		rs.Process.Capabilities.Bounding = append(rs.Process.Capabilities.Bounding, caps...)
	}
	return rs
}
<commit_msg>seccomp: use oci-spec consts in tests<commit_after>// +build linux

package seccomp // import "github.com/docker/docker/profiles/seccomp"

import (
	"encoding/json"
	"io/ioutil"
	"strings"
	"testing"

	"github.com/opencontainers/runtime-spec/specs-go"
	"gotest.tools/v3/assert"
)

func TestLoadProfile(t *testing.T) {
	f, err := ioutil.ReadFile("fixtures/example.json")
	if err != nil {
		t.Fatal(err)
	}
	rs := createSpec()
	p, err := LoadProfile(string(f), &rs)
	if err != nil {
		t.Fatal(err)
	}
	var expectedErrno uint = 12345
	expected := specs.LinuxSeccomp{
		DefaultAction: specs.ActErrno,
		Syscalls: []specs.LinuxSyscall{
			{
				Names:  []string{"clone"},
				Action: specs.ActAllow,
				Args: []specs.LinuxSeccompArg{{
					Index:    0,
					Value:    2114060288,
					ValueTwo: 0,
					Op:       specs.OpMaskedEqual,
				}},
			},
			{

				Names:  []string{"open"},
				Action: specs.ActAllow,
				Args:   []specs.LinuxSeccompArg{},
			},
			{
				Names:  []string{"close"},
				Action: specs.ActAllow,
				Args:   []specs.LinuxSeccompArg{},
			},
			{
				Names:    []string{"syslog"},
				Action:   specs.ActErrno,
				ErrnoRet: &expectedErrno,
				Args:     []specs.LinuxSeccompArg{},
			},
		},
	}

	assert.DeepEqual(t, expected, *p)
}

func TestLoadProfileWithDefaultErrnoRet(t *testing.T) {
	var profile = []byte(`{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 6
}`)
	rs := createSpec()
	p, err := LoadProfile(string(profile), &rs)
	if err != nil {
		t.Fatal(err)
	}

	expectedErrnoRet := uint(6)
	expected := specs.LinuxSeccomp{
		DefaultAction:   specs.ActErrno,
		DefaultErrnoRet: &expectedErrnoRet,
	}

	assert.DeepEqual(t, expected, *p)
}

func TestLoadProfileWithListenerPath(t *testing.T) {
	var profile = []byte(`{
"defaultAction": "SCMP_ACT_ERRNO",
"listenerPath": "/var/run/seccompaget.sock",
"listenerMetadata": "opaque-metadata"
}`)
	rs := createSpec()
	p, err := LoadProfile(string(profile), &rs)
	if err != nil {
		t.Fatal(err)
	}

	expected := specs.LinuxSeccomp{
		DefaultAction:    specs.ActErrno,
		ListenerPath:     "/var/run/seccompaget.sock",
		ListenerMetadata: "opaque-metadata",
	}

	assert.DeepEqual(t, expected, *p)
}

// TestLoadLegacyProfile tests loading a seccomp profile in the old format
// (before https://github.com/docker/docker/pull/24510)
func TestLoadLegacyProfile(t *testing.T) {
	f, err := ioutil.ReadFile("fixtures/default-old-format.json")
	if err != nil {
		t.Fatal(err)
	}
	rs := createSpec()
	if _, err := LoadProfile(string(f), &rs); err != nil {
		t.Fatal(err)
	}
}

func TestLoadDefaultProfile(t *testing.T) {
	f, err := ioutil.ReadFile("default.json")
	if err != nil {
		t.Fatal(err)
	}
	rs := createSpec()
	if _, err := LoadProfile(string(f), &rs); err != nil {
		t.Fatal(err)
	}
}

func TestUnmarshalDefaultProfile(t *testing.T) {
	expected := DefaultProfile()
	if expected == nil {
		t.Skip("seccomp not supported")
	}

	f, err := ioutil.ReadFile("default.json")
	if err != nil {
		t.Fatal(err)
	}
	var profile Seccomp
	err = json.Unmarshal(f, &profile)
	if err != nil {
		t.Fatal(err)
	}
	assert.DeepEqual(t, expected.Architectures, profile.Architectures)
	assert.DeepEqual(t, expected.ArchMap, profile.ArchMap)
	assert.DeepEqual(t, expected.DefaultAction, profile.DefaultAction)
	assert.DeepEqual(t, expected.Syscalls, profile.Syscalls)
}

func TestMarshalUnmarshalFilter(t *testing.T) {
	t.Parallel()
	tests := []struct {
		in    string
		out   string
		error bool
	}{
		{in: `{"arches":["s390x"],"minKernel":3}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":3.12}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":true}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":"0.0"}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":"3"}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":".3"}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":"3."}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":"true"}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":"3.12.1\""}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":"4.15abc"}`, error: true},
		{in: `{"arches":["s390x"],"minKernel":null}`, out: `{"arches":["s390x"]}`},
		{in: `{"arches":["s390x"],"minKernel":""}`, out: `{"arches":["s390x"],"minKernel":""}`}, // FIXME: try to fix omitempty for this
		{in: `{"arches":["s390x"],"minKernel":"0.5"}`, out: `{"arches":["s390x"],"minKernel":"0.5"}`},
		{in: `{"arches":["s390x"],"minKernel":"0.50"}`, out: `{"arches":["s390x"],"minKernel":"0.50"}`},
		{in: `{"arches":["s390x"],"minKernel":"5.0"}`, out: `{"arches":["s390x"],"minKernel":"5.0"}`},
		{in: `{"arches":["s390x"],"minKernel":"50.0"}`, out: `{"arches":["s390x"],"minKernel":"50.0"}`},
		{in: `{"arches":["s390x"],"minKernel":"4.15"}`, out: `{"arches":["s390x"],"minKernel":"4.15"}`},
	}
	for _, tc := range tests {
		tc := tc
		t.Run(tc.in, func(t *testing.T) {
			var filter Filter
			err := json.Unmarshal([]byte(tc.in), &filter)
			if tc.error {
				if err == nil {
					t.Fatal("expected an error")
				} else if !strings.Contains(err.Error(), "invalid kernel version") {
					t.Fatal("unexpected error:", err)
				}
				return
			}
			if err != nil {
				t.Fatal(err)
			}
			out, err := json.Marshal(filter)
			if err != nil {
				t.Fatal(err)
			}
			if string(out) != tc.out {
				t.Fatalf("expected %s, got %s", tc.out, string(out))
			}
		})
	}
}

func TestLoadConditional(t *testing.T) {
	f, err := ioutil.ReadFile("fixtures/conditional_include.json")
	if err != nil {
		t.Fatal(err)
	}
	tests := []struct {
		doc      string
		cap      string
		expected []string
	}{
		{doc: "no caps", expected: []string{"chmod", "ptrace"}},
		{doc: "with syslog", cap: "CAP_SYSLOG", expected: []string{"chmod", "syslog", "ptrace"}},
		{doc: "no ptrace", cap: "CAP_SYS_ADMIN", expected: []string{"chmod"}},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.doc, func(t *testing.T) {
			rs := createSpec(tc.cap)
			p, err := LoadProfile(string(f), &rs)
			if err != nil {
				t.Fatal(err)
			}
			if len(p.Syscalls) != len(tc.expected) {
				t.Fatalf("expected %d syscalls in profile, have %d", len(tc.expected), len(p.Syscalls))
			}
			for i, v := range p.Syscalls {
				if v.Names[0] != tc.expected[i] {
					t.Fatalf("expected %s syscall, have %s", tc.expected[i], v.Names[0])
				}
			}
		})
	}
}

// createSpec() creates a minimum spec for testing
func createSpec(caps ...string) specs.Spec {
	rs := specs.Spec{
		Process: &specs.Process{
			Capabilities: &specs.LinuxCapabilities{},
		},
	}
	if caps != nil {
		rs.Process.Capabilities.Bounding = append(rs.Process.Capabilities.Bounding, caps...)
	}
	return rs
}
<|endoftext|>


<commit_before>package progress

import (
	"bytes"
	"fmt"
	"io"
	"regexp"
	"strings"

	"github.com/containerd/console"
)

var (
	regexCleanLine = regexp.MustCompile("\x1b\\[[0-9]+m[\x1b]?")
)

// Writer buffers writes until flush, at which time the last screen is cleared
// and the current buffer contents are written. This is useful for
// implementing progress displays, such as those implemented in docker and
// git.
type Writer struct {
	buf   bytes.Buffer
	w     io.Writer
	lines int
}

// NewWriter returns a writer
func NewWriter(w io.Writer) *Writer {
	return &Writer{
		w: w,
	}
}

// Write the provided bytes
func (w *Writer) Write(p []byte) (n int, err error) {
	return w.buf.Write(p)
}

// Flush should be called when refreshing the current display.
func (w *Writer) Flush() error {
	if w.buf.Len() == 0 {
		return nil
	}

	if err := w.clear(); err != nil {
		return err
	}

	ws, err := console.Current().Size()
	if err != nil {
		return fmt.Errorf("failed to get terminal width: %v", err)
	}
	width := int(ws.Width)
	if width == 0 {
		width = 80
	}
	strlines := strings.Split(w.buf.String(), "\n")
	w.lines = -1
	for _, line := range strlines {
		w.lines += (len(stripLine(line))-1)/width + 1
	}

	if _, err := w.w.Write(w.buf.Bytes()); err != nil {
		return err
	}

	w.buf.Reset()
	return nil
}

// TODO(stevvooe): The following are system specific. Break these out if we
// decide to build this package further.

func (w *Writer) clear() error {
	for i := 0; i < w.lines; i++ {
		if _, err := fmt.Fprintf(w.w, "\x1b[1A\x1b[2K\r"); err != nil {
			return err
		}
	}

	return nil
}

func stripLine(line string) string {
	return string(regexCleanLine.ReplaceAll([]byte(line), []byte{}))
}
<commit_msg>Dont clear lines if the console width is unavailable<commit_after>package progress

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"regexp"
	"strings"

	"github.com/containerd/console"
)

var (
	regexCleanLine = regexp.MustCompile("\x1b\\[[0-9]+m[\x1b]?")
)

// Writer buffers writes until flush, at which time the last screen is cleared
// and the current buffer contents are written. This is useful for
// implementing progress displays, such as those implemented in docker and
// git.
type Writer struct {
	buf   bytes.Buffer
	w     io.Writer
	lines int
}

// NewWriter returns a writer
func NewWriter(w io.Writer) *Writer {
	return &Writer{
		w: w,
	}
}

// Write the provided bytes
func (w *Writer) Write(p []byte) (n int, err error) {
	return w.buf.Write(p)
}

// Flush should be called when refreshing the current display.
func (w *Writer) Flush() error {
	if w.buf.Len() == 0 {
		return nil
	}

	if err := w.clearLines(); err != nil {
		return err
	}
	w.lines = countLines(w.buf.String())

	if _, err := w.w.Write(w.buf.Bytes()); err != nil {
		return err
	}

	w.buf.Reset()
	return nil
}

// TODO(stevvooe): The following are system specific. Break these out if we
// decide to build this package further.

func (w *Writer) clearLines() error {
	for i := 0; i < w.lines; i++ {
		if _, err := fmt.Fprintf(w.w, "\x1b[1A\x1b[2K\r"); err != nil {
			return err
		}
	}

	return nil
}

// countLines in the output. If a line is longer than the console width then
// an extra line is added to the count for each wrapped line. If the console
// width is undefined then 0 is returned so that no lines are cleared on the next
// flush.
func countLines(output string) int {
	con, err := console.ConsoleFromFile(os.Stdin)
	if err != nil {
		return 0
	}
	ws, err := con.Size()
	if err != nil {
		return 0
	}
	width := int(ws.Width)
	if width <= 0 {
		return 0
	}
	strlines := strings.Split(output, "\n")
	lines := -1
	for _, line := range strlines {
		lines += (len(stripLine(line))-1)/width + 1
	}
	return lines
}

func stripLine(line string) string {
	return string(regexCleanLine.ReplaceAll([]byte(line), []byte{}))
}
<|endoftext|>


<commit_before>// Client library for statsd.
package statsd

import (
	"bufio"
	"fmt"
	"io"
	"math/rand"
	"net"
	"time"
)

// A statsd client representing a connection to a statsd server.
type Client struct {
	Name string
	rw   *bufio.ReadWriter
}

func millisecond(d time.Duration) int {
	return int(d.Seconds() * 1000)
}

// Dial connects to the given address on the given network using net.Dial and then returns a new Client for the connection.
func Dial(addr string) (*Client, error) {
	rw, err := net.Dial("udp", addr)
	if err != nil {
		return nil, err
	}
	return newClient(addr, rw), nil
}

func DialTimeout(addr string, timeout time.Duration) (*Client, error) {
	rw, err := net.DialTimeout("udp", addr, timeout)
	if err != nil {
		return nil, err
	}
	return newClient(addr, rw), nil
}

func newClient(name string, rw io.ReadWriter) *Client {
	return &Client{
		name,
		bufio.NewReadWriter(bufio.NewReader(rw), bufio.NewWriter(rw)),
	}
}

// Increment the counter for the given bucket
func (c *Client) Increment(stat string, count int, rate float64) error {
	return c.send(stat, rate, "%d|c", count)
}

// Decrement the counter for the given bucket
func (c *Client) Decrement(stat string, count int, rate float64) error {
	return c.Increment(stat, -count, rate)
}

// Record time spend for the given bucket
func (c *Client) Timing(stat string, delta int, rate float64) error {
	return c.send(stat, rate, "%d|ms", delta)
}

// Calculate time spend in given function and send it
func (c *Client) Time(stat string, rate float64, f func()) error {
	ts := time.Now()
	f()
	delta := millisecond(time.Now().Sub(ts))
	return c.Timing(stat, delta, rate)
}

// Record arbitrary values for the given bucket
func (c *Client) Gauge(stat string, value int, rate float64) error {
	return c.send(stat, rate, "%d|g", value)
}

func (c *Client) send(stat string, rate float64, format string, args ...interface{}) error {
	if rate < 1 {
		if rand.Float64() < rate {
			format = fmt.Sprintf("%s|@%1.2f", format, rate)
		} else {
			return nil
		}
	}

	format = fmt.Sprintf("%s:%s", stat, format)
	_, err := fmt.Fprintf(c.rw, format, args...)
	if err != nil {
		return err
	}

	err = c.rw.Flush()
	return err
}
<commit_msg>Made socket writing threadsafe<commit_after>// Client library for statsd.
package statsd

import (
	"bufio"
	"fmt"
	"io"
	"math/rand"
	"net"
	"sync"
	"time"
)

// A statsd client representing a connection to a statsd server.
type Client struct {
	Name string
	rw   *bufio.ReadWriter
	sync.Mutex
}

func millisecond(d time.Duration) int {
	return int(d.Seconds() * 1000)
}

// Dial connects to the given address on the given network using net.Dial and then returns a new Client for the connection.
func Dial(addr string) (*Client, error) {
	rw, err := net.Dial("udp", addr)
	if err != nil {
		return nil, err
	}
	return newClient(addr, rw), nil
}

func DialTimeout(addr string, timeout time.Duration) (*Client, error) {
	rw, err := net.DialTimeout("udp", addr, timeout)
	if err != nil {
		return nil, err
	}
	return newClient(addr, rw), nil
}

func newClient(name string, rw io.ReadWriter) *Client {
	return &Client{
		Name: name,
		rw:   bufio.NewReadWriter(bufio.NewReader(rw), bufio.NewWriter(rw)),
	}
}

// Increment the counter for the given bucket
func (c *Client) Increment(stat string, count int, rate float64) error {
	return c.send(stat, rate, "%d|c", count)
}

// Decrement the counter for the given bucket
func (c *Client) Decrement(stat string, count int, rate float64) error {
	return c.Increment(stat, -count, rate)
}

// Record time spend for the given bucket
func (c *Client) Timing(stat string, delta int, rate float64) error {
	return c.send(stat, rate, "%d|ms", delta)
}

// Calculate time spend in given function and send it
func (c *Client) Time(stat string, rate float64, f func()) error {
	ts := time.Now()
	f()
	delta := millisecond(time.Now().Sub(ts))
	return c.Timing(stat, delta, rate)
}

// Record arbitrary values for the given bucket
func (c *Client) Gauge(stat string, value int, rate float64) error {
	return c.send(stat, rate, "%d|g", value)
}

func (c *Client) send(stat string, rate float64, format string, args ...interface{}) error {
	if rate < 1 {
		if rand.Float64() < rate {
			format = fmt.Sprintf("%s|@%1.2f", format, rate)
		} else {
			return nil
		}
	}

	format = fmt.Sprintf("%s:%s", stat, format)

	c.Lock()
	defer c.Unlock()

	_, err := fmt.Fprintf(c.rw, format, args...)
	if err != nil {
		return err
	}

	err = c.rw.Flush()
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/hashicorp/raft"
	"github.com/robustirc/robustirc/config"
	"github.com/robustirc/robustirc/ircserver"
	"github.com/robustirc/robustirc/types"
	"github.com/robustirc/robustirc/util"

	pb "github.com/robustirc/robustirc/proto"
)

//go:generate go run gentmpl.go status irclog

func handleStatus(res http.ResponseWriter, req *http.Request) {
	p, _ := peerStore.Peers()

	// robustirc-rollingrestart wants a machine-readable version of the status.
	if req.Header.Get("Accept") == "application/json" {
		type jsonStatus struct {
			State          string
			Leader         string
			Peers          []string
			AppliedIndex   uint64
			CommitIndex    uint64
			LastContact    time.Time
			ExecutableHash string
			CurrentTime    time.Time
		}
		res.Header().Set("Content-Type", "application/json")
		leaderStr := node.Leader()
		stats := node.Stats()
		appliedIndex, err := strconv.ParseUint(stats["applied_index"], 0, 64)
		if err != nil {
			http.Error(res, err.Error(), http.StatusInternalServerError)
			return
		}
		commitIndex, err := strconv.ParseUint(stats["commit_index"], 0, 64)
		if err != nil {
			http.Error(res, err.Error(), http.StatusInternalServerError)
			return
		}
		if err := json.NewEncoder(res).Encode(jsonStatus{
			State:          node.State().String(),
			Leader:         leaderStr,
			AppliedIndex:   appliedIndex,
			CommitIndex:    commitIndex,
			Peers:          p,
			LastContact:    node.LastContact(),
			ExecutableHash: executablehash,
			CurrentTime:    time.Now(),
		}); err != nil {
			log.Printf("%v\n", err)
			http.Error(res, err.Error(), http.StatusInternalServerError)
		}
		return
	}

	lo, err := ircStore.FirstIndex()
	if err != nil {
		log.Printf("Could not get first index: %v", err)
		http.Error(res, "internal error", 500)
		return
	}
	hi, err := ircStore.LastIndex()
	if err != nil {
		log.Printf("Could not get last index: %v", err)
		http.Error(res, "internal error", 500)
		return
	}

	// Show the last 50 messages by default.
	if hi > 50 && hi-50 > lo {
		lo = hi - 50
	}

	if offsetStr := req.FormValue("offset"); offsetStr != "" {
		offset, err := strconv.ParseInt(offsetStr, 0, 64)
		if err != nil {
			http.Error(res, err.Error(), http.StatusBadRequest)
			return
		}
		lo = uint64(offset)
	}

	if hi > lo+50 {
		hi = lo + 50
	}

	var entries []*raft.Log
	if lo != 0 && hi != 0 {
		for i := lo; i <= hi; i++ {
			l := new(raft.Log)

			if err := ircStore.GetLog(i, l); err != nil {
				// Not every message goes into the ircStore (e.g. raft peer change
				// messages do not).
				continue
			}
			if l.Type == raft.LogCommand {
				msg := types.NewRobustMessageFromBytes(l.Data)
				msg.Data = msg.PrivacyFilter()
				l.Data, _ = json.Marshal(&msg)
			}
			entries = append(entries, l)
		}
	}

	prevOffset := int64(lo) - 50
	if prevOffset < 0 {
		prevOffset = 1
	}

	textState := "state serialization failed"
	state, err := ircServer.Marshal(0)
	if err != nil {
		textState = fmt.Sprintf("state serialization failed: %v", err)
	} else {
		var snapshot pb.Snapshot
		if err := proto.Unmarshal(state, &snapshot); err != nil {
			textState = fmt.Sprintf("unmarshaling state failed: %v", err)
		} else {
			snapshot = util.PrivacyFilterSnapshot(snapshot)
			var marshaler proto.TextMarshaler
			textState = marshaler.Text(&snapshot)
		}
	}

	args := struct {
		Addr               string
		State              raft.RaftState
		Leader             string
		Peers              []string
		First              uint64
		Last               uint64
		Entries            []*raft.Log
		Stats              map[string]string
		Sessions           map[types.RobustId]ircserver.Session
		GetMessageRequests map[string]GetMessageStats
		PrevOffset         int64
		NextOffset         uint64
		NetConfig          config.Network
		ServerState        string
	}{
		*peerAddr,
		node.State(),
		node.Leader(),
		p,
		lo,
		hi,
		entries,
		node.Stats(),
		ircServer.GetSessions(),
		GetMessageRequests,
		prevOffset,
		lo + 50,
		ircServer.Config,
		textState,
	}

	statusTpl.Execute(res, args)
}

func handleIrclog(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(r.FormValue("sessionid"), 0, 64)
	if err != nil || id == 0 {
		http.Error(w, "Invalid session", http.StatusBadRequest)
		return
	}

	session := types.RobustId{Id: id}

	// TODO(secure): pagination

	var messages []*types.RobustMessage
	first, _ := ircStore.FirstIndex()
	last, _ := ircStore.LastIndex()
	for idx := first; idx <= last; idx++ {
		var elog raft.Log

		if err := ircStore.GetLog(idx, &elog); err != nil {
			// Not every message goes into the ircStore (e.g. raft peer change
			// messages do not).
			continue
		}
		if elog.Type != raft.LogCommand {
			continue
		}
		msg := types.NewRobustMessageFromBytes(elog.Data)
		if msg.Session.Id == session.Id {
			messages = append(messages, &msg)
		}
		output, ok := ircServer.Get(msg.Id)
		if ok {
			for _, msg := range output {
				if !msg.InterestingFor[session.Id] {
					continue
				}
				messages = append(messages, msg)
			}
		}
	}

	args := struct {
		Session  types.RobustId
		Messages []*types.RobustMessage
	}{
		session,
		messages,
	}
	if err := irclogTpl.Execute(w, args); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}
<commit_msg>irclog: switch to bulk iterator<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/hashicorp/raft"
	"github.com/robustirc/robustirc/config"
	"github.com/robustirc/robustirc/ircserver"
	"github.com/robustirc/robustirc/types"
	"github.com/robustirc/robustirc/util"

	pb "github.com/robustirc/robustirc/proto"
)

//go:generate go run gentmpl.go status irclog

func handleStatus(res http.ResponseWriter, req *http.Request) {
	p, _ := peerStore.Peers()

	// robustirc-rollingrestart wants a machine-readable version of the status.
	if req.Header.Get("Accept") == "application/json" {
		type jsonStatus struct {
			State          string
			Leader         string
			Peers          []string
			AppliedIndex   uint64
			CommitIndex    uint64
			LastContact    time.Time
			ExecutableHash string
			CurrentTime    time.Time
		}
		res.Header().Set("Content-Type", "application/json")
		leaderStr := node.Leader()
		stats := node.Stats()
		appliedIndex, err := strconv.ParseUint(stats["applied_index"], 0, 64)
		if err != nil {
			http.Error(res, err.Error(), http.StatusInternalServerError)
			return
		}
		commitIndex, err := strconv.ParseUint(stats["commit_index"], 0, 64)
		if err != nil {
			http.Error(res, err.Error(), http.StatusInternalServerError)
			return
		}
		if err := json.NewEncoder(res).Encode(jsonStatus{
			State:          node.State().String(),
			Leader:         leaderStr,
			AppliedIndex:   appliedIndex,
			CommitIndex:    commitIndex,
			Peers:          p,
			LastContact:    node.LastContact(),
			ExecutableHash: executablehash,
			CurrentTime:    time.Now(),
		}); err != nil {
			log.Printf("%v\n", err)
			http.Error(res, err.Error(), http.StatusInternalServerError)
		}
		return
	}

	lo, err := ircStore.FirstIndex()
	if err != nil {
		log.Printf("Could not get first index: %v", err)
		http.Error(res, "internal error", 500)
		return
	}
	hi, err := ircStore.LastIndex()
	if err != nil {
		log.Printf("Could not get last index: %v", err)
		http.Error(res, "internal error", 500)
		return
	}

	// Show the last 50 messages by default.
	if hi > 50 && hi-50 > lo {
		lo = hi - 50
	}

	if offsetStr := req.FormValue("offset"); offsetStr != "" {
		offset, err := strconv.ParseInt(offsetStr, 0, 64)
		if err != nil {
			http.Error(res, err.Error(), http.StatusBadRequest)
			return
		}
		lo = uint64(offset)
	}

	if hi > lo+50 {
		hi = lo + 50
	}

	var entries []*raft.Log
	if lo != 0 && hi != 0 {
		for i := lo; i <= hi; i++ {
			l := new(raft.Log)

			if err := ircStore.GetLog(i, l); err != nil {
				// Not every message goes into the ircStore (e.g. raft peer change
				// messages do not).
				continue
			}
			if l.Type == raft.LogCommand {
				msg := types.NewRobustMessageFromBytes(l.Data)
				msg.Data = msg.PrivacyFilter()
				l.Data, _ = json.Marshal(&msg)
			}
			entries = append(entries, l)
		}
	}

	prevOffset := int64(lo) - 50
	if prevOffset < 0 {
		prevOffset = 1
	}

	textState := "state serialization failed"
	state, err := ircServer.Marshal(0)
	if err != nil {
		textState = fmt.Sprintf("state serialization failed: %v", err)
	} else {
		var snapshot pb.Snapshot
		if err := proto.Unmarshal(state, &snapshot); err != nil {
			textState = fmt.Sprintf("unmarshaling state failed: %v", err)
		} else {
			snapshot = util.PrivacyFilterSnapshot(snapshot)
			var marshaler proto.TextMarshaler
			textState = marshaler.Text(&snapshot)
		}
	}

	args := struct {
		Addr               string
		State              raft.RaftState
		Leader             string
		Peers              []string
		First              uint64
		Last               uint64
		Entries            []*raft.Log
		Stats              map[string]string
		Sessions           map[types.RobustId]ircserver.Session
		GetMessageRequests map[string]GetMessageStats
		PrevOffset         int64
		NextOffset         uint64
		NetConfig          config.Network
		ServerState        string
	}{
		*peerAddr,
		node.State(),
		node.Leader(),
		p,
		lo,
		hi,
		entries,
		node.Stats(),
		ircServer.GetSessions(),
		GetMessageRequests,
		prevOffset,
		lo + 50,
		ircServer.Config,
		textState,
	}

	statusTpl.Execute(res, args)
}

func handleIrclog(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(r.FormValue("sessionid"), 0, 64)
	if err != nil || id == 0 {
		http.Error(w, "Invalid session", http.StatusBadRequest)
		return
	}

	session := types.RobustId{Id: id}

	// TODO(secure): pagination

	var messages []*types.RobustMessage
	first, _ := ircStore.FirstIndex()
	last, _ := ircStore.LastIndex()
	iterator := ircStore.GetBulkIterator(first, last+1)
	defer iterator.Release()
	for iterator.Next() {
		var elog raft.Log
		if err := json.Unmarshal(iterator.Value(), &elog); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		if elog.Type != raft.LogCommand {
			continue
		}
		msg := types.NewRobustMessageFromBytes(elog.Data)
		if msg.Session.Id == session.Id {
			messages = append(messages, &msg)
		}
		output, ok := ircServer.Get(msg.Id)
		if ok {
			for _, msg := range output {
				if !msg.InterestingFor[session.Id] {
					continue
				}
				messages = append(messages, msg)
			}
		}
	}

	if err := iterator.Error(); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	args := struct {
		Session  types.RobustId
		Messages []*types.RobustMessage
	}{
		session,
		messages,
	}
	if err := irclogTpl.Execute(w, args); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/codegangsta/cli"
)

const kiteHTTPResponse = "Welcome to SockJS!\n"

var defaultHealthChecker *HealthChecker

func init() {
	defaultHealthChecker = &HealthChecker{
		HTTPClient: &http.Client{
			Timeout: 4 * time.Second,
		},
		LocalKiteAddress:  KlientAddress,
		RemoteKiteAddress: KontrolURL,
		RemoteHTTPAddress: S3UpdateLocation,
	}
}

// HealthChecker implements state for the various HealthCheck functions,
// ideal for mocking the health check interfaces (local kite, remote http,
// remote kite, etc)
type HealthChecker struct {
	HTTPClient *http.Client

	// Used for verifying a locally / remotely running kite
	LocalKiteAddress  string
	RemoteKiteAddress string

	// Used for verifying a working internet connection
	RemoteHTTPAddress string
}

// ErrHealthDialFailed is used when dialing klient itself is failing. Local or remote,
// it depends on the error message.
type ErrHealthDialFailed struct{ Message string }

// ErrHealthNoHTTPReponse is used when a klient is not returning an http
// response. Local or remote, it depends on the error message.
type ErrHealthNoHTTPReponse struct{ Message string }

// ErrHealthUnreadableKiteKey is used when we are unable to read the kite.key,
// so it either doesn't exist at the specified location or the permissions are
// broken relative to the current user.
type ErrHealthUnreadableKiteKey struct{ Message string }

// ErrHealthUnexpectedResponse is used when a klient's http response on
// kiteAddress:/kite does not match the "Welcome to SockJS!" response. Local or
// remote, it depends on the error message.
type ErrHealthUnexpectedResponse struct{ Message string }

// ErrHealthNoInternet is used when the http response to a reliable endpoint
// (Google.com, for example) was unable to connect. If this is the case, the
// user is having internet troubles.
type ErrHealthNoInternet struct{ Message string }

// ErrHealthNoKontrolHTTPResponse is used when the http response from
// https://koding.com/kontrol/kite failed. Koding itself might be down, or the
// users internet might be spotty.
type ErrHealthNoKontrolHTTPResponse struct{ Message string }

func (e ErrHealthDialFailed) Error() string            { return e.Message }
func (e ErrHealthNoHTTPReponse) Error() string         { return e.Message }
func (e ErrHealthUnreadableKiteKey) Error() string     { return e.Message }
func (e ErrHealthUnexpectedResponse) Error() string    { return e.Message }
func (e ErrHealthNoInternet) Error() string            { return e.Message }
func (e ErrHealthNoKontrolHTTPResponse) Error() string { return e.Message }

// StatusCommand informs the user about the status of the Klient service. It
// does this in multiple stages, to help identify specific problems.
//
// 1. First it checks if the expected localhost http response is
// available. If it isn't, klient is not running properly or something
// else had taken the port.
//
// 2. Next, it checks if the auth is working properly, by dialing
// klient. Because we already checked if the http response was working,
// something else may be wrong. Such as the key not existing, or
// somehow kd using the wrong key, etc.
//
// 3. Lastly it checks if the user's IP has the exposed klient port. This
// is not an error because outgoing klient communication will still work,
// but incoming klient functionality will obviously be limited. So by
// checking, we can inform the user.
func StatusCommand(c *cli.Context) int {
	res, ok := defaultHealthChecker.CheckAllWithResponse()

	fmt.Println(res)
	if !ok {
		return 1
	}

	return 0
}

// CheckLocal runs several diagnostics on the local Klient. Errors
// indicate an unhealthy or not running Klient, and can be compare to
// the ErrHealth* types.
//
// TODO: Possibly return a set of warnings too? If we have any..
func (c *HealthChecker) CheckLocal() error {
	res, err := c.HTTPClient.Get(c.LocalKiteAddress)
	// If there was an error even talking to Klient, something is wrong.
	if err != nil {
		return ErrHealthNoHTTPReponse{Message: fmt.Sprintf(
			"The local klient /kite route is returning an error: '%s'", err,
		)}
	}
	defer res.Body.Close()

	// It should be safe to ignore any errors dumping the response data,
	// since we just want to check the data itself. Handling the error
	// might aid with debugging any problems though.
	resData, _ := ioutil.ReadAll(res.Body)
	if string(resData) != kiteHTTPResponse {
		return ErrHealthUnexpectedResponse{Message: fmt.Sprintf(
			"The local klient /kite route is returning an unexpected response: '%s'",
			string(resData),
		)}
	}

	// The only error CreateKlientClient returns (currently) is kite read
	// error, so we can handle that.
	k, err := CreateKlientClient(NewKlientOptions())
	if err != nil {
		return ErrHealthUnreadableKiteKey{Message: fmt.Sprintf(
			"The klient kite key is unable to be read. Reason: '%s'", err.Error(),
		)}
	}

	// TODO: Identify varing Dial errors to produce meaningful health
	// responses.
	if err = k.Dial(); err != nil {
		return ErrHealthDialFailed{Message: fmt.Sprintf(
			"Dailing local klient failed. Reason: %s", err,
		)}
	}

	return nil
}

// CheckRemote checks the integrity of the ability to connect
// to remote addresses, and thus verifying internet.
func (c *HealthChecker) CheckRemote() error {
	// Attempt to connect to google (or some reliable service) to
	// confirm the user's outbound internet connection.
	res, err := c.HTTPClient.Get(c.RemoteHTTPAddress)
	if err != nil {
		return ErrHealthNoInternet{Message: fmt.Sprintf(
			"The internet connection fails to '%s'. Reason: %s",
			c.RemoteHTTPAddress, err.Error(),
		)}
	}
	defer res.Body.Close()

	// Attempt to connect to kontrol's http page, simply to get an idea
	// if Koding is running or not.
	res, err = c.HTTPClient.Get(c.RemoteKiteAddress)
	if err != nil {
		return ErrHealthNoKontrolHTTPResponse{Message: fmt.Sprintf(
			"A http request to Kontrol failed. Reason: %s", err.Error(),
		)}
	}
	defer res.Body.Close()

	// Kontrol should return a 200 response.
	if res.StatusCode < 200 || res.StatusCode > 299 {
		return ErrHealthNoKontrolHTTPResponse{Message: fmt.Sprintf(
			"A http request to Kontrol returned bad status code. Code: %d",
			res.StatusCode,
		)}
	}

	// It should be safe to ignore any errors dumping the response data,
	// since we just want to check the data itself. Handling the error
	// might aid with debugging any problems though.
	//
	// TODO: Log the response if it's not as expected, to help
	// debug Cloudflare/nginx issues.
	resData, _ := ioutil.ReadAll(res.Body)
	if string(resData) != kiteHTTPResponse {
		return ErrHealthUnexpectedResponse{Message: fmt.Sprintf(
			"The '%s' route is returning an unexpected response: '%s'",
			c.RemoteKiteAddress, string(resData),
		)}
	}

	// TODO: Check the local ip address for an open port. We
	// need to implement a service on Koding to properly ip check though,
	// since we've been having problems with echoip.net failing.

	return nil
}

// CheckAllWithResponse checks local and remote, and parses the response to a
// user-friendly response. Because a response may be good or bad, a bool is also
// returned. If true, the response is good _(ie, positive, not an problem)_, and
// if it is false the response represents a problem.
//
// TODO: Enable debug logs
// log.Print(err.Error())
func (c *HealthChecker) CheckAllWithResponse() (res string, ok bool) {
	if err := defaultHealthChecker.CheckLocal(); err != nil {
		switch err.(type) {
		case ErrHealthNoHTTPReponse:
			res = fmt.Sprintf(
				`Error: The %s does not appear to be running. Please run
the following command to start it:

    sudo kd start
`,
				KlientName)

		case ErrHealthUnexpectedResponse:
			res = fmt.Sprintf(`Error: The %s is not running properly. Please run the
following command to restart it:

    sudo kd restart
`,
				KlientName)

		case ErrHealthUnreadableKiteKey:
			res = fmt.Sprintf(`Error: The authorization file for the %s is malformed
or missing. Please run the following command:

    sudo kd install
`,
				KlientName)

		// TODO: What are some good steps for the user to take if dial fails?
		case ErrHealthDialFailed:
			res = fmt.Sprintf(`Error: The %s does not appear to be running properly.
Please run the following command:

    sudo kd restart
`,
				KlientName)

		default:
			res = fmt.Sprintf("Unknown local healthcheck error: %s", err.Error())
		}

		return res, false
	}

	if err := defaultHealthChecker.CheckRemote(); err != nil {
		switch err.(type) {
		case ErrHealthNoInternet:
			res = fmt.Sprintf(`Error: You do not appear to have a properly working internet connection.`)

		case ErrHealthNoKontrolHTTPResponse:
			res = fmt.Sprintf(`Error: koding.com does not appear to be responding.
If this problem persists, please contact us at: support@koding.com
`)

		default:
			res = fmt.Sprintf("Unknown remote healthcheck error: %s", err.Error())
		}

		return res, false
	}

	res = fmt.Sprintf(
		"The %s appears to be running and is healthy\n", KlientName,
	)

	return res, true
}

// CheckAllFailureOrMessagef runs CheckAllWithResponse and if there is a failure,
// returns that status message. If CheckAllWithResponse returns ok, the formatted
// message is returned. This *does not* print success messages.
//
// This is a shorthand for informing the user about an error. There already was an
// error, we're just trying to inform the user what it was about. For comparison,
// here is the syntax that this method provides:
//
//     fmt.Println(defaultHealthChecker.FailureResponseOrMessagef(
//       "Error connecting to %s: '%s'\n", KlientName, err,
//     ))
//
// And here is the syntax we're avoiding:
//
//     s, ok := defaultHealthChecker.CheckAllWithResponse()
//     if ok {
//       fmt.Printf("Error connecting to %s: '%s'\n", KlientName, err)
//     } else {
//       fmt.Println(s)
//     }
func (c *HealthChecker) CheckAllFailureOrMessagef(f string, i ...interface{}) string {
	if s, ok := c.CheckAllWithResponse(); !ok {
		return s
	}

	return fmt.Sprintf(f, i...)
}

// IsKlientRunning does a quick check against klient's http server
// to verify that it is running. It does *not* check the auth or tcp
// connection, it *just* attempts to verify that klient is running.
func IsKlientRunning(a string) bool {
	res, err := http.Get(a)

	if res != nil {
		defer res.Body.Close()
	}

	// If there was an error even talking to Klient, something is wrong.
	if err != nil {
		return false
	}

	// It should be safe to ignore any errors dumping the response data,
	// since we just want to check the data itself. Handling the error
	// might aid with debugging any problems though.
	resData, _ := ioutil.ReadAll(res.Body)
	if string(resData) != kiteHTTPResponse {
		return false
	}

	return true
}
<commit_msg>better format kd status output<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/codegangsta/cli"
)

const kiteHTTPResponse = "Welcome to SockJS!\n"

var defaultHealthChecker *HealthChecker

func init() {
	defaultHealthChecker = &HealthChecker{
		HTTPClient: &http.Client{
			Timeout: 4 * time.Second,
		},
		LocalKiteAddress:  KlientAddress,
		RemoteKiteAddress: KontrolURL,
		RemoteHTTPAddress: S3UpdateLocation,
	}
}

// HealthChecker implements state for the various HealthCheck functions,
// ideal for mocking the health check interfaces (local kite, remote http,
// remote kite, etc)
type HealthChecker struct {
	HTTPClient *http.Client

	// Used for verifying a locally / remotely running kite
	LocalKiteAddress  string
	RemoteKiteAddress string

	// Used for verifying a working internet connection
	RemoteHTTPAddress string
}

// ErrHealthDialFailed is used when dialing klient itself is failing. Local or remote,
// it depends on the error message.
type ErrHealthDialFailed struct{ Message string }

// ErrHealthNoHTTPReponse is used when a klient is not returning an http
// response. Local or remote, it depends on the error message.
type ErrHealthNoHTTPReponse struct{ Message string }

// ErrHealthUnreadableKiteKey is used when we are unable to read the kite.key,
// so it either doesn't exist at the specified location or the permissions are
// broken relative to the current user.
type ErrHealthUnreadableKiteKey struct{ Message string }

// ErrHealthUnexpectedResponse is used when a klient's http response on
// kiteAddress:/kite does not match the "Welcome to SockJS!" response. Local or
// remote, it depends on the error message.
type ErrHealthUnexpectedResponse struct{ Message string }

// ErrHealthNoInternet is used when the http response to a reliable endpoint
// (Google.com, for example) was unable to connect. If this is the case, the
// user is having internet troubles.
type ErrHealthNoInternet struct{ Message string }

// ErrHealthNoKontrolHTTPResponse is used when the http response from
// https://koding.com/kontrol/kite failed. Koding itself might be down, or the
// users internet might be spotty.
type ErrHealthNoKontrolHTTPResponse struct{ Message string }

func (e ErrHealthDialFailed) Error() string            { return e.Message }
func (e ErrHealthNoHTTPReponse) Error() string         { return e.Message }
func (e ErrHealthUnreadableKiteKey) Error() string     { return e.Message }
func (e ErrHealthUnexpectedResponse) Error() string    { return e.Message }
func (e ErrHealthNoInternet) Error() string            { return e.Message }
func (e ErrHealthNoKontrolHTTPResponse) Error() string { return e.Message }

// StatusCommand informs the user about the status of the Klient service. It
// does this in multiple stages, to help identify specific problems.
//
// 1. First it checks if the expected localhost http response is
// available. If it isn't, klient is not running properly or something
// else had taken the port.
//
// 2. Next, it checks if the auth is working properly, by dialing
// klient. Because we already checked if the http response was working,
// something else may be wrong. Such as the key not existing, or
// somehow kd using the wrong key, etc.
//
// 3. Lastly it checks if the user's IP has the exposed klient port. This
// is not an error because outgoing klient communication will still work,
// but incoming klient functionality will obviously be limited. So by
// checking, we can inform the user.
func StatusCommand(c *cli.Context) int {
	res, ok := defaultHealthChecker.CheckAllWithResponse()

	fmt.Println(res)
	if !ok {
		return 1
	}

	return 0
}

// CheckLocal runs several diagnostics on the local Klient. Errors
// indicate an unhealthy or not running Klient, and can be compare to
// the ErrHealth* types.
//
// TODO: Possibly return a set of warnings too? If we have any..
func (c *HealthChecker) CheckLocal() error {
	res, err := c.HTTPClient.Get(c.LocalKiteAddress)
	// If there was an error even talking to Klient, something is wrong.
	if err != nil {
		return ErrHealthNoHTTPReponse{Message: fmt.Sprintf(
			"The local klient /kite route is returning an error: '%s'", err,
		)}
	}
	defer res.Body.Close()

	// It should be safe to ignore any errors dumping the response data,
	// since we just want to check the data itself. Handling the error
	// might aid with debugging any problems though.
	resData, _ := ioutil.ReadAll(res.Body)
	if string(resData) != kiteHTTPResponse {
		return ErrHealthUnexpectedResponse{Message: fmt.Sprintf(
			"The local klient /kite route is returning an unexpected response: '%s'",
			string(resData),
		)}
	}

	// The only error CreateKlientClient returns (currently) is kite read
	// error, so we can handle that.
	k, err := CreateKlientClient(NewKlientOptions())
	if err != nil {
		return ErrHealthUnreadableKiteKey{Message: fmt.Sprintf(
			"The klient kite key is unable to be read. Reason: '%s'", err.Error(),
		)}
	}

	// TODO: Identify varing Dial errors to produce meaningful health
	// responses.
	if err = k.Dial(); err != nil {
		return ErrHealthDialFailed{Message: fmt.Sprintf(
			"Dailing local klient failed. Reason: %s", err,
		)}
	}

	return nil
}

// CheckRemote checks the integrity of the ability to connect
// to remote addresses, and thus verifying internet.
func (c *HealthChecker) CheckRemote() error {
	// Attempt to connect to google (or some reliable service) to
	// confirm the user's outbound internet connection.
	res, err := c.HTTPClient.Get(c.RemoteHTTPAddress)
	if err != nil {
		return ErrHealthNoInternet{Message: fmt.Sprintf(
			"The internet connection fails to '%s'. Reason: %s",
			c.RemoteHTTPAddress, err.Error(),
		)}
	}
	defer res.Body.Close()

	// Attempt to connect to kontrol's http page, simply to get an idea
	// if Koding is running or not.
	res, err = c.HTTPClient.Get(c.RemoteKiteAddress)
	if err != nil {
		return ErrHealthNoKontrolHTTPResponse{Message: fmt.Sprintf(
			"A http request to Kontrol failed. Reason: %s", err.Error(),
		)}
	}
	defer res.Body.Close()

	// Kontrol should return a 200 response.
	if res.StatusCode < 200 || res.StatusCode > 299 {
		return ErrHealthNoKontrolHTTPResponse{Message: fmt.Sprintf(
			"A http request to Kontrol returned bad status code. Code: %d",
			res.StatusCode,
		)}
	}

	// It should be safe to ignore any errors dumping the response data,
	// since we just want to check the data itself. Handling the error
	// might aid with debugging any problems though.
	//
	// TODO: Log the response if it's not as expected, to help
	// debug Cloudflare/nginx issues.
	resData, _ := ioutil.ReadAll(res.Body)
	if string(resData) != kiteHTTPResponse {
		return ErrHealthUnexpectedResponse{Message: fmt.Sprintf(
			"The '%s' route is returning an unexpected response: '%s'",
			c.RemoteKiteAddress, string(resData),
		)}
	}

	// TODO: Check the local ip address for an open port. We
	// need to implement a service on Koding to properly ip check though,
	// since we've been having problems with echoip.net failing.

	return nil
}

// CheckAllWithResponse checks local and remote, and parses the response to a
// user-friendly response. Because a response may be good or bad, a bool is also
// returned. If true, the response is good _(ie, positive, not an problem)_, and
// if it is false the response represents a problem.
//
// TODO: Enable debug logs
// log.Print(err.Error())
func (c *HealthChecker) CheckAllWithResponse() (res string, ok bool) {
	if err := defaultHealthChecker.CheckLocal(); err != nil {
		switch err.(type) {
		case ErrHealthNoHTTPReponse:
			res = fmt.Sprintf(
				`Error: The %s does not appear to be running. Please run
the following command to start it:

    sudo kd start
`,
				KlientName)

		case ErrHealthUnexpectedResponse:
			res = fmt.Sprintf(`Error: The %s is not running properly. Please run the
following command to restart it:

    sudo kd restart
`,
				KlientName)

		case ErrHealthUnreadableKiteKey:
			res = fmt.Sprintf(`Error: The authorization file for the %s is malformed
or missing. Please run the following command:

    sudo kd install
`,
				KlientName)

		// TODO: What are some good steps for the user to take if dial fails?
		case ErrHealthDialFailed:
			res = fmt.Sprintf(`Error: The %s does not appear to be running properly.
Please run the following command:

    sudo kd restart
`,
				KlientName)

		default:
			res = fmt.Sprintf("Unknown local healthcheck error: %s", err.Error())
		}

		return res, false
	}

	if err := defaultHealthChecker.CheckRemote(); err != nil {
		switch err.(type) {
		case ErrHealthNoInternet:
			res = fmt.Sprintf(`Error: You do not appear to have a properly working internet connection.`)

		case ErrHealthNoKontrolHTTPResponse:
			res = fmt.Sprintf(`Error: koding.com does not appear to be responding.
If this problem persists, please contact us at: support@koding.com
`)

		default:
			res = fmt.Sprintf("Unknown remote healthcheck error: %s", err.Error())
		}

		return res, false
	}

	res = fmt.Sprintf(
		"The %s appears to be running and is healthy.", KlientName,
	)

	return res, true
}

// CheckAllFailureOrMessagef runs CheckAllWithResponse and if there is a failure,
// returns that status message. If CheckAllWithResponse returns ok, the formatted
// message is returned. This *does not* print success messages.
//
// This is a shorthand for informing the user about an error. There already was an
// error, we're just trying to inform the user what it was about. For comparison,
// here is the syntax that this method provides:
//
//     fmt.Println(defaultHealthChecker.FailureResponseOrMessagef(
//       "Error connecting to %s: '%s'\n", KlientName, err,
//     ))
//
// And here is the syntax we're avoiding:
//
//     s, ok := defaultHealthChecker.CheckAllWithResponse()
//     if ok {
//       fmt.Printf("Error connecting to %s: '%s'\n", KlientName, err)
//     } else {
//       fmt.Println(s)
//     }
func (c *HealthChecker) CheckAllFailureOrMessagef(f string, i ...interface{}) string {
	if s, ok := c.CheckAllWithResponse(); !ok {
		return s
	}

	return fmt.Sprintf(f, i...)
}

// IsKlientRunning does a quick check against klient's http server
// to verify that it is running. It does *not* check the auth or tcp
// connection, it *just* attempts to verify that klient is running.
func IsKlientRunning(a string) bool {
	res, err := http.Get(a)

	if res != nil {
		defer res.Body.Close()
	}

	// If there was an error even talking to Klient, something is wrong.
	if err != nil {
		return false
	}

	// It should be safe to ignore any errors dumping the response data,
	// since we just want to check the data itself. Handling the error
	// might aid with debugging any problems though.
	resData, _ := ioutil.ReadAll(res.Body)
	if string(resData) != kiteHTTPResponse {
		return false
	}

	return true
}
<|endoftext|>


<commit_before>package fidias

import chord "github.com/hexablock/go-chord"

// Status contains status information of a node.
type Status struct {
	Ring *chord.Status
}

// Status returns the status of this node
func (fidias *Fidias) Status() *Status {
	return &Status{
		Ring: fidias.ring.Status(),
	}
}
<commit_msg>Updated status<commit_after>package fidias

import (
	chord "github.com/hexablock/go-chord"
	"github.com/hexablock/hexatype"
)

// Status contains status information of a node.
type Status struct {
	Hash hexatype.HashAlgorithm
	DHT  *chord.Status
}

// Status returns the status of this node
func (fidias *Fidias) Status() *Status {
	return &Status{
		Hash: fidias.conf.Hasher().Algorithm(),
		DHT:  fidias.ring.Status(),
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2014 Dmitry Ponomarev <demdxx@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package gocast

import (
  "reflect"
  "strings"
)

func ToStruct(dst, src interface{}, tag string) (err error) {
  if nil == dst || nil == src {
    err = errInvalidParams
  } else {
    s := reflectTarget(reflect.ValueOf(dst))
    t := s.Type()

    switch src.(type) {
    case map[interface{}]interface{}, map[string]interface{}, map[string]string:
      for i := 0; i < s.NumField(); i++ {
        f := s.Field(i)
        v := mapValueByStringKeys(src, fieldNames(t.Field(i), tag))
        f.Set(reflect.ValueOf(To(v, f.Kind())))
      }
      break
    default:
      err = errUnsupportedType
    }
  }
  return
}

func StructFields(st interface{}, tag string) []string {
  fields := []string{}

  s := reflectTarget(reflect.ValueOf(st))
  t := s.Type()

  for i := 0; i < s.NumField(); i++ {
    fname := fieldName(t.Field(i), tag)
    if len(fname) > 0 && "-" != fname {
      fields = append(fields, fname)
    }
  }
  return fields
}

func StructFieldTags(st interface{}, tag string) map[string]string {
  fields := map[string]string{}

  s := reflectTarget(reflect.ValueOf(st))
  t := s.Type()

  for i := 0; i < s.NumField(); i++ {
    f := t.Field(i)
    tag := fieldTag(f, tag)
    if len(tag) > 0 && "-" != tag {
      fields[f.Name] = tag
    }
  }
  return fields
}

///////////////////////////////////////////////////////////////////////////////
/// MARK: Helpers
///////////////////////////////////////////////////////////////////////////////

var fieldNameArr = []string{"field", "schema", "sql", "json", "xml", "yaml"}

func fieldName(f reflect.StructField, tag string) string {
  return fieldNames(f, tag)[0]
}

func fieldNames(f reflect.StructField, tag string) []string {
  return strings.Split(fieldTag(f, tag), ",")
}

func fieldTag(f reflect.StructField, tag string) string {
  if "-" != tag {
    var fields string
    var tags []string

    if len(tag) > 0 {
      tags = strings.Split(tag, ",")
    } else {
      tags = fieldNameArr
    }

    for _, k := range tags {
      fields = f.Tag.Get(k)
      if len(fields) > 0 {
        break
      }
    }

    if len(fields) > 0 {
      if "-" == fields {
        return ""
      }
      return fields
    }
  }
  return f.Name
}
<commit_msg>Add function StructFieldTagsUnsorted<commit_after>// Copyright (c) 2014 Dmitry Ponomarev <demdxx@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package gocast

import (
  "reflect"
  "strings"
)

func ToStruct(dst, src interface{}, tag string) (err error) {
  if nil == dst || nil == src {
    err = errInvalidParams
  } else {
    s := reflectTarget(reflect.ValueOf(dst))
    t := s.Type()

    switch src.(type) {
    case map[interface{}]interface{}, map[string]interface{}, map[string]string:
      for i := 0; i < s.NumField(); i++ {
        f := s.Field(i)
        v := mapValueByStringKeys(src, fieldNames(t.Field(i), tag))
        f.Set(reflect.ValueOf(To(v, f.Kind())))
      }
      break
    default:
      err = errUnsupportedType
    }
  }
  return
}

func StructFields(st interface{}, tag string) []string {
  fields := []string{}

  s := reflectTarget(reflect.ValueOf(st))
  t := s.Type()

  for i := 0; i < s.NumField(); i++ {
    fname := fieldName(t.Field(i), tag)
    if len(fname) > 0 && "-" != fname {
      fields = append(fields, fname)
    }
  }
  return fields
}

func StructFieldTags(st interface{}, tag string) map[string]string {
  fields := map[string]string{}
  keys, values := StructFieldTagsUnsorted(st, tag)

  for i, k := range keys {
    fields[k] = values[i]
  }
  return fields
}

func StructFieldTagsUnsorted(st interface{}, tag string) ([]string, []string) {
  keys := []string{}
  values := []string{}

  s := reflectTarget(reflect.ValueOf(st))
  t := s.Type()

  for i := 0; i < s.NumField(); i++ {
    f := t.Field(i)
    tag := fieldTag(f, tag)
    if len(tag) > 0 && "-" != tag {
      keys = append(keys, f.Name)
      values = append(values, tag)
    }
  }
  return keys, values
}

///////////////////////////////////////////////////////////////////////////////
/// MARK: Helpers
///////////////////////////////////////////////////////////////////////////////

var fieldNameArr = []string{"field", "schema", "sql", "json", "xml", "yaml"}

func fieldName(f reflect.StructField, tag string) string {
  return fieldNames(f, tag)[0]
}

func fieldNames(f reflect.StructField, tag string) []string {
  return strings.Split(fieldTag(f, tag), ",")
}

func fieldTag(f reflect.StructField, tag string) string {
  if "-" != tag {
    var fields string
    var tags []string

    if len(tag) > 0 {
      tags = strings.Split(tag, ",")
    } else {
      tags = fieldNameArr
    }

    for _, k := range tags {
      fields = f.Tag.Get(k)
      if len(fields) > 0 {
        break
      }
    }

    if len(fields) > 0 {
      if "-" == fields {
        return ""
      }
      return fields
    }
  }
  return f.Name
}
<|endoftext|>


<commit_before>// Copyright 2017 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package text offers functions to draw texts on an Ebiten's image.
//
// For the example using a TTF font, see font package in the examples.
package text

import (
	"image"
	"image/color"
	"math"
	"sync"

	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/internal/colormcache"
)

var (
	monotonicClock int64
)

func now() int64 {
	monotonicClock++
	return monotonicClock
}

func fixed26_6ToFloat64(x fixed.Int26_6) float64 {
	return float64(x>>6) + float64(x&((1<<6)-1))/float64(1<<6)
}

const (
	cacheLimit = 512 // This is an arbitrary number.
)

func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *glyphImage, x, y fixed.Int26_6, clr ebiten.ColorM) {
	if img == nil {
		return
	}

	b := getGlyphBounds(face, r)
	op := &ebiten.DrawImageOptions{}
	op.GeoM.Translate(fixed26_6ToFloat64(x+b.Min.X), fixed26_6ToFloat64(y+b.Min.Y))
	op.ColorM = clr
	_ = dst.DrawImage(img.image.SubImage(image.Rect(img.x, img.y, img.x+img.width, img.y+img.height)).(*ebiten.Image), op)
}

var (
	// Use pointers to avoid copying on browsers.
	glyphBoundsCache = map[font.Face]map[rune]*fixed.Rectangle26_6{}
)

func getGlyphBounds(face font.Face, r rune) *fixed.Rectangle26_6 {
	if _, ok := glyphBoundsCache[face]; !ok {
		glyphBoundsCache[face] = map[rune]*fixed.Rectangle26_6{}
	}
	if b, ok := glyphBoundsCache[face][r]; ok {
		return b
	}
	b, _, _ := face.GlyphBounds(r)
	glyphBoundsCache[face][r] = &b
	return &b
}

type glyphImage struct {
	image  *ebiten.Image
	x      int
	y      int
	width  int
	height int
}

type glyphImageCacheEntry struct {
	image *glyphImage
	atime int64
}

var (
	glyphImageCache = map[font.Face]map[rune]*glyphImageCacheEntry{}
	emptyGlyphs     = map[font.Face]map[rune]struct{}{}
)

func getGlyphImages(face font.Face, runes []rune) []*glyphImage {
	if _, ok := emptyGlyphs[face]; !ok {
		emptyGlyphs[face] = map[rune]struct{}{}
	}
	if _, ok := glyphImageCache[face]; !ok {
		glyphImageCache[face] = map[rune]*glyphImageCacheEntry{}
	}

	imgs := make([]*glyphImage, len(runes))
	glyphBounds := map[rune]*fixed.Rectangle26_6{}
	neededGlyphIndices := map[int]rune{}
	for i, r := range runes {
		if _, ok := emptyGlyphs[face][r]; ok {
			continue
		}

		if e, ok := glyphImageCache[face][r]; ok {
			e.atime = now()
			imgs[i] = e.image
			continue
		}

		b := getGlyphBounds(face, r)
		w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
		if w == 0 || h == 0 {
			emptyGlyphs[face][r] = struct{}{}
			continue
		}

		// TODO: What if len(runes) > cacheLimit?
		if len(glyphImageCache[face]) > cacheLimit {
			oldest := int64(math.MaxInt64)
			oldestKey := rune(-1)
			for r, e := range glyphImageCache[face] {
				if e.atime < oldest {
					oldestKey = r
					oldest = e.atime
				}
			}
			delete(glyphImageCache[face], oldestKey)
		}

		glyphBounds[r] = b
		neededGlyphIndices[i] = r
	}

	if len(neededGlyphIndices) > 0 {
		// TODO: What if w2 is too big (e.g. > 4096)?
		w2 := 0
		h2 := 0
		for _, b := range glyphBounds {
			w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
			w2 += w
			if h2 < h {
				h2 = h
			}
		}
		rgba := image.NewRGBA(image.Rect(0, 0, w2, h2))

		x := 0
		xs := map[rune]int{}
		for r, b := range glyphBounds {
			w := (b.Max.X - b.Min.X).Ceil()

			d := font.Drawer{
				Dst:  rgba,
				Src:  image.White,
				Face: face,
			}
			d.Dot = fixed.Point26_6{X: fixed.I(x) - b.Min.X, Y: -b.Min.Y}
			d.DrawString(string(r))
			xs[r] = x

			x += w
		}

		img, _ := ebiten.NewImageFromImage(rgba, ebiten.FilterDefault)
		for i, r := range neededGlyphIndices {
			b := glyphBounds[r]
			w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()

			g := &glyphImage{
				image:  img,
				x:      xs[r],
				y:      0,
				width:  w,
				height: h,
			}
			if _, ok := glyphImageCache[face][r]; !ok {
				glyphImageCache[face][r] = &glyphImageCacheEntry{
					image: g,
					atime: now(),
				}
			}
			imgs[i] = g
		}
	}
	return imgs
}

var textM sync.Mutex

// Draw draws a given text on a given destination image dst.
//
// face is the font for text rendering.
// (x, y) represents a 'dot' (period) position.
// Be careful that this doesn't represent left-upper corner position.
// clr is the color for text rendering.
//
// The '\n' newline character puts the following text on the next line.
// Line height is based on Metrics().Height of the font.
//
// Glyphs used for rendering are cached in least-recently-used way.
// It is OK to call Draw with a same text and a same face at every frame in terms of performance.
//
// Be careful that the passed font face is held by this package and is never released.
// This is a known issue (#498).
//
// Draw is concurrent-safe.
func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Color) {
	textM.Lock()
	defer textM.Unlock()

	fx, fy := fixed.I(x), fixed.I(y)
	prevR := rune(-1)

	faceHeight := face.Metrics().Height

	runes := []rune(text)
	glyphImgs := getGlyphImages(face, runes)
	colorm := colormcache.ColorToColorM(clr)

	for i, r := range runes {
		if prevR >= 0 {
			fx += face.Kern(prevR, r)
		}
		if r == '\n' {
			fx = fixed.I(x)
			fy += faceHeight
			prevR = rune(-1)
			continue
		}

		drawGlyph(dst, face, r, glyphImgs[i], fx, fy, colorm)
		fx += glyphAdvance(face, r)

		prevR = r
	}
}

// MeasureString returns the measured size of a given string using a given font.
// This method will return the exact size in pixels that a string drawn by Draw will be.
//
// text is the string that's being measured.
// face is the font for text rendering.
//
// Be careful that the passed font face is held by this package and is never released.
// This is a known issue (#498).
//
// MeasureString is concurrent-safe.
func MeasureString(text string, face font.Face) image.Point {
	textM.Lock()
	defer textM.Unlock()

	var w, h fixed.Int26_6

	m := face.Metrics()
	faceHeight := m.Height
	faceDescent := m.Descent

	fx, fy := fixed.I(0), fixed.I(0)
	prevR := rune(-1)

	runes := []rune(text)

	for _, r := range runes {
		if prevR >= 0 {
			fx += face.Kern(prevR, r)
		}
		if r == '\n' {
			fx = fixed.I(0)
			fy += faceHeight
			prevR = rune(-1)
			continue
		}

		fx += glyphAdvance(face, r)

		if fx > w {
			w = fx
		}
		if (fy + faceHeight) > h {
			h = fy + faceHeight
		}

		prevR = r
	}

	bounds := image.Point{
		X: int(math.Ceil(fixed26_6ToFloat64(w))),
		Y: int(math.Ceil(fixed26_6ToFloat64(h + faceDescent))),
	}

	return bounds
}
<commit_msg>text: Add comments<commit_after>// Copyright 2017 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package text offers functions to draw texts on an Ebiten's image.
//
// For the example using a TTF font, see font package in the examples.
package text

import (
	"image"
	"image/color"
	"math"
	"sync"

	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/internal/colormcache"
)

var (
	monotonicClock int64
)

func now() int64 {
	monotonicClock++
	return monotonicClock
}

func fixed26_6ToFloat64(x fixed.Int26_6) float64 {
	return float64(x>>6) + float64(x&((1<<6)-1))/float64(1<<6)
}

const (
	cacheLimit = 512 // This is an arbitrary number.
)

func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *glyphImage, x, y fixed.Int26_6, clr ebiten.ColorM) {
	if img == nil {
		return
	}

	b := getGlyphBounds(face, r)
	op := &ebiten.DrawImageOptions{}
	op.GeoM.Translate(fixed26_6ToFloat64(x+b.Min.X), fixed26_6ToFloat64(y+b.Min.Y))
	op.ColorM = clr
	_ = dst.DrawImage(img.image.SubImage(image.Rect(img.x, img.y, img.x+img.width, img.y+img.height)).(*ebiten.Image), op)
}

var (
	// Use pointers to avoid copying on browsers.
	glyphBoundsCache = map[font.Face]map[rune]*fixed.Rectangle26_6{}
)

func getGlyphBounds(face font.Face, r rune) *fixed.Rectangle26_6 {
	if _, ok := glyphBoundsCache[face]; !ok {
		glyphBoundsCache[face] = map[rune]*fixed.Rectangle26_6{}
	}
	if b, ok := glyphBoundsCache[face][r]; ok {
		return b
	}
	b, _, _ := face.GlyphBounds(r)
	glyphBoundsCache[face][r] = &b
	return &b
}

type glyphImage struct {
	image  *ebiten.Image
	x      int
	y      int
	width  int
	height int
}

type glyphImageCacheEntry struct {
	image *glyphImage
	atime int64
}

var (
	glyphImageCache = map[font.Face]map[rune]*glyphImageCacheEntry{}
	emptyGlyphs     = map[font.Face]map[rune]struct{}{}
)

func getGlyphImages(face font.Face, runes []rune) []*glyphImage {
	if _, ok := emptyGlyphs[face]; !ok {
		emptyGlyphs[face] = map[rune]struct{}{}
	}
	if _, ok := glyphImageCache[face]; !ok {
		glyphImageCache[face] = map[rune]*glyphImageCacheEntry{}
	}

	imgs := make([]*glyphImage, len(runes))
	glyphBounds := map[rune]*fixed.Rectangle26_6{}
	neededGlyphIndices := map[int]rune{}
	for i, r := range runes {
		if _, ok := emptyGlyphs[face][r]; ok {
			continue
		}

		if e, ok := glyphImageCache[face][r]; ok {
			e.atime = now()
			imgs[i] = e.image
			continue
		}

		b := getGlyphBounds(face, r)
		w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
		if w == 0 || h == 0 {
			emptyGlyphs[face][r] = struct{}{}
			continue
		}

		// TODO: What if len(runes) > cacheLimit?
		if len(glyphImageCache[face]) > cacheLimit {
			oldest := int64(math.MaxInt64)
			oldestKey := rune(-1)
			for r, e := range glyphImageCache[face] {
				if e.atime < oldest {
					oldestKey = r
					oldest = e.atime
				}
			}
			delete(glyphImageCache[face], oldestKey)
		}

		glyphBounds[r] = b
		neededGlyphIndices[i] = r
	}

	if len(neededGlyphIndices) > 0 {
		// TODO: What if w2 is too big (e.g. > 4096)?
		w2 := 0
		h2 := 0
		for _, b := range glyphBounds {
			w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
			w2 += w
			if h2 < h {
				h2 = h
			}
		}
		rgba := image.NewRGBA(image.Rect(0, 0, w2, h2))

		x := 0
		xs := map[rune]int{}
		for r, b := range glyphBounds {
			w := (b.Max.X - b.Min.X).Ceil()

			d := font.Drawer{
				Dst:  rgba,
				Src:  image.White,
				Face: face,
			}
			d.Dot = fixed.Point26_6{X: fixed.I(x) - b.Min.X, Y: -b.Min.Y}
			d.DrawString(string(r))
			xs[r] = x

			x += w
		}

		img, _ := ebiten.NewImageFromImage(rgba, ebiten.FilterDefault)
		for i, r := range neededGlyphIndices {
			b := glyphBounds[r]
			w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()

			g := &glyphImage{
				image:  img,
				x:      xs[r],
				y:      0,
				width:  w,
				height: h,
			}
			if _, ok := glyphImageCache[face][r]; !ok {
				glyphImageCache[face][r] = &glyphImageCacheEntry{
					image: g,
					atime: now(),
				}
			}
			imgs[i] = g
		}
	}
	return imgs
}

var textM sync.Mutex

// Draw draws a given text on a given destination image dst.
//
// face is the font for text rendering.
// (x, y) represents a 'dot' (period) position.
// Be careful that this doesn't represent left-upper corner position.
// clr is the color for text rendering.
//
// The '\n' newline character puts the following text on the next line.
// Line height is based on Metrics().Height of the font.
//
// Glyphs used for rendering are cached in least-recently-used way.
// It is OK to call Draw with a same text and a same face at every frame in terms of performance.
//
// Be careful that the passed font face is held by this package and is never released.
// This is a known issue (#498).
//
// Draw is concurrent-safe.
func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Color) {
	textM.Lock()
	defer textM.Unlock()

	fx, fy := fixed.I(x), fixed.I(y)
	prevR := rune(-1)

	faceHeight := face.Metrics().Height

	runes := []rune(text)
	glyphImgs := getGlyphImages(face, runes)
	colorm := colormcache.ColorToColorM(clr)

	for i, r := range runes {
		if prevR >= 0 {
			fx += face.Kern(prevR, r)
		}
		if r == '\n' {
			fx = fixed.I(x)
			fy += faceHeight
			prevR = rune(-1)
			continue
		}

		drawGlyph(dst, face, r, glyphImgs[i], fx, fy, colorm)
		fx += glyphAdvance(face, r)

		prevR = r
	}
}

// MeasureString returns the measured size of a given string using a given font.
// This method will return the exact size in pixels that a string drawn by Draw will be.
// text can include multiple lines.
//
// text is the string that's being measured.
// face is the font for text rendering.
//
// Be careful that the passed font face is held by this package and is never released.
// This is a known issue (#498).
//
// MeasureString is concurrent-safe.
func MeasureString(text string, face font.Face) image.Point {
	textM.Lock()
	defer textM.Unlock()

	var w, h fixed.Int26_6

	m := face.Metrics()
	faceHeight := m.Height
	faceDescent := m.Descent

	fx, fy := fixed.I(0), fixed.I(0)
	prevR := rune(-1)

	runes := []rune(text)

	for _, r := range runes {
		if prevR >= 0 {
			fx += face.Kern(prevR, r)
		}
		if r == '\n' {
			fx = fixed.I(0)
			fy += faceHeight
			prevR = rune(-1)
			continue
		}

		fx += glyphAdvance(face, r)

		if fx > w {
			w = fx
		}
		if (fy + faceHeight) > h {
			h = fy + faceHeight
		}

		prevR = r
	}

	bounds := image.Point{
		X: int(math.Ceil(fixed26_6ToFloat64(w))),
		Y: int(math.Ceil(fixed26_6ToFloat64(h + faceDescent))),
	}

	return bounds
}
<|endoftext|>