<commit_before>package utils

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"syscall"
	"testing"
)

func TestGenerateName(t *testing.T) {
	name, err := GenerateRandomName("veth", 5)
	if err != nil {
		t.Fatal(err)
	}

	expected := 5 + len("veth")
	if len(name) != expected {
		t.Fatalf("expected name to be %d chars but received %d", expected, len(name))
	}

	name, err = GenerateRandomName("veth", 65)
	if err != nil {
		t.Fatal(err)
	}

	expected = 64 + len("veth")
	if len(name) != expected {
		t.Fatalf("expected name to be %d chars but received %d", expected, len(name))
	}
}

var labelTest = []struct {
	labels        []string
	query         string
	expectedValue string
}{
	{[]string{"bundle=/path/to/bundle"}, "bundle", "/path/to/bundle"},
	{[]string{"test=a", "test=b"}, "bundle", ""},
	{[]string{"bundle=a", "test=b", "bundle=c"}, "bundle", "a"},
	{[]string{"", "test=a", "bundle=b"}, "bundle", "b"},
	{[]string{"test", "bundle=a"}, "bundle", "a"},
	{[]string{"test=a", "bundle="}, "bundle", ""},
}

func TestSearchLabels(t *testing.T) {
	for _, tt := range labelTest {
		if v := SearchLabels(tt.labels, tt.query); v != tt.expectedValue {
			t.Errorf("expected value '%s' for query '%s'; got '%s'", tt.expectedValue, tt.query, v)
		}
	}
}

func TestResolveRootfs(t *testing.T) {
	dir := "rootfs"
	os.Mkdir(dir, 0600)
	defer os.Remove(dir)

	path, err := ResolveRootfs(dir)
	if err != nil {
		t.Fatal(err)
	}
	pwd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	if path != fmt.Sprintf("%s/%s", pwd, "rootfs") {
		t.Errorf("expected rootfs to be abs and was %s", path)
	}
}

func TestResolveRootfsWithSymlink(t *testing.T) {
	dir := "rootfs"
	tmpDir, _ := filepath.EvalSymlinks(os.TempDir())
	os.Symlink(tmpDir, dir)
	defer os.Remove(dir)

	path, err := ResolveRootfs(dir)
	if err != nil {
		t.Fatal(err)
	}

	if path != tmpDir {
		t.Errorf("expected rootfs to be the real path %s and was %s", path, os.TempDir())
	}
}

func TestResolveRootfsWithNonExistingDir(t *testing.T) {
	_, err := ResolveRootfs("foo")
	if err == nil {
		t.Error("expected error to happen but received nil")
	}
}

func TestExitStatus(t *testing.T) {
	status := syscall.WaitStatus(0)
	ex := ExitStatus(status)
	if ex != 0 {
		t.Errorf("expected exit status to equal 0 and received %d", ex)
	}
}

func TestExitStatusSignaled(t *testing.T) {
	status := syscall.WaitStatus(2)
	ex := ExitStatus(status)
	if ex != 130 {
		t.Errorf("expected exit status to equal 130 and received %d", ex)
	}
}

func TestWriteJSON(t *testing.T) {
	person := struct {
		Name string
		Age  int
	}{
		Name: "Alice",
		Age:  30,
	}

	var b bytes.Buffer
	err := WriteJSON(&b, person)
	if err != nil {
		t.Fatal(err)
	}

	expected := `{"Name":"Alice","Age":30}`
	if b.String() != expected {
		t.Errorf("expected to write %s but was %s", expected, b.String())
	}
}

func TestCleanPath(t *testing.T) {
	path := CleanPath("")
	if path != "" {
		t.Errorf("expected to received empty string and received %s", path)
	}

	path = CleanPath("rootfs")
	if path != "rootfs" {
		t.Errorf("expected to received 'rootfs' and received %s", path)
	}

	path = CleanPath("../../../var")
	if path != "var" {
		t.Errorf("expected to received 'var' and received %s", path)
	}

	path = CleanPath("/../../../var")
	if path != "/var" {
		t.Errorf("expected to received '/var' and received %s", path)
	}
}
<commit_msg>Correction in util error messages<commit_after>package utils

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"syscall"
	"testing"
)

func TestGenerateName(t *testing.T) {
	name, err := GenerateRandomName("veth", 5)
	if err != nil {
		t.Fatal(err)
	}

	expected := 5 + len("veth")
	if len(name) != expected {
		t.Fatalf("expected name to be %d chars but received %d", expected, len(name))
	}

	name, err = GenerateRandomName("veth", 65)
	if err != nil {
		t.Fatal(err)
	}

	expected = 64 + len("veth")
	if len(name) != expected {
		t.Fatalf("expected name to be %d chars but received %d", expected, len(name))
	}
}

var labelTest = []struct {
	labels        []string
	query         string
	expectedValue string
}{
	{[]string{"bundle=/path/to/bundle"}, "bundle", "/path/to/bundle"},
	{[]string{"test=a", "test=b"}, "bundle", ""},
	{[]string{"bundle=a", "test=b", "bundle=c"}, "bundle", "a"},
	{[]string{"", "test=a", "bundle=b"}, "bundle", "b"},
	{[]string{"test", "bundle=a"}, "bundle", "a"},
	{[]string{"test=a", "bundle="}, "bundle", ""},
}

func TestSearchLabels(t *testing.T) {
	for _, tt := range labelTest {
		if v := SearchLabels(tt.labels, tt.query); v != tt.expectedValue {
			t.Errorf("expected value '%s' for query '%s'; got '%s'", tt.expectedValue, tt.query, v)
		}
	}
}

func TestResolveRootfs(t *testing.T) {
	dir := "rootfs"
	os.Mkdir(dir, 0600)
	defer os.Remove(dir)

	path, err := ResolveRootfs(dir)
	if err != nil {
		t.Fatal(err)
	}
	pwd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	if path != fmt.Sprintf("%s/%s", pwd, "rootfs") {
		t.Errorf("expected rootfs to be abs and was %s", path)
	}
}

func TestResolveRootfsWithSymlink(t *testing.T) {
	dir := "rootfs"
	tmpDir, _ := filepath.EvalSymlinks(os.TempDir())
	os.Symlink(tmpDir, dir)
	defer os.Remove(dir)

	path, err := ResolveRootfs(dir)
	if err != nil {
		t.Fatal(err)
	}

	if path != tmpDir {
		t.Errorf("expected rootfs to be the real path %s and was %s", path, os.TempDir())
	}
}

func TestResolveRootfsWithNonExistingDir(t *testing.T) {
	_, err := ResolveRootfs("foo")
	if err == nil {
		t.Error("expected error to happen but received nil")
	}
}

func TestExitStatus(t *testing.T) {
	status := syscall.WaitStatus(0)
	ex := ExitStatus(status)
	if ex != 0 {
		t.Errorf("expected exit status to equal 0 and received %d", ex)
	}
}

func TestExitStatusSignaled(t *testing.T) {
	status := syscall.WaitStatus(2)
	ex := ExitStatus(status)
	if ex != 130 {
		t.Errorf("expected exit status to equal 130 and received %d", ex)
	}
}

func TestWriteJSON(t *testing.T) {
	person := struct {
		Name string
		Age  int
	}{
		Name: "Alice",
		Age:  30,
	}

	var b bytes.Buffer
	err := WriteJSON(&b, person)
	if err != nil {
		t.Fatal(err)
	}

	expected := `{"Name":"Alice","Age":30}`
	if b.String() != expected {
		t.Errorf("expected to write %s but was %s", expected, b.String())
	}
}

func TestCleanPath(t *testing.T) {
	path := CleanPath("")
	if path != "" {
		t.Errorf("expected to receive empty string and received %s", path)
	}

	path = CleanPath("rootfs")
	if path != "rootfs" {
		t.Errorf("expected to receive 'rootfs' and received %s", path)
	}

	path = CleanPath("../../../var")
	if path != "var" {
		t.Errorf("expected to receive 'var' and received %s", path)
	}

	path = CleanPath("/../../../var")
	if path != "/var" {
		t.Errorf("expected to receive '/var' and received %s", path)
	}
}
<|endoftext|>


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

package queue

import (
	"crypto/rand"
	"errors"
	"fmt"
	"github.com/globocom/tsuru/log"
	"sync"
	"sync/atomic"
	"time"
)

const (
	stopped int32 = iota
	running
	stopping
)

// Handler is a thread safe generic handler for queue messages.
//
// When started, whenever a new message arrives, handler invokes F, giving the
// message as parameter. F is invoked in its own goroutine, so the handler can
// handle other messages as they arrive.
type Handler struct {
	F     func(*Message)
	state int32
	id    string
}

// Start starts the handler. It's safe to call this function multiple times.
func (h *Handler) Start() {
	r.add(h)
	if atomic.CompareAndSwapInt32(&h.state, stopped, running) {
		go h.loop()
	}
}

// DryRun changes the state of the handler, but does not start it.
//
// It's intended for using in tests. It returns an error if the handler is not
// stopped.
func (h *Handler) DryRun() error {
	if !atomic.CompareAndSwapInt32(&h.state, stopped, running) {
		return errors.New("Handler is not stopped.")
	}
	r.add(h)
	go h.fakeLoop()
	return nil
}

// Stop sends a signal to stop the handler, it won't stop the handler
// immediately. After calling Stop, one should call Wait for blocking until the
// handler is stopped.
//
// This method will return an error if the handler is not running.
func (h *Handler) Stop() error {
	if !atomic.CompareAndSwapInt32(&h.state, running, stopping) {
		return errors.New("Not running.")
	}
	r.remove(h)
	return nil
}

// Wait blocks until the handler is stopped.
func (h *Handler) Wait() {
	for atomic.LoadInt32(&h.state) != stopped {
		time.Sleep(1e3)
	}
}

func (h *Handler) fakeLoop() {
	for atomic.LoadInt32(&h.state) == running {
		time.Sleep(1e3)
	}
	atomic.StoreInt32(&h.state, stopped)
}

// loop will get messages from the queue and dispatch them to Handler.F.
func (h *Handler) loop() {
	for {
		if message, err := Get(1e9); err == nil {
			go h.F(message)
		} else if atomic.LoadInt32(&h.state) == running {
			log.Printf("Failed to get message from the queue: %s. Trying again...", err)
			continue
		} else {
			atomic.StoreInt32(&h.state, stopped)
			return
		}
	}
}

// registry stores references to all running handlers.
type registry struct {
	mut      sync.Mutex
	handlers map[string]*Handler
}

func newRegistry() *registry {
	return &registry{
		handlers: make(map[string]*Handler),
	}
}

func (r *registry) add(h *Handler) {
	if h.id == "" {
		var buf [16]byte
		rand.Read(buf[:])
		h.id = fmt.Sprintf("%x", buf)
	}
	r.mut.Lock()
	r.handlers[h.id] = h
	r.mut.Unlock()
}

func (r *registry) remove(h *Handler) {
	if h.id != "" {
		r.mut.Lock()
		delete(r.handlers, h.id)
		r.mut.Unlock()
	}
}

var r *registry = newRegistry()

// Preempt calls Stop and Wait for each running handler.
func Preempt() {
	var wg sync.WaitGroup
	r.mut.Lock()
	preemptable := r.handlers
	r.mut.Unlock()
	wg.Add(len(preemptable))
	for _, h := range preemptable {
		go func(h *Handler) {
			defer wg.Done()
			h.Stop()
			h.Wait()
		}(h)
	}
	wg.Wait()
}
<commit_msg>queue: change Preempt so it copy the map<commit_after>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package queue

import (
	"crypto/rand"
	"errors"
	"fmt"
	"github.com/globocom/tsuru/log"
	"sync"
	"sync/atomic"
	"time"
)

const (
	stopped int32 = iota
	running
	stopping
)

// Handler is a thread safe generic handler for queue messages.
//
// When started, whenever a new message arrives, handler invokes F, giving the
// message as parameter. F is invoked in its own goroutine, so the handler can
// handle other messages as they arrive.
type Handler struct {
	F     func(*Message)
	state int32
	id    string
}

// Start starts the handler. It's safe to call this function multiple times.
func (h *Handler) Start() {
	r.add(h)
	if atomic.CompareAndSwapInt32(&h.state, stopped, running) {
		go h.loop()
	}
}

// DryRun changes the state of the handler, but does not start it.
//
// It's intended for using in tests. It returns an error if the handler is not
// stopped.
func (h *Handler) DryRun() error {
	if !atomic.CompareAndSwapInt32(&h.state, stopped, running) {
		return errors.New("Handler is not stopped.")
	}
	r.add(h)
	go h.fakeLoop()
	return nil
}

// Stop sends a signal to stop the handler, it won't stop the handler
// immediately. After calling Stop, one should call Wait for blocking until the
// handler is stopped.
//
// This method will return an error if the handler is not running.
func (h *Handler) Stop() error {
	if !atomic.CompareAndSwapInt32(&h.state, running, stopping) {
		return errors.New("Not running.")
	}
	r.remove(h)
	return nil
}

// Wait blocks until the handler is stopped.
func (h *Handler) Wait() {
	for atomic.LoadInt32(&h.state) != stopped {
		time.Sleep(1e3)
	}
}

func (h *Handler) fakeLoop() {
	for atomic.LoadInt32(&h.state) == running {
		time.Sleep(1e3)
	}
	atomic.StoreInt32(&h.state, stopped)
}

// loop will get messages from the queue and dispatch them to Handler.F.
func (h *Handler) loop() {
	for {
		if message, err := Get(1e9); err == nil {
			go h.F(message)
		} else if atomic.LoadInt32(&h.state) == running {
			log.Printf("Failed to get message from the queue: %s. Trying again...", err)
			continue
		} else {
			atomic.StoreInt32(&h.state, stopped)
			return
		}
	}
}

// registry stores references to all running handlers.
type registry struct {
	mut      sync.Mutex
	handlers map[string]*Handler
}

func newRegistry() *registry {
	return &registry{
		handlers: make(map[string]*Handler),
	}
}

func (r *registry) add(h *Handler) {
	if h.id == "" {
		var buf [16]byte
		rand.Read(buf[:])
		h.id = fmt.Sprintf("%x", buf)
	}
	r.mut.Lock()
	r.handlers[h.id] = h
	r.mut.Unlock()
}

func (r *registry) remove(h *Handler) {
	if h.id != "" {
		r.mut.Lock()
		delete(r.handlers, h.id)
		r.mut.Unlock()
	}
}

var r *registry = newRegistry()

// Preempt calls Stop and Wait for each running handler.
func Preempt() {
	var wg sync.WaitGroup
	r.mut.Lock()
	preemptable := make(map[string]*Handler, len(r.handlers))
	for k, v := range r.handlers {
		preemptable[k] = v
	}
	r.mut.Unlock()
	wg.Add(len(preemptable))
	for _, h := range preemptable {
		go func(h *Handler) {
			defer wg.Done()
			h.Stop()
			h.Wait()
		}(h)
	}
	wg.Wait()
}
<|endoftext|>


<commit_before>package job_tracker

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"path"
	"sync"
	"syscall"

	"github.com/vito/garden/backend"
	"github.com/vito/garden/command_runner"
)

type Job struct {
	ID            uint32
	DiscardOutput bool

	containerPath string
	runner        command_runner.CommandRunner

	waitingLinks *sync.Cond
	runningLink  *sync.Once
	link         *exec.Cmd
	unlinked     bool

	streams    []chan backend.JobStream
	streamLock *sync.RWMutex

	completed bool

	exitStatus uint32
	stdout     *namedStream
	stderr     *namedStream
}

func NewJob(
	id uint32,
	discardOutput bool,
	containerPath string,
	runner command_runner.CommandRunner,
) *Job {
	j := &Job{
		ID:            id,
		DiscardOutput: discardOutput,

		containerPath: containerPath,
		runner:        runner,

		waitingLinks: sync.NewCond(&sync.Mutex{}),
		runningLink:  &sync.Once{},
		streamLock:   &sync.RWMutex{},
	}

	j.stdout = newNamedStream(j, "stdout", j.DiscardOutput)
	j.stderr = newNamedStream(j, "stderr", j.DiscardOutput)

	return j
}

func (j *Job) Spawn(cmd *exec.Cmd) (ready, active chan error) {
	ready = make(chan error, 1)
	active = make(chan error, 1)

	spawnPath := path.Join(j.containerPath, "bin", "iomux-spawn")
	jobDir := path.Join(j.containerPath, "jobs", fmt.Sprintf("%d", j.ID))

	mkdir := &exec.Cmd{
		Path: "mkdir",
		Args: []string{"-p", jobDir},
	}

	err := j.runner.Run(mkdir)
	if err != nil {
		ready <- err
		return
	}

	spawn := &exec.Cmd{
		Path:  spawnPath,
		Stdin: cmd.Stdin,
	}

	spawn.Args = append([]string{jobDir}, cmd.Path)
	spawn.Args = append(spawn.Args, cmd.Args...)

	spawn.Env = cmd.Env

	spawnR, spawnW, err := os.Pipe()
	if err != nil {
		ready <- err
		return
	}

	spawn.Stdout = spawnW

	spawnOut := bufio.NewReader(spawnR)

	err = j.runner.Start(spawn)
	if err != nil {
		ready <- err
		return
	}

	go func() {
		defer func() {
			spawn.Wait()
			spawnW.Close()
			spawnR.Close()
		}()

		_, err = spawnOut.ReadBytes('\n')
		if err != nil {
			ready <- err
			return
		}

		ready <- nil

		_, err = spawnOut.ReadBytes('\n')
		if err != nil {
			active <- err
			return
		}

		active <- nil
	}()

	return
}

func (j *Job) Link() (uint32, []byte, []byte, error) {
	j.waitingLinks.L.Lock()
	defer j.waitingLinks.L.Unlock()

	if j.completed {
		return j.exitStatus, j.stdout.Bytes(), j.stderr.Bytes(), nil
	}

	j.runningLink.Do(j.runLinker)

	if !j.completed {
		j.waitingLinks.Wait()
	}

	return j.exitStatus, j.stdout.Bytes(), j.stderr.Bytes(), nil
}

func (j *Job) Unlink() error {
	if j.link != nil {
		j.unlinked = true
		return j.runner.Signal(j.link, os.Interrupt)
	}

	return nil
}

func (j *Job) Stream() chan backend.JobStream {
	return j.registerStream()
}

func (j *Job) runLinker() {
	linkPath := path.Join(j.containerPath, "bin", "iomux-link")
	jobDir := path.Join(j.containerPath, "jobs", fmt.Sprintf("%d", j.ID))

	j.link = &exec.Cmd{
		Path:   linkPath,
		Args:   []string{"-w", path.Join(jobDir, "cursors"), jobDir},
		Stdout: j.stdout,
		Stderr: j.stderr,
	}

	j.runner.Run(j.link)

	if j.unlinked {
		// iomux-link was killed on shutdown via .Unlink; command didn't
		// actually exit, so just block forever until server dies and re-links
		select {}
	}

	exitStatus := uint32(255)

	if j.link.ProcessState != nil {
		exitStatus = uint32(j.link.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
	}

	j.exitStatus = exitStatus

	j.completed = true

	j.sendToStreams(backend.JobStream{ExitStatus: &exitStatus})
	j.closeStreams()

	j.waitingLinks.Broadcast()
}

func (j *Job) registerStream() chan backend.JobStream {
	j.streamLock.Lock()
	defer j.streamLock.Unlock()

	stream := make(chan backend.JobStream, 2)

	stdout := j.stdout.Bytes()
	stderr := j.stderr.Bytes()

	if len(stdout) > 0 {
		stream <- backend.JobStream{
			Name: "stdout",
			Data: stdout,
		}
	}

	if len(stderr) > 0 {
		stream <- backend.JobStream{
			Name: "stderr",
			Data: stderr,
		}
	}

	j.streams = append(j.streams, stream)

	return stream
}

func (j *Job) sendToStreams(chunk backend.JobStream) {
	j.streamLock.RLock()
	defer j.streamLock.RUnlock()

	for _, sink := range j.streams {
		sink <- chunk
	}
}

func (j *Job) closeStreams() {
	j.streamLock.RLock()
	defer j.streamLock.RUnlock()

	for _, sink := range j.streams {
		close(sink)
	}
}
<commit_msg>use channels for interacting with job's streams<commit_after>package job_tracker

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"path"
	"sync"
	"syscall"

	"github.com/vito/garden/backend"
	"github.com/vito/garden/command_runner"
)

type Job struct {
	ID            uint32
	DiscardOutput bool

	containerPath string
	runner        command_runner.CommandRunner

	waitingLinks *sync.Cond
	runningLink  *sync.Once
	link         *exec.Cmd
	unlinked     bool

	streams      chan backend.JobStream
	closeStreams chan bool
	addStream    chan chan backend.JobStream

	completed bool

	exitStatus uint32
	stdout     *namedStream
	stderr     *namedStream
}

func NewJob(
	id uint32,
	discardOutput bool,
	containerPath string,
	runner command_runner.CommandRunner,
) *Job {
	j := &Job{
		ID:            id,
		DiscardOutput: discardOutput,

		containerPath: containerPath,
		runner:        runner,

		streams:      make(chan backend.JobStream),
		closeStreams: make(chan bool),
		addStream:    make(chan chan backend.JobStream),

		waitingLinks: sync.NewCond(&sync.Mutex{}),
		runningLink:  &sync.Once{},
	}

	j.stdout = newNamedStream(j, "stdout", j.DiscardOutput)
	j.stderr = newNamedStream(j, "stderr", j.DiscardOutput)

	go j.dispatchStreams()

	return j
}

func (j *Job) Spawn(cmd *exec.Cmd) (ready, active chan error) {
	ready = make(chan error, 1)
	active = make(chan error, 1)

	spawnPath := path.Join(j.containerPath, "bin", "iomux-spawn")
	jobDir := path.Join(j.containerPath, "jobs", fmt.Sprintf("%d", j.ID))

	mkdir := &exec.Cmd{
		Path: "mkdir",
		Args: []string{"-p", jobDir},
	}

	err := j.runner.Run(mkdir)
	if err != nil {
		ready <- err
		return
	}

	spawn := &exec.Cmd{
		Path:  spawnPath,
		Stdin: cmd.Stdin,
	}

	spawn.Args = append([]string{jobDir}, cmd.Path)
	spawn.Args = append(spawn.Args, cmd.Args...)

	spawn.Env = cmd.Env

	spawnR, spawnW, err := os.Pipe()
	if err != nil {
		ready <- err
		return
	}

	spawn.Stdout = spawnW

	spawnOut := bufio.NewReader(spawnR)

	err = j.runner.Start(spawn)
	if err != nil {
		ready <- err
		return
	}

	go func() {
		defer func() {
			spawn.Wait()
			spawnW.Close()
			spawnR.Close()
		}()

		_, err = spawnOut.ReadBytes('\n')
		if err != nil {
			ready <- err
			return
		}

		ready <- nil

		_, err = spawnOut.ReadBytes('\n')
		if err != nil {
			active <- err
			return
		}

		active <- nil
	}()

	return
}

func (j *Job) Link() (uint32, []byte, []byte, error) {
	j.waitingLinks.L.Lock()
	defer j.waitingLinks.L.Unlock()

	if j.completed {
		return j.exitStatus, j.stdout.Bytes(), j.stderr.Bytes(), nil
	}

	j.runningLink.Do(j.runLinker)

	if !j.completed {
		j.waitingLinks.Wait()
	}

	return j.exitStatus, j.stdout.Bytes(), j.stderr.Bytes(), nil
}

func (j *Job) Unlink() error {
	if j.link != nil {
		j.unlinked = true
		return j.runner.Signal(j.link, os.Interrupt)
	}

	return nil
}

func (j *Job) Stream() chan backend.JobStream {
	return j.registerStream()
}

func (j *Job) runLinker() {
	linkPath := path.Join(j.containerPath, "bin", "iomux-link")
	jobDir := path.Join(j.containerPath, "jobs", fmt.Sprintf("%d", j.ID))

	j.link = &exec.Cmd{
		Path:   linkPath,
		Args:   []string{"-w", path.Join(jobDir, "cursors"), jobDir},
		Stdout: j.stdout,
		Stderr: j.stderr,
	}

	j.runner.Run(j.link)

	if j.unlinked {
		// iomux-link was killed on shutdown via .Unlink; command didn't
		// actually exit, so just block forever until server dies and re-links
		select {}
	}

	exitStatus := uint32(255)

	if j.link.ProcessState != nil {
		exitStatus = uint32(j.link.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
	}

	j.exitStatus = exitStatus

	j.completed = true

	j.sendToStreams(backend.JobStream{ExitStatus: &exitStatus})
	j.closeStreams <- true

	j.waitingLinks.Broadcast()
}

func (j *Job) registerStream() chan backend.JobStream {
	stream := make(chan backend.JobStream, 2)

	stdout := j.stdout.Bytes()
	stderr := j.stderr.Bytes()

	if len(stdout) > 0 {
		stream <- backend.JobStream{
			Name: "stdout",
			Data: stdout,
		}
	}

	if len(stderr) > 0 {
		stream <- backend.JobStream{
			Name: "stderr",
			Data: stderr,
		}
	}

	j.addStream <- stream

	return stream
}

func (j *Job) sendToStreams(chunk backend.JobStream) {
	j.streams <- chunk
}

func (j *Job) dispatchStreams() {
	streams := []chan backend.JobStream{}

	for {
		select {
		case stream := <-j.addStream:
			streams = append(streams, stream)

		case chunk := <-j.streams:
			for _, stream := range streams {
				stream <- chunk
			}

		case <-j.closeStreams:
			for _, stream := range streams {
				close(stream)
			}

			return
		}
	}
}
<|endoftext|>


<commit_before>package watch

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"

	"time"

	"io"

	"encoding/json"

	"bytes"

	"io/ioutil"

	"github.com/foomo/petze/config"
)

func runSession(service *config.Service, r *Result, client *http.Client) error {
	//log.Println("running session with session length:", len(service.Session))
	// utils.JSONDump(service)
	endPointURL, errURL := service.GetURL()
	if errURL != nil {
		return errors.New("can not run session: " + errURL.Error())
	}
	for indexCall, call := range service.Session {
		// copy URL
		callURL := &url.URL{}
		*callURL = *endPointURL

		uriURL, errURIURL := call.GetURL()
		if errURIURL != nil {
			return errURIURL
		}

		callURL.Path = uriURL.Path
		callURL.RawQuery = uriURL.RawQuery

		var body io.Reader
		method := http.MethodGet
		if call.Method != "" {
			method = call.Method
		}
		if call.Data != nil {
			dataBytes, errDataBytes := json.Marshal(call.Data)
			if errDataBytes != nil {
				return errors.New("could not encode data bytes: " + errDataBytes.Error())
			}
			body = bytes.NewBuffer(dataBytes)
		}

		req, errNewRequest := http.NewRequest(method, callURL.String(), body)
		if errNewRequest != nil {
			return errNewRequest
		}
		start := time.Now()
		response, errResponse := client.Do(req)
		if errResponse != nil {
			return errResponse
		}
		defer response.Body.Close()

		duration := time.Since(start)

		responseBodyReader, readerErr := getResponseBodyReader(response, call.Check)
		if readerErr != nil {
			return readerErr
		}

		for indexCheck, chk := range call.Check {
			ctx := &CheckContext{
				response:           response,
				responseBodyReader: responseBodyReader,
				check:              chk,
				call:               call,
				duration:           duration,
			}
			for _, newErr := range checkResponse(ctx) {
				newErr.Comment = fmt.Sprint(chk.Comment, " @call[", indexCall, "].check[", indexCheck, "]")
				r.Errors = append(r.Errors, newErr)
			}
		}

	}
	return nil
}

func getResponseBodyReader(response *http.Response, checks []config.Check) (io.Reader, error) {
	if len(checks) > 1 {
		return response.Body, nil
	}
	responseBody, errReadAll := ioutil.ReadAll(response.Body)
	if errReadAll != nil {
		return nil, errors.New("could not read from response" + errReadAll.Error())
	}
	return bytes.NewReader(responseBody), nil
}

type CheckContext struct {
	response           *http.Response
	responseBodyReader io.Reader
	check              config.Check
	call               config.Call
	duration           time.Duration
}

var ContextValidators = []ValidatorFunc{
	ValidateJsonPath,
	ValidateGoQuery,
	ValidateDuration,
	ValidateContentType,
	ValidateRegex,
}

func checkResponse(ctx *CheckContext) []Error {
	errs := []Error{}

	for _, validator := range ContextValidators {
		errs = append(errs, validator(ctx)...)
	}

	return errs
}
<commit_msg>Always add buffering on response body (read seeker required for multiple body checks)<commit_after>package watch

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"

	"time"

	"io"

	"encoding/json"

	"bytes"

	"io/ioutil"

	"github.com/foomo/petze/config"
)

func runSession(service *config.Service, r *Result, client *http.Client) error {
	//log.Println("running session with session length:", len(service.Session))
	// utils.JSONDump(service)
	endPointURL, errURL := service.GetURL()
	if errURL != nil {
		return errors.New("can not run session: " + errURL.Error())
	}
	for indexCall, call := range service.Session {
		// copy URL
		callURL := &url.URL{}
		*callURL = *endPointURL

		uriURL, errURIURL := call.GetURL()
		if errURIURL != nil {
			return errURIURL
		}

		callURL.Path = uriURL.Path
		callURL.RawQuery = uriURL.RawQuery

		var body io.Reader
		method := http.MethodGet
		if call.Method != "" {
			method = call.Method
		}
		if call.Data != nil {
			dataBytes, errDataBytes := json.Marshal(call.Data)
			if errDataBytes != nil {
				return errors.New("could not encode data bytes: " + errDataBytes.Error())
			}
			body = bytes.NewBuffer(dataBytes)
		}

		req, errNewRequest := http.NewRequest(method, callURL.String(), body)
		if errNewRequest != nil {
			return errNewRequest
		}
		start := time.Now()
		response, errResponse := client.Do(req)
		if errResponse != nil {
			return errResponse
		}
		defer response.Body.Close()

		duration := time.Since(start)

		responseBodyReader, readerErr := getResponseBodyReader(response)
		if readerErr != nil {
			return readerErr
		}

		for indexCheck, chk := range call.Check {
			ctx := &CheckContext{
				response:           response,
				responseBodyReader: responseBodyReader,
				check:              chk,
				call:               call,
				duration:           duration,
			}
			for _, newErr := range checkResponse(ctx) {
				newErr.Comment = fmt.Sprint(chk.Comment, " @call[", indexCall, "].check[", indexCheck, "]")
				r.Errors = append(r.Errors, newErr)
			}
			responseBodyReader.Seek(0, io.SeekStart)
		}

	}
	return nil
}

func getResponseBodyReader(response *http.Response) (io.ReadSeeker, error) {
	responseBody, errReadAll := ioutil.ReadAll(response.Body)
	if errReadAll != nil {
		return nil, errors.New("could not read from response" + errReadAll.Error())
	}
	return bytes.NewReader(responseBody), nil
}

type CheckContext struct {
	response           *http.Response
	responseBodyReader io.Reader
	check              config.Check
	call               config.Call
	duration           time.Duration
}

var ContextValidators = []ValidatorFunc{
	ValidateJsonPath,
	ValidateGoQuery,
	ValidateDuration,
	ValidateContentType,
	ValidateRegex,
}

func checkResponse(ctx *CheckContext) []Error {
	errs := []Error{}

	for _, validator := range ContextValidators {
		errs = append(errs, validator(ctx)...)
	}

	return errs
}
<|endoftext|>


<commit_before>// 文件监控类
// create by gloomy 2017-5-3 11:40:32
package gutil

import (
	"errors"
	"fmt"
	"github.com/gobwas/glob"
	"github.com/howeyc/fsnotify"
	"os"
	"strings"
	"sync"
	"time"
)

var (
	AutoMatedTaskLock sync.RWMutex
	AutoMatedTaskFile map[string]int = make(map[string]int)
)

// 文件监控
// create by gloomy 2017-5-3 11:42:09
func WatchFile(filePathStr, matchFileName string, deleteFileCallBack, modifyFileCallBack, renameFileCallBack, createFileCallBack func(string)) (*fsnotify.Watcher, error) {
	fi, err := os.Stat(filePathStr)
	if err != nil {
		return nil, err
	}
	if !fi.IsDir() {
		return nil, errors.New("WatchFile filePathStr isn't dir!")
	}
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	go func() {
		for {
			select {
			case ev := <-watcher.Event:
				if ev == nil {
					continue
				}
				if strings.TrimSpace(matchFileName) != "" {
					if glob.MustCompile(matchFileName).Match(ev.Name) {
						fmt.Printf("WatchFile file is not match! matchFileName: %s fileName: %s \n", matchFileName, ev.Name)
						continue
					}
				}
				if ev.IsDelete() && deleteFileCallBack != nil {
					deleteFileCallBack(ev.Name)
				}
				if ev.IsRename() && renameFileCallBack != nil {
					renameFileCallBack(ev.Name)
				}
				if ev.IsCreate() && createFileCallBack != nil {
					createFileCallBack(ev.Name)
				}
				if ev.IsModify() && modifyFileCallBack != nil {
					AutoMatedTaskLock.RLock()
					_, ok := AutoMatedTaskFile[ev.Name]
					AutoMatedTaskLock.RUnlock()
					if !ok {
						AutoMatedTaskLock.Lock()
						AutoMatedTaskFile[ev.Name] = 0
						AutoMatedTaskLock.Unlock()
						go WatchFileAutoMated(ev.Name, modifyFileCallBack)
					}
				}
			case err := <-watcher.Error:
				if err == nil {
					continue
				}
				fmt.Printf("WatchFile fsnotify watcher is error! err: %s \n", err.Error())
			}
		}
	}()
	err = watcher.WatchFlags(filePathStr, fsnotify.FSN_ALL)
	if err != nil {
		return nil, err
	}
	return watcher, err
}

// 自动化创建任务 需要监控的文件,判断文件是否上传完毕
// 创建人:邵炜
// 创建时间:2016年9月5日14:58:13
// 输入参数: 文件路劲
func WatchFileAutoMated(filePath string, callBack func(string)) {
	tmrIntal := 30 * time.Second
	fileSaveTmr := time.NewTimer(tmrIntal)
	fileState, err := os.Stat(filePath)
	if err != nil {
		fmt.Printf("watchFileAutoMated can't load file! path: %s err: %s \n", filePath, err.Error())
		return
	}
	var (
		size   = fileState.Size()
		number int64
	)
	defer func() {
		fileSaveTmr.Stop()
		AutoMatedTaskLock.Lock()
		delete(AutoMatedTaskFile, filePath)
		AutoMatedTaskLock.Unlock()
	}()
	<-fileSaveTmr.C
	for {
		fileState, err = os.Stat(filePath)
		if err != nil {
			fmt.Printf("watchFileAutoMated can't load file! path: %s err: %s \n", filePath, err.Error())
			return
		}
		number = fileState.Size()
		if size == number {
			go callBack(filePath)
			return
		}
		size = number
		fileSaveTmr.Reset(tmrIntal)
		<-fileSaveTmr.C
	}
}
<commit_msg>文件监控方法修正<commit_after>// 文件监控类
// create by gloomy 2017-5-3 11:40:32
package gutil

import (
	"errors"
	"fmt"
	"github.com/gobwas/glob"
	"github.com/howeyc/fsnotify"
	"os"
	"strings"
	"sync"
	"time"
)

var (
	AutoMatedTaskLock sync.RWMutex
	AutoMatedTaskFile map[string]int = make(map[string]int)
)

// 文件监控
// create by gloomy 2017-5-3 11:42:09
func WatchFile(filePathStr, matchFileName string, deleteFileCallBack, modifyFileCallBack, renameFileCallBack, createFileCallBack func(string)) (*fsnotify.Watcher, error) {
	fi, err := os.Stat(filePathStr)
	if err != nil {
		return nil, err
	}
	if !fi.IsDir() {
		return nil, errors.New("WatchFile filePathStr isn't dir!")
	}
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	go func() {
		for {
			select {
			case ev := <-watcher.Event:
				if ev == nil {
					continue
				}
				if strings.TrimSpace(matchFileName) != "" {
					if glob.MustCompile(matchFileName).Match(ev.Name) {
						fmt.Printf("WatchFile file is not match! matchFileName: %s fileName: %s \n", matchFileName, ev.Name)
						continue
					}
				}
				if ev.IsDelete() && deleteFileCallBack != nil {
					AutoMatedTaskLock.RLock()
					_, ok := AutoMatedTaskFile[ev.Name]
					AutoMatedTaskLock.RUnlock()
					if !ok {
						AutoMatedTaskLock.Lock()
						AutoMatedTaskFile[ev.Name] = 0
						AutoMatedTaskLock.Unlock()
						go WatchFileAutoMated(ev.Name, deleteFileCallBack)
					}
				}
				if ev.IsRename() && renameFileCallBack != nil {
					AutoMatedTaskLock.RLock()
					_, ok := AutoMatedTaskFile[ev.Name]
					AutoMatedTaskLock.RUnlock()
					if !ok {
						AutoMatedTaskLock.Lock()
						AutoMatedTaskFile[ev.Name] = 0
						AutoMatedTaskLock.Unlock()
						go WatchFileAutoMated(ev.Name, renameFileCallBack)
					}
				}
				if ev.IsCreate() && createFileCallBack != nil {
					AutoMatedTaskLock.RLock()
					_, ok := AutoMatedTaskFile[ev.Name]
					AutoMatedTaskLock.RUnlock()
					if !ok {
						AutoMatedTaskLock.Lock()
						AutoMatedTaskFile[ev.Name] = 0
						AutoMatedTaskLock.Unlock()
						go WatchFileAutoMated(ev.Name, createFileCallBack)
					}
				}
				if ev.IsModify() && modifyFileCallBack != nil {
					AutoMatedTaskLock.RLock()
					_, ok := AutoMatedTaskFile[ev.Name]
					AutoMatedTaskLock.RUnlock()
					if !ok {
						AutoMatedTaskLock.Lock()
						AutoMatedTaskFile[ev.Name] = 0
						AutoMatedTaskLock.Unlock()
						go WatchFileAutoMated(ev.Name, modifyFileCallBack)
					}
				}
			case err := <-watcher.Error:
				if err == nil {
					continue
				}
				fmt.Printf("WatchFile fsnotify watcher is error! err: %s \n", err.Error())
			}
		}
	}()
	err = watcher.WatchFlags(filePathStr, fsnotify.FSN_ALL)
	if err != nil {
		return nil, err
	}
	return watcher, err
}

// 自动化创建任务 需要监控的文件,判断文件是否上传完毕
// 创建人:邵炜
// 创建时间:2016年9月5日14:58:13
// 输入参数: 文件路劲
func WatchFileAutoMated(filePath string, callBack func(string)) {
	tmrIntal := 30 * time.Second
	fileSaveTmr := time.NewTimer(tmrIntal)
	fileState, err := os.Stat(filePath)
	if err != nil {
		fmt.Printf("watchFileAutoMated can't load file! path: %s err: %s \n", filePath, err.Error())
		return
	}
	var (
		size   = fileState.Size()
		number int64
	)
	defer func() {
		fileSaveTmr.Stop()
		AutoMatedTaskLock.Lock()
		delete(AutoMatedTaskFile, filePath)
		AutoMatedTaskLock.Unlock()
	}()
	<-fileSaveTmr.C
	for {
		fileState, err = os.Stat(filePath)
		if err != nil {
			fmt.Printf("watchFileAutoMated can't load file! path: %s err: %s \n", filePath, err.Error())
			return
		}
		number = fileState.Size()
		if size == number {
			go callBack(filePath)
			return
		}
		size = number
		fileSaveTmr.Reset(tmrIntal)
		<-fileSaveTmr.C
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/ecopony/gamedayapi"
	"log"
	"os"
	"time"
	"fmt"
)

var validCommands = map[string]bool {
	"game": true,
//	"games-for-team-and-year": true,
//	"games-for-team-and-years": true,
}

func main() {
	args := os.Args[1:]

	if len(args) <= 2 {
		fmt.Println("Usage: mlbgd <command> <team code> <date|year(s)>")
		os.Exit(1)
	}

	command := args[0]
	if !isCommandValid(command) {
		fmt.Println(fmt.Sprintf("%s is not a valid command. Valid commands:", command))

		for k, _ := range validCommands {
			fmt.Println(fmt.Sprintf("\t%s", k))
		}

		os.Exit(1)
	}

	teamCode := args[1]

	if command == "game" {
		date, err := time.Parse("2006-01-02", args[2])
		if err != nil {
			fmt.Println("Date must be in the format 2006-01-02")
			os.Exit(1)
		}
		game, _ := gamedayapi.GameFor(teamCode, date)
		game.EagerLoad()
		fmt.Println("Game files saved to " + gamedayapi.BaseCachePath() + game.GameDataDirectory)
	}
}

func isCommandValid(command string) bool {
	return validCommands[command]
}
<commit_msg>Adding a separate batch fetch command to the command line.<commit_after>package main

import (
	"fmt"
	"github.com/ecopony/gamedayapi"
	"os"
	"strconv"
	"time"
)

var validCommands = map[string]bool{
	"game": true,
	"games-for-team-and-year":  true,
	"games-for-team-and-years": true,
}

func main() {
	args := os.Args[1:]

	if len(args) <= 2 {
		fmt.Println("Usage: mlbgd <command> <team code> <date|year(s)>")
		os.Exit(1)
	}

	command := args[0]
	if !isCommandValid(command) {
		fmt.Println(fmt.Sprintf("%s is not a valid command. Valid commands:", command))

		for k := range validCommands {
			fmt.Println(fmt.Sprintf("\t%s", k))
		}

		os.Exit(1)
	}

	teamCode := args[1]

	if command == "game" {
		date, err := time.Parse("2006-01-02", args[2])
		if err != nil {
			fmt.Println("Date must be in the format 2006-01-02")
			os.Exit(1)
		}
		game, _ := gamedayapi.GameFor(teamCode, date)
		game.EagerLoad()
		fmt.Println("Game files saved to " + gamedayapi.BaseCachePath() + game.GameDataDirectory)
	} else {
		yearArgs := args[2:]
		var years []int
		for i := 0; i < len(yearArgs); i++ {
			year, err := strconv.Atoi(yearArgs[i])
			if err != nil {
				fmt.Println("Year is not valid")
			}
			years = append(years, year)
		}
		gamedayapi.FetchByTeamAndYears(teamCode, years, eagerLoadGame)
	}
}

func isCommandValid(command string) bool {
	return validCommands[command]
}

func eagerLoadGame(game *gamedayapi.Game) {
	game.EagerLoad()
	fmt.Println("Game files saved to " + gamedayapi.BaseCachePath() + game.GameDataDirectory)
}
<|endoftext|>


<commit_before>package webhooklistener

import (
	"encoding/json"
	"gitlab.informatik.haw-hamburg.de/icc/gl-k8s-integrator/usecases"
	"io/ioutil"
	"log"
	"net/http"
)

func Listen(quit chan int) {
	router := http.NewServeMux()
	router.HandleFunc("/healthz", handleHealthz)
	router.HandleFunc("/", handleGitlabWebhook)
	router.HandleFunc("sync", handleSync)

	log.Fatal(http.ListenAndServe(":8080", router))
	quit <- 0
}

func handleSync(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "POST":
		go usecases.PerformGlK8sSync()
		w.WriteHeader(202)
	}
}

// handleGitlabWebhook listens for the following events from the
// Gitlab System Webhooks Events: https://docs.gitlab.com/ce/system_hooks/system_hooks.html
func handleGitlabWebhook(w http.ResponseWriter, r *http.Request) {
	switch r.Method {

	case "POST":
		if r.Header.Get("X-Gitlab-Event") != "System Hook" {
			return
		}
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			HandleError(err, w, "Could not read body!", http.StatusBadRequest)
		}
		go usecases.HandleGitlabEvent(body)
		w.WriteHeader(http.StatusOK)
	}
}

func handleHealthz(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("ok"))
}

type ErrorMessage struct {
	Msg string
}

func HandleError(err error, w http.ResponseWriter, msg string, statusCode int) {
	log.Println("Error occurred! Err was: " + err.Error())
	w.WriteHeader(statusCode)
	if msg != "" {
		answer, _ := json.Marshal(ErrorMessage{msg + err.Error()})
		w.Write(answer)
	}
	return
}
<commit_msg>fixed typo in sync endpoint<commit_after>package webhooklistener

import (
	"encoding/json"
	"gitlab.informatik.haw-hamburg.de/icc/gl-k8s-integrator/usecases"
	"io/ioutil"
	"log"
	"net/http"
)

func Listen(quit chan int) {
	router := http.NewServeMux()
	router.HandleFunc("/healthz", handleHealthz)
	router.HandleFunc("/", handleGitlabWebhook)
	router.HandleFunc("/sync", handleSync)

	log.Fatal(http.ListenAndServe(":8080", router))
	quit <- 0
}

func handleSync(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "POST":
		go usecases.PerformGlK8sSync()
		w.WriteHeader(202)
	}
}

// handleGitlabWebhook listens for the following events from the
// Gitlab System Webhooks Events: https://docs.gitlab.com/ce/system_hooks/system_hooks.html
func handleGitlabWebhook(w http.ResponseWriter, r *http.Request) {
	switch r.Method {

	case "POST":
		if r.Header.Get("X-Gitlab-Event") != "System Hook" {
			return
		}
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			HandleError(err, w, "Could not read body!", http.StatusBadRequest)
		}
		go usecases.HandleGitlabEvent(body)
		w.WriteHeader(http.StatusOK)
	}
}

func handleHealthz(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("ok"))
}

type ErrorMessage struct {
	Msg string
}

func HandleError(err error, w http.ResponseWriter, msg string, statusCode int) {
	log.Println("Error occurred! Err was: " + err.Error())
	w.WriteHeader(statusCode)
	if msg != "" {
		answer, _ := json.Marshal(ErrorMessage{msg + err.Error()})
		w.Write(answer)
	}
	return
}
<|endoftext|>


<commit_before>package ui

import (
	"strings"
	"time"

	"github.com/moncho/dry/search"
	"github.com/nsf/termbox-go"
)

const (
	endtext   = "(end)"
	starttext = "(start)"
)

//Less is a View specilization with less-like behaviour and characteristics, meaning:
// * The cursor is always shown at the bottom of the screen.
// * Navigation is done using less keybindings.
// * Basic searching is supported.
type Less struct {
	*View
	searchResult *search.Result
	filtering    bool
}

//NewLess creates a view that partially simulates less.
func NewLess() *Less {
	width, height := termbox.Size()
	view := &View{
		name:       "",
		x1:         width,
		y1:         height,
		cursorX:    0,
		cursorY:    height - 1, //Last line is at height -1
		showCursor: true,
	}
	return &Less{
		view, nil, false,
	}
}

//Focus sets the view as active, so it starts handling terminal events
//and user actions
func (less *Less) Focus(events <-chan termbox.Event) error {
	inputMode := false
	inputBoxEventChan := make(chan termbox.Event)
	inputBoxOuput := make(chan string, 1)
	refreshTimer := time.NewTicker(500 * time.Millisecond)
	stop := make(chan struct{})

	//the first render is done when some content is added to the buffer
	go func() {
		for {
			select {
			case <-refreshTimer.C:
				if less.bufferSize() > 0 {
					less.tainted = false
					less.Render()
					termbox.Flush()
					return
				}
			case <-stop:
				return
			}
		}
	}()

	defer close(inputBoxOuput)
	defer close(inputBoxEventChan)
	defer close(stop)
loop:
	for {
		select {
		case input := <-inputBoxOuput:
			inputMode = false
			less.Search(input)
			clear()
			if err := less.Render(); err != nil {
				return err
			}
			termbox.Flush()
		case event := <-events:
			switch event.Type {
			case termbox.EventKey:
				if !inputMode {
					if event.Key == termbox.KeyEsc {
						break loop
					} else if event.Key == termbox.KeyArrowDown { //cursor down
						less.ScrollDown()
					} else if event.Key == termbox.KeyArrowUp { // cursor up
						less.ScrollUp()
					} else if event.Key == termbox.KeyPgdn { //cursor one page down
						less.ScrollPageDown()
					} else if event.Key == termbox.KeyPgup { // cursor one page up
						less.ScrollPageUp()
					} else if event.Ch == 'N' { //to the top of the view
						less.gotoPreviousSearchHit()
					} else if event.Ch == 'n' { //to the bottom of the view
						less.gotoNextSearchHit()
					} else if event.Ch == 'g' { //to the top of the view
						less.ScrollToTop()
					} else if event.Ch == 'G' { //to the bottom of the view
						less.ScrollToBottom()
					} else if event.Ch == '/' {
						inputMode = true
						less.tainted = false
						less.filtering = false
						go less.readInput(inputBoxEventChan, inputBoxOuput)
					} else if event.Ch == 'f' {
						inputMode = true
						less.tainted = false
						less.filtering = true
						go less.readInput(inputBoxEventChan, inputBoxOuput)
					}

					if less.tainted {
						clear()
						if err := less.Render(); err != nil {
							return err
						}
						termbox.Flush()
					}
				} else {
					inputBoxEventChan <- event
				}
			}
		}
	}
	return nil
}

//Search searchs in the view buffer for the given pattern
func (less *Less) Search(pattern string) error {
	if pattern != "" {
		less.tainted = true
		searchResult, err := search.NewSearch(less.lines, pattern)
		if err == nil {
			less.searchResult = searchResult
			_, y := less.Position()
			searchResult.InitialLine(y)
		} else {
			return err
		}
	} else {
		less.searchResult = nil
	}
	return nil
}

func (less *Less) readInput(inputBoxEventChan chan termbox.Event, inputBoxOuput chan string) error {
	_, height := less.ViewSize()
	eb := NewInputBox(0, height, "/", inputBoxOuput, inputBoxEventChan)
	eb.Focus()
	return nil
}

// Render renders the view buffer contents.
func (less *Less) Render() error {
	_, maxY := less.renderSize()

	//less.prepareViewForRender()

	y := 0
	for i, vline := range less.lines {
		if i < less.bufferY {
			continue
		}
		if y > maxY {
			break
		}
		less.renderLine(0, y, string(vline))
		y++
	}

	less.renderMessage()
	less.drawCursor()
	return nil
}

//ScrollDown moves the cursor down one line
func (less *Less) ScrollDown() {
	less.scrollDown(1)
}

//ScrollUp moves the cursor up one line
func (less *Less) ScrollUp() {
	less.scrollUp(1)
}

//ScrollPageDown moves the buffer position down by the length of the screen,
//at the end of buffer it also moves the cursor position to the bottom
//of the screen
func (less *Less) ScrollPageDown() {
	_, height := less.ViewSize()
	less.scrollDown(height)
}

//ScrollPageUp moves the buffer position up by the length of the screen,
//at the beginning of buffer it also moves the cursor position to the beginning
//of the screen
func (less *Less) ScrollPageUp() {
	_, height := less.ViewSize()
	less.scrollUp(height)
}

//ScrollToBottom moves the cursor to the bottom of the view buffer
func (less *Less) ScrollToBottom() {
	less.bufferY = less.bufferSize() - less.y1
	less.tainted = true

}

//ScrollToTop moves the cursor to the top of the view buffer
func (less *Less) ScrollToTop() {
	less.bufferY = 0
	less.tainted = true

}

func (less *Less) atTheStartOfBuffer() bool {
	_, y := less.Position()
	if y == 0 {
		return true
	}
	return false
}

func (less *Less) atTheEndOfBuffer() bool {
	viewLength := less.bufferSize()
	_, y := less.Position()
	_, height := less.ViewSize()
	if y+height >= viewLength-1 {
		return true
	}
	return false
}

func (less *Less) bufferSize() int {
	return len(less.lines)
}

func (less *Less) gotoPreviousSearchHit() {
	sr := less.searchResult
	if sr != nil {
		x, _ := less.Position()
		if newy, err := sr.PreviousLine(); err == nil {
			less.setPosition(x, newy)
		}
	}
}
func (less *Less) gotoNextSearchHit() {
	sr := less.searchResult
	if sr != nil {
		x, _ := less.Position()
		if newy, err := sr.NextLine(); err == nil {
			less.setPosition(x, newy)
		}
	}
}

//renderSize return the part of the view size available for rendering.
func (less *Less) renderSize() (int, int) {
	maxX, maxY := less.ViewSize()
	return maxX, maxY - 1
}

func (less *Less) renderLine(x int, y int, line string) error {
	if less.searchResult != nil {
		//If markup support is active then it might happen that tags are present in the line
		//but since we are searching, markups are ignored and coloring output is
		//decided here.
		if strings.Contains(line, less.searchResult.Pattern) {
			if less.markup != nil {
				start, column := 0, 0
				for _, token := range Tokenize(line, supportedTags) {
					if less.markup.IsTag(token) {

						continue
					}
					// Here comes the actual text: display it one character at a time.
					for _, char := range token {
						start = x + column
						column++
						termbox.SetCell(start, y, char, termbox.ColorYellow, termbox.ColorDefault)
					}
				}
			} else {
				renderString(x, y, line, termbox.ColorYellow, termbox.ColorDefault)
			}
		} else if !less.filtering {
			return less.View.renderLine(x, y, line)
		}

	} else {
		return less.View.renderLine(x, y, line)
	}
	return nil
}

//scrollDown moves the buffer position down by the given number of lines
func (less *Less) scrollDown(lines int) {
	_, height := less.ViewSize()
	viewLength := less.bufferSize()

	posX, posY := less.Position()
	//This is a down as scrolling can go
	maxY := viewLength - height
	if posY+lines < maxY {
		newOy := posY + lines
		if newOy >= viewLength {
			less.setPosition(posX, viewLength-height)
		} else {
			less.setPosition(posX, newOy)
		}
	} else {

		less.ScrollToBottom()
	}
	less.tainted = true
}

//scrollUp moves the buffer position up by the given number of lines
func (less *Less) scrollUp(lines int) {
	ox, bufferY := less.Position()
	if bufferY-lines >= 0 {
		less.setPosition(ox, bufferY-lines)
	} else {
		less.setPosition(ox, 0)
	}
	less.tainted = true
}

func (less *Less) renderMessage() {
	_, maxY := less.ViewSize()
	var cursorX = 1
	switch {
	case less.searchResult != nil:
		{
			renderString(0, maxY, less.searchResult.String(), termbox.ColorWhite, termbox.ColorDefault)
			cursorX = len(less.searchResult.String())
		}
	case !less.atTheEndOfBuffer() && !less.atTheStartOfBuffer():
		termbox.SetCell(0, maxY, ':', termbox.ColorDefault, termbox.ColorDefault)
	case less.atTheStartOfBuffer():
		renderString(0, maxY, starttext, termbox.ColorWhite, termbox.ColorDefault)
		cursorX = len(starttext)
	default:
		{
			renderString(0, maxY, endtext, termbox.ColorWhite, termbox.ColorDefault)
			cursorX = len(endtext)
		}
	}
	less.cursorX = cursorX
}

func (less *Less) drawCursor() {
	x, y := less.Cursor()

	termbox.SetCursor(x, y)
}

func clear() {
	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
	termbox.Flush()
}
<commit_msg>Remove unused code<commit_after>package ui

import (
	"strings"
	"time"

	"github.com/moncho/dry/search"
	"github.com/nsf/termbox-go"
)

const (
	endtext   = "(end)"
	starttext = "(start)"
)

//Less is a View specilization with less-like behaviour and characteristics, meaning:
// * The cursor is always shown at the bottom of the screen.
// * Navigation is done using less keybindings.
// * Basic searching is supported.
type Less struct {
	*View
	searchResult *search.Result
	filtering    bool
}

//NewLess creates a view that partially simulates less.
func NewLess() *Less {
	width, height := termbox.Size()
	view := &View{
		name:       "",
		x1:         width,
		y1:         height,
		cursorX:    0,
		cursorY:    height - 1, //Last line is at height -1
		showCursor: true,
	}
	return &Less{
		view, nil, false,
	}
}

//Focus sets the view as active, so it starts handling terminal events
//and user actions
func (less *Less) Focus(events <-chan termbox.Event) error {
	inputMode := false
	inputBoxEventChan := make(chan termbox.Event)
	inputBoxOuput := make(chan string, 1)
	refreshTimer := time.NewTicker(500 * time.Millisecond)
	stop := make(chan struct{})

	//the first render is done when some content is added to the buffer
	go func() {
		for {
			select {
			case <-refreshTimer.C:
				if less.bufferSize() > 0 {
					less.tainted = false
					less.Render()
					termbox.Flush()
					return
				}
			case <-stop:
				return
			}
		}
	}()

	defer close(inputBoxOuput)
	defer close(inputBoxEventChan)
	defer close(stop)
loop:
	for {
		select {
		case input := <-inputBoxOuput:
			inputMode = false
			less.Search(input)
			clear()
			if err := less.Render(); err != nil {
				return err
			}
			termbox.Flush()
		case event := <-events:
			switch event.Type {
			case termbox.EventKey:
				if !inputMode {
					if event.Key == termbox.KeyEsc {
						break loop
					} else if event.Key == termbox.KeyArrowDown { //cursor down
						less.ScrollDown()
					} else if event.Key == termbox.KeyArrowUp { // cursor up
						less.ScrollUp()
					} else if event.Key == termbox.KeyPgdn { //cursor one page down
						less.ScrollPageDown()
					} else if event.Key == termbox.KeyPgup { // cursor one page up
						less.ScrollPageUp()
					} else if event.Ch == 'N' { //to the top of the view
						less.gotoPreviousSearchHit()
					} else if event.Ch == 'n' { //to the bottom of the view
						less.gotoNextSearchHit()
					} else if event.Ch == 'g' { //to the top of the view
						less.ScrollToTop()
					} else if event.Ch == 'G' { //to the bottom of the view
						less.ScrollToBottom()
					} else if event.Ch == '/' {
						inputMode = true
						less.tainted = false
						less.filtering = false
						go less.readInput(inputBoxEventChan, inputBoxOuput)
					} else if event.Ch == 'f' {
						inputMode = true
						less.tainted = false
						less.filtering = true
						go less.readInput(inputBoxEventChan, inputBoxOuput)
					}

					if less.tainted {
						clear()
						if err := less.Render(); err != nil {
							return err
						}
						termbox.Flush()
					}
				} else {
					inputBoxEventChan <- event
				}
			}
		}
	}
	return nil
}

//Search searchs in the view buffer for the given pattern
func (less *Less) Search(pattern string) error {
	if pattern != "" {
		less.tainted = true
		searchResult, err := search.NewSearch(less.lines, pattern)
		if err == nil {
			less.searchResult = searchResult
			_, y := less.Position()
			searchResult.InitialLine(y)
		} else {
			return err
		}
	} else {
		less.searchResult = nil
	}
	return nil
}

func (less *Less) readInput(inputBoxEventChan chan termbox.Event, inputBoxOuput chan string) error {
	_, height := less.ViewSize()
	eb := NewInputBox(0, height, "/", inputBoxOuput, inputBoxEventChan)
	eb.Focus()
	return nil
}

// Render renders the view buffer contents.
func (less *Less) Render() error {
	_, maxY := less.renderSize()

	y := 0
	for i, vline := range less.lines {
		if i < less.bufferY {
			continue
		}
		if y > maxY {
			break
		}
		less.renderLine(0, y, string(vline))
		y++
	}

	less.renderMessage()
	less.drawCursor()
	return nil
}

//ScrollDown moves the cursor down one line
func (less *Less) ScrollDown() {
	less.scrollDown(1)
}

//ScrollUp moves the cursor up one line
func (less *Less) ScrollUp() {
	less.scrollUp(1)
}

//ScrollPageDown moves the buffer position down by the length of the screen,
//at the end of buffer it also moves the cursor position to the bottom
//of the screen
func (less *Less) ScrollPageDown() {
	_, height := less.ViewSize()
	less.scrollDown(height)
}

//ScrollPageUp moves the buffer position up by the length of the screen,
//at the beginning of buffer it also moves the cursor position to the beginning
//of the screen
func (less *Less) ScrollPageUp() {
	_, height := less.ViewSize()
	less.scrollUp(height)
}

//ScrollToBottom moves the cursor to the bottom of the view buffer
func (less *Less) ScrollToBottom() {
	less.bufferY = less.bufferSize() - less.y1
	less.tainted = true

}

//ScrollToTop moves the cursor to the top of the view buffer
func (less *Less) ScrollToTop() {
	less.bufferY = 0
	less.tainted = true

}

func (less *Less) atTheStartOfBuffer() bool {
	_, y := less.Position()
	if y == 0 {
		return true
	}
	return false
}

func (less *Less) atTheEndOfBuffer() bool {
	viewLength := less.bufferSize()
	_, y := less.Position()
	_, height := less.ViewSize()
	if y+height >= viewLength-1 {
		return true
	}
	return false
}

func (less *Less) bufferSize() int {
	return len(less.lines)
}

func (less *Less) gotoPreviousSearchHit() {
	sr := less.searchResult
	if sr != nil {
		x, _ := less.Position()
		if newy, err := sr.PreviousLine(); err == nil {
			less.setPosition(x, newy)
		}
	}
}
func (less *Less) gotoNextSearchHit() {
	sr := less.searchResult
	if sr != nil {
		x, _ := less.Position()
		if newy, err := sr.NextLine(); err == nil {
			less.setPosition(x, newy)
		}
	}
}

//renderSize return the part of the view size available for rendering.
func (less *Less) renderSize() (int, int) {
	maxX, maxY := less.ViewSize()
	return maxX, maxY - 1
}

func (less *Less) renderLine(x int, y int, line string) error {
	if less.searchResult != nil {
		//If markup support is active then it might happen that tags are present in the line
		//but since we are searching, markups are ignored and coloring output is
		//decided here.
		if strings.Contains(line, less.searchResult.Pattern) {
			if less.markup != nil {
				start, column := 0, 0
				for _, token := range Tokenize(line, supportedTags) {
					if less.markup.IsTag(token) {

						continue
					}
					// Here comes the actual text: display it one character at a time.
					for _, char := range token {
						start = x + column
						column++
						termbox.SetCell(start, y, char, termbox.ColorYellow, termbox.ColorDefault)
					}
				}
			} else {
				renderString(x, y, line, termbox.ColorYellow, termbox.ColorDefault)
			}
		} else if !less.filtering {
			return less.View.renderLine(x, y, line)
		}

	} else {
		return less.View.renderLine(x, y, line)
	}
	return nil
}

//scrollDown moves the buffer position down by the given number of lines
func (less *Less) scrollDown(lines int) {
	_, height := less.ViewSize()
	viewLength := less.bufferSize()

	posX, posY := less.Position()
	//This is a down as scrolling can go
	maxY := viewLength - height
	if posY+lines < maxY {
		newOy := posY + lines
		if newOy >= viewLength {
			less.setPosition(posX, viewLength-height)
		} else {
			less.setPosition(posX, newOy)
		}
	} else {

		less.ScrollToBottom()
	}
	less.tainted = true
}

//scrollUp moves the buffer position up by the given number of lines
func (less *Less) scrollUp(lines int) {
	ox, bufferY := less.Position()
	if bufferY-lines >= 0 {
		less.setPosition(ox, bufferY-lines)
	} else {
		less.setPosition(ox, 0)
	}
	less.tainted = true
}

func (less *Less) renderMessage() {
	_, maxY := less.ViewSize()
	var cursorX = 1
	switch {
	case less.searchResult != nil:
		{
			renderString(0, maxY, less.searchResult.String(), termbox.ColorWhite, termbox.ColorDefault)
			cursorX = len(less.searchResult.String())
		}
	case !less.atTheEndOfBuffer() && !less.atTheStartOfBuffer():
		termbox.SetCell(0, maxY, ':', termbox.ColorDefault, termbox.ColorDefault)
	case less.atTheStartOfBuffer():
		renderString(0, maxY, starttext, termbox.ColorWhite, termbox.ColorDefault)
		cursorX = len(starttext)
	default:
		{
			renderString(0, maxY, endtext, termbox.ColorWhite, termbox.ColorDefault)
			cursorX = len(endtext)
		}
	}
	less.cursorX = cursorX
}

func (less *Less) drawCursor() {
	x, y := less.Cursor()

	termbox.SetCursor(x, y)
}

func clear() {
	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
	termbox.Flush()
}
<|endoftext|>


<commit_before>// Package build provides a builder system for constructing various xdr
// structures used by the stellar network.
//
// At the core of this package is the *Builder and *Mutator types.  A Builder
// object (ex. PaymentBuilder, TransactionBuilder) contain an underlying xdr
// struct that is being iteratively built by having zero or more Mutator structs
// applied to it. See ExampleTransactionBuilder in main_test.go for an example.
package build

import "github.com/stellar/go-stellar-base"

// Defaults is a mutator that sets defaults
type Defaults struct{}

// Destination is a mutator capable of setting the destination on
// an xdr.PaymentOp
type Destination struct {
	Address string
}

// SourceAccount is a mutator capable of setting the source account on
// an xdr.Operation and an xdr.Transaction
type SourceAccount struct {
	Address string
}

// NativeAmount is a mutator that configures a payment to be using native
// currency and have the amount provided.
type NativeAmount struct {
	Amount int64
}

// Sequence is a mutator that sets the sequence number on a transaction
type Sequence struct {
	Sequence int64
}

// Sign is a mutator that contributes a signature of the provided envelope's
// transaction with the configured key
type Sign struct {
	Key stellarbase.Signer
}
<commit_msg>Make Sequence mutator take an xdr.SequenceNumber<commit_after>// Package build provides a builder system for constructing various xdr
// structures used by the stellar network.
//
// At the core of this package is the *Builder and *Mutator types.  A Builder
// object (ex. PaymentBuilder, TransactionBuilder) contain an underlying xdr
// struct that is being iteratively built by having zero or more Mutator structs
// applied to it. See ExampleTransactionBuilder in main_test.go for an example.
package build

import (
	"github.com/stellar/go-stellar-base"
	"github.com/stellar/go-stellar-base/xdr"
)

// Defaults is a mutator that sets defaults
type Defaults struct{}

// Destination is a mutator capable of setting the destination on
// an xdr.PaymentOp
type Destination struct {
	Address string
}

// SourceAccount is a mutator capable of setting the source account on
// an xdr.Operation and an xdr.Transaction
type SourceAccount struct {
	Address string
}

// NativeAmount is a mutator that configures a payment to be using native
// currency and have the amount provided.
type NativeAmount struct {
	Amount int64
}

// Sequence is a mutator that sets the sequence number on a transaction
type Sequence struct {
	Sequence xdr.SequenceNumber
}

// Sign is a mutator that contributes a signature of the provided envelope's
// transaction with the configured key
type Sign struct {
	Key stellarbase.Signer
}
<|endoftext|>


<commit_before>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of getgauge/html-report.

// getgauge/html-report is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// getgauge/html-report is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with getgauge/html-report.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strings"
)

const (
	CGO_ENABLED = "CGO_ENABLED"
)

const (
	dotGauge          = ".gauge"
	plugins           = "plugins"
	GOARCH            = "GOARCH"
	GOOS              = "GOOS"
	X86               = "386"
	X86_64            = "amd64"
	DARWIN            = "darwin"
	LINUX             = "linux"
	WINDOWS           = "windows"
	bin               = "bin"
	newDirPermissions = 0755
	gauge             = "gauge"
	htmlReport        = "html-report"
	deploy            = "deploy"
	pluginJsonFile    = "plugin.json"
	reportTemplate    = "report-template"
	GAUGE_MESSAGES    = "gauge_messages"
)

var deployDir = filepath.Join(deploy, htmlReport)

func main() {
	flag.Parse()
	if *install {
		updatePluginInstallPrefix()
		installPlugin(*pluginInstallPrefix)
	} else if *distro {
		createPluginDistro(*allPlatforms)
	} else {
		compile()
	}
}

func compile() {
	if *allPlatforms {
		compileAcrossPlatforms()
	} else {
		compileGoPackage(htmlReport)
	}
}

func createPluginDistro(forAllPlatforms bool) {
	if forAllPlatforms {
		for _, platformEnv := range platformEnvs {
			setEnv(platformEnv)
			*binDir = filepath.Join(bin, fmt.Sprintf("%s_%s", platformEnv[GOOS], platformEnv[GOARCH]))
			fmt.Printf("Creating distro for platform => OS:%s ARCH:%s \n", platformEnv[GOOS], platformEnv[GOARCH])
			createDistro()
		}
	} else {
		createDistro()
	}
	log.Printf("Distributables created in directory => %s \n", deploy)
}

func createDistro() {
	packageName := fmt.Sprintf("%s-%s-%s.%s", htmlReport, getPluginVersion(), getGOOS(), getArch())
	distroDir := filepath.Join(deploy, packageName)
	copyPluginFiles(distroDir)
	createZipFromUtil(deploy, packageName)
	os.RemoveAll(distroDir)
}

func createZipFromUtil(dir, name string) {
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	os.Chdir(filepath.Join(dir, name))
	output, err := executeCommand("zip", "-r", filepath.Join("..", name+".zip"), ".")
	fmt.Println(output)
	if err != nil {
		panic(fmt.Sprintf("Failed to zip: %s", err))
	}
	os.Chdir(wd)
}

func isExecMode(mode os.FileMode) bool {
	return (mode & 0111) != 0
}

func mirrorFile(src, dst string) error {
	sfi, err := os.Stat(src)
	if err != nil {
		return err
	}
	if sfi.Mode()&os.ModeType != 0 {
		log.Fatalf("mirrorFile can't deal with non-regular file %s", src)
	}
	dfi, err := os.Stat(dst)
	if err == nil &&
		isExecMode(sfi.Mode()) == isExecMode(dfi.Mode()) &&
		(dfi.Mode()&os.ModeType == 0) &&
		dfi.Size() == sfi.Size() &&
		dfi.ModTime().Unix() == sfi.ModTime().Unix() {
		// Seems to not be modified.
		return nil
	}

	dstDir := filepath.Dir(dst)
	if err := os.MkdirAll(dstDir, newDirPermissions); err != nil {
		return err
	}

	df, err := os.Create(dst)
	if err != nil {
		return err
	}
	sf, err := os.Open(src)
	if err != nil {
		return err
	}
	defer sf.Close()

	n, err := io.Copy(df, sf)
	if err == nil && n != sfi.Size() {
		err = fmt.Errorf("copied wrong size for %s -> %s: copied %d; want %d", src, dst, n, sfi.Size())
	}
	cerr := df.Close()
	if err == nil {
		err = cerr
	}
	if err == nil {
		err = os.Chmod(dst, sfi.Mode())
	}
	if err == nil {
		err = os.Chtimes(dst, sfi.ModTime(), sfi.ModTime())
	}
	return err
}

func mirrorDir(src, dst string) error {
	log.Printf("Copying '%s' -> '%s'\n", src, dst)
	err := filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if fi.IsDir() {
			return nil
		}
		suffix, err := filepath.Rel(src, path)
		if err != nil {
			return fmt.Errorf("Failed to find Rel(%q, %q): %v", src, path, err)
		}
		return mirrorFile(path, filepath.Join(dst, suffix))
	})
	return err
}

func set(envName, envValue string) {
	log.Printf("%s = %s\n", envName, envValue)
	err := os.Setenv(envName, envValue)
	if err != nil {
		panic(err)
	}
}

func runProcess(command string, arg ...string) {
	cmd := exec.Command(command, arg...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	log.Printf("Execute %v\n", cmd.Args)
	err := cmd.Run()
	if err != nil {
		panic(err)
	}
}

func executeCommand(command string, arg ...string) (string, error) {
	cmd := exec.Command(command, arg...)
	bytes, err := cmd.Output()
	return strings.TrimSpace(fmt.Sprintf("%s", bytes)), err
}

func compileGoPackage(packageName string) {
	runProcess("go", "build", "-o", getGaugeExecutablePath(htmlReport))
}

func getGaugeExecutablePath(file string) string {
	return filepath.Join(getBinDir(), getExecutableName(file))
}

func getExecutableName(file string) string {
	if getGOOS() == "windows" {
		return file + ".exe"
	}
	return file
}

func getBinDir() string {
	if *binDir != "" {
		return *binDir
	}
	return filepath.Join(bin, fmt.Sprintf("%s_%s", getGOOS(), getGOARCH()))
}

// key will be the source file and value will be the target
func copyFiles(files map[string]string, installDir string) {
	for src, dst := range files {
		base := filepath.Base(src)
		installDst := filepath.Join(installDir, dst)
		log.Printf("Copying %s -> %s\n", src, installDst)
		stat, err := os.Stat(src)
		if err != nil {
			panic(err)
		}
		if stat.IsDir() {
			err = mirrorDir(src, installDst)
		} else {
			err = mirrorFile(src, filepath.Join(installDst, base))
		}
		if err != nil {
			panic(err)
		}
	}
}

func copyPluginFiles(destDir string) {
	files := make(map[string]string)
	if getGOOS() == "windows" {
		files[filepath.Join(getBinDir(), htmlReport+".exe")] = bin
	} else {
		files[filepath.Join(getBinDir(), htmlReport)] = bin
	}
	files[pluginJsonFile] = ""
	files[reportTemplate] = reportTemplate
	copyFiles(files, destDir)
}

func getPluginVersion() string {
	pluginProperties, err := getPluginProperties(pluginJsonFile)
	if err != nil {
		panic(fmt.Sprintf("Failed to get properties file. %s", err))
	}
	return pluginProperties["version"].(string)
}

func moveOSBinaryToCurrentOSArchDirectory(targetName string) {
	destDir := path.Join(bin, fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
	moveBinaryToDirectory(path.Base(targetName), destDir)
}

func moveBinaryToDirectory(target, destDir string) error {
	if runtime.GOOS == "windows" {
		target = target + ".exe"
	}
	srcFile := path.Join(bin, target)
	destFile := path.Join(destDir, target)
	if err := os.MkdirAll(destDir, newDirPermissions); err != nil {
		return err
	}
	if err := mirrorFile(srcFile, destFile); err != nil {
		return err
	}
	return os.Remove(srcFile)
}

func setEnv(envVariables map[string]string) {
	for k, v := range envVariables {
		os.Setenv(k, v)
	}
}

var install = flag.Bool("install", false, "Install to the specified prefix")
var pluginInstallPrefix = flag.String("plugin-prefix", "", "Specifies the prefix where the plugin will be installed")
var distro = flag.Bool("distro", false, "Creates distributables for the plugin")
var allPlatforms = flag.Bool("all-platforms", false, "Compiles or creates distributables for all platforms windows, linux, darwin both x86 and x86_64")
var binDir = flag.String("bin-dir", "", "Specifies OS_PLATFORM specific binaries to install when cross compiling")

var (
	platformEnvs = []map[string]string{
		map[string]string{GOARCH: X86, GOOS: DARWIN, CGO_ENABLED: "0"},
		map[string]string{GOARCH: X86_64, GOOS: DARWIN, CGO_ENABLED: "0"},
		map[string]string{GOARCH: X86, GOOS: LINUX, CGO_ENABLED: "0"},
		map[string]string{GOARCH: X86_64, GOOS: LINUX, CGO_ENABLED: "0"},
		map[string]string{GOARCH: X86, GOOS: WINDOWS, CGO_ENABLED: "0"},
		map[string]string{GOARCH: X86_64, GOOS: WINDOWS, CGO_ENABLED: "0"},
	}
)

func getPluginProperties(jsonPropertiesFile string) (map[string]interface{}, error) {
	pluginPropertiesJson, err := ioutil.ReadFile(jsonPropertiesFile)
	if err != nil {
		fmt.Printf("Could not read %s: %s\n", filepath.Base(jsonPropertiesFile), err)
		return nil, err
	}
	var pluginJson interface{}
	if err = json.Unmarshal([]byte(pluginPropertiesJson), &pluginJson); err != nil {
		fmt.Printf("Could not read %s: %s\n", filepath.Base(jsonPropertiesFile), err)
		return nil, err
	}
	return pluginJson.(map[string]interface{}), nil
}

func compileAcrossPlatforms() {
	for _, platformEnv := range platformEnvs {
		setEnv(platformEnv)
		fmt.Printf("Compiling for platform => OS:%s ARCH:%s \n", platformEnv[GOOS], platformEnv[GOARCH])
		compileGoPackage(htmlReport)
	}
}

func installPlugin(installPrefix string) {
	copyPluginFiles(deployDir)
	pluginInstallPath := filepath.Join(installPrefix, htmlReport, getPluginVersion())
	mirrorDir(deployDir, pluginInstallPath)
}

func updatePluginInstallPrefix() {
	if *pluginInstallPrefix == "" {
		if runtime.GOOS == "windows" {
			*pluginInstallPrefix = os.Getenv("APPDATA")
			if *pluginInstallPrefix == "" {
				panic(fmt.Errorf("Failed to find AppData directory"))
			}
			*pluginInstallPrefix = filepath.Join(*pluginInstallPrefix, gauge, plugins)
		} else {
			userHome := getUserHome()
			if userHome == "" {
				panic(fmt.Errorf("Failed to find User Home directory"))
			}
			*pluginInstallPrefix = filepath.Join(userHome, dotGauge, plugins)
		}
	}
}

func getUserHome() string {
	return os.Getenv("HOME")
}

func getArch() string {
	arch := getGOARCH()
	if arch == X86 {
		return "x86"
	}
	return "x86_64"
}

func getGOARCH() string {
	goArch := os.Getenv(GOARCH)
	if goArch == "" {
		return runtime.GOARCH

	}
	return goArch
}

func getGOOS() string {
	os := os.Getenv(GOOS)
	if os == "" {
		return runtime.GOOS

	}
	return os
}
<commit_msg>fix templates directory name<commit_after>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of getgauge/html-report.

// getgauge/html-report is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// getgauge/html-report is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with getgauge/html-report.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strings"
)

const (
	CGO_ENABLED = "CGO_ENABLED"
)

const (
	dotGauge          = ".gauge"
	plugins           = "plugins"
	GOARCH            = "GOARCH"
	goOS              = "GOOS"
	x86               = "386"
	x86_64            = "amd64"
	DARWIN            = "darwin"
	LINUX             = "linux"
	WINDOWS           = "windows"
	bin               = "bin"
	newDirPermissions = 0755
	gauge             = "gauge"
	htmlReport        = "html-report"
	deploy            = "deploy"
	pluginJSONFile    = "plugin.json"
	themesDir         = "themes"
)

var deployDir = filepath.Join(deploy, htmlReport)

func main() {
	flag.Parse()
	if *install {
		updatePluginInstallPrefix()
		installPlugin(*pluginInstallPrefix)
	} else if *distro {
		createPluginDistro(*allPlatforms)
	} else {
		compile()
	}
}

func compile() {
	if *allPlatforms {
		compileAcrossPlatforms()
	} else {
		compileGoPackage(htmlReport)
	}
}

func createPluginDistro(forAllPlatforms bool) {
	if forAllPlatforms {
		for _, platformEnv := range platformEnvs {
			setEnv(platformEnv)
			*binDir = filepath.Join(bin, fmt.Sprintf("%s_%s", platformEnv[goOS], platformEnv[GOARCH]))
			fmt.Printf("Creating distro for platform => OS:%s ARCH:%s \n", platformEnv[goOS], platformEnv[GOARCH])
			createDistro()
		}
	} else {
		createDistro()
	}
	log.Printf("Distributables created in directory => %s \n", deploy)
}

func createDistro() {
	packageName := fmt.Sprintf("%s-%s-%s.%s", htmlReport, getPluginVersion(), getGOOS(), getArch())
	distroDir := filepath.Join(deploy, packageName)
	copyPluginFiles(distroDir)
	createZipFromUtil(deploy, packageName)
	os.RemoveAll(distroDir)
}

func createZipFromUtil(dir, name string) {
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	os.Chdir(filepath.Join(dir, name))
	output, err := executeCommand("zip", "-r", filepath.Join("..", name+".zip"), ".")
	fmt.Println(output)
	if err != nil {
		panic(fmt.Sprintf("Failed to zip: %s", err))
	}
	os.Chdir(wd)
}

func isExecMode(mode os.FileMode) bool {
	return (mode & 0111) != 0
}

func mirrorFile(src, dst string) error {
	sfi, err := os.Stat(src)
	if err != nil {
		return err
	}
	if sfi.Mode()&os.ModeType != 0 {
		log.Fatalf("mirrorFile can't deal with non-regular file %s", src)
	}
	dfi, err := os.Stat(dst)
	if err == nil &&
		isExecMode(sfi.Mode()) == isExecMode(dfi.Mode()) &&
		(dfi.Mode()&os.ModeType == 0) &&
		dfi.Size() == sfi.Size() &&
		dfi.ModTime().Unix() == sfi.ModTime().Unix() {
		// Seems to not be modified.
		return nil
	}

	dstDir := filepath.Dir(dst)
	if err := os.MkdirAll(dstDir, newDirPermissions); err != nil {
		return err
	}

	df, err := os.Create(dst)
	if err != nil {
		return err
	}
	sf, err := os.Open(src)
	if err != nil {
		return err
	}
	defer sf.Close()

	n, err := io.Copy(df, sf)
	if err == nil && n != sfi.Size() {
		err = fmt.Errorf("copied wrong size for %s -> %s: copied %d; want %d", src, dst, n, sfi.Size())
	}
	cerr := df.Close()
	if err == nil {
		err = cerr
	}
	if err == nil {
		err = os.Chmod(dst, sfi.Mode())
	}
	if err == nil {
		err = os.Chtimes(dst, sfi.ModTime(), sfi.ModTime())
	}
	return err
}

func mirrorDir(src, dst string) error {
	log.Printf("Copying '%s' -> '%s'\n", src, dst)
	err := filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if fi.IsDir() {
			return nil
		}
		suffix, err := filepath.Rel(src, path)
		if err != nil {
			return fmt.Errorf("Failed to find Rel(%q, %q): %v", src, path, err)
		}
		return mirrorFile(path, filepath.Join(dst, suffix))
	})
	return err
}

func set(envName, envValue string) {
	log.Printf("%s = %s\n", envName, envValue)
	err := os.Setenv(envName, envValue)
	if err != nil {
		panic(err)
	}
}

func runProcess(command string, arg ...string) {
	cmd := exec.Command(command, arg...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	log.Printf("Execute %v\n", cmd.Args)
	err := cmd.Run()
	if err != nil {
		panic(err)
	}
}

func executeCommand(command string, arg ...string) (string, error) {
	cmd := exec.Command(command, arg...)
	bytes, err := cmd.Output()
	return strings.TrimSpace(fmt.Sprintf("%s", bytes)), err
}

func compileGoPackage(packageName string) {
	runProcess("go", "build", "-o", getGaugeExecutablePath(htmlReport))
}

func getGaugeExecutablePath(file string) string {
	return filepath.Join(getBinDir(), getExecutableName(file))
}

func getExecutableName(file string) string {
	if getGOOS() == "windows" {
		return file + ".exe"
	}
	return file
}

func getBinDir() string {
	if *binDir != "" {
		return *binDir
	}
	return filepath.Join(bin, fmt.Sprintf("%s_%s", getGOOS(), getGOARCH()))
}

// key will be the source file and value will be the target
func copyFiles(files map[string]string, installDir string) {
	for src, dst := range files {
		base := filepath.Base(src)
		installDst := filepath.Join(installDir, dst)
		log.Printf("Copying %s -> %s\n", src, installDst)
		stat, err := os.Stat(src)
		if err != nil {
			panic(err)
		}
		if stat.IsDir() {
			err = mirrorDir(src, installDst)
		} else {
			err = mirrorFile(src, filepath.Join(installDst, base))
		}
		if err != nil {
			panic(err)
		}
	}
}

func copyPluginFiles(destDir string) {
	files := make(map[string]string)
	if getGOOS() == "windows" {
		files[filepath.Join(getBinDir(), htmlReport+".exe")] = bin
	} else {
		files[filepath.Join(getBinDir(), htmlReport)] = bin
	}
	files[pluginJSONFile] = ""
	files[themesDir] = themesDir
	copyFiles(files, destDir)
}

func getPluginVersion() string {
	pluginProperties, err := getPluginProperties(pluginJSONFile)
	if err != nil {
		panic(fmt.Sprintf("Failed to get properties file. %s", err))
	}
	return pluginProperties["version"].(string)
}

func moveOSBinaryToCurrentOSArchDirectory(targetName string) {
	destDir := path.Join(bin, fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
	moveBinaryToDirectory(path.Base(targetName), destDir)
}

func moveBinaryToDirectory(target, destDir string) error {
	if runtime.GOOS == "windows" {
		target = target + ".exe"
	}
	srcFile := path.Join(bin, target)
	destFile := path.Join(destDir, target)
	if err := os.MkdirAll(destDir, newDirPermissions); err != nil {
		return err
	}
	if err := mirrorFile(srcFile, destFile); err != nil {
		return err
	}
	return os.Remove(srcFile)
}

func setEnv(envVariables map[string]string) {
	for k, v := range envVariables {
		os.Setenv(k, v)
	}
}

var install = flag.Bool("install", false, "Install to the specified prefix")
var pluginInstallPrefix = flag.String("plugin-prefix", "", "Specifies the prefix where the plugin will be installed")
var distro = flag.Bool("distro", false, "Creates distributables for the plugin")
var allPlatforms = flag.Bool("all-platforms", false, "Compiles or creates distributables for all platforms windows, linux, darwin both x86 and x86_64")
var binDir = flag.String("bin-dir", "", "Specifies OS_PLATFORM specific binaries to install when cross compiling")

var (
	platformEnvs = []map[string]string{
		map[string]string{GOARCH: x86, goOS: DARWIN, CGO_ENABLED: "0"},
		map[string]string{GOARCH: x86_64, goOS: DARWIN, CGO_ENABLED: "0"},
		map[string]string{GOARCH: x86, goOS: LINUX, CGO_ENABLED: "0"},
		map[string]string{GOARCH: x86_64, goOS: LINUX, CGO_ENABLED: "0"},
		map[string]string{GOARCH: x86, goOS: WINDOWS, CGO_ENABLED: "0"},
		map[string]string{GOARCH: x86_64, goOS: WINDOWS, CGO_ENABLED: "0"},
	}
)

func getPluginProperties(jsonPropertiesFile string) (map[string]interface{}, error) {
	pluginPropertiesJson, err := ioutil.ReadFile(jsonPropertiesFile)
	if err != nil {
		fmt.Printf("Could not read %s: %s\n", filepath.Base(jsonPropertiesFile), err)
		return nil, err
	}
	var pluginJson interface{}
	if err = json.Unmarshal([]byte(pluginPropertiesJson), &pluginJson); err != nil {
		fmt.Printf("Could not read %s: %s\n", filepath.Base(jsonPropertiesFile), err)
		return nil, err
	}
	return pluginJson.(map[string]interface{}), nil
}

func compileAcrossPlatforms() {
	for _, platformEnv := range platformEnvs {
		setEnv(platformEnv)
		fmt.Printf("Compiling for platform => OS:%s ARCH:%s \n", platformEnv[goOS], platformEnv[GOARCH])
		compileGoPackage(htmlReport)
	}
}

func installPlugin(installPrefix string) {
	copyPluginFiles(deployDir)
	pluginInstallPath := filepath.Join(installPrefix, htmlReport, getPluginVersion())
	mirrorDir(deployDir, pluginInstallPath)
}

func updatePluginInstallPrefix() {
	if *pluginInstallPrefix == "" {
		if runtime.GOOS == "windows" {
			*pluginInstallPrefix = os.Getenv("APPDATA")
			if *pluginInstallPrefix == "" {
				panic(fmt.Errorf("Failed to find AppData directory"))
			}
			*pluginInstallPrefix = filepath.Join(*pluginInstallPrefix, gauge, plugins)
		} else {
			userHome := getUserHome()
			if userHome == "" {
				panic(fmt.Errorf("Failed to find User Home directory"))
			}
			*pluginInstallPrefix = filepath.Join(userHome, dotGauge, plugins)
		}
	}
}

func getUserHome() string {
	return os.Getenv("HOME")
}

func getArch() string {
	arch := getGOARCH()
	if arch == x86 {
		return "x86"
	}
	return "x86_64"
}

func getGOARCH() string {
	goArch := os.Getenv(GOARCH)
	if goArch == "" {
		return runtime.GOARCH

	}
	return goArch
}

func getGOOS() string {
	os := os.Getenv(goOS)
	if os == "" {
		return runtime.GOOS

	}
	return os
}
<|endoftext|>


<commit_before>// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

// Package gfpool provides io-reusable pool for file pointer.
//
// 文件指针池.
package gfpool

import (
    "fmt"
    "github.com/gogf/gf/g/container/gmap"
    "github.com/gogf/gf/g/container/gpool"
    "github.com/gogf/gf/g/container/gtype"
    "github.com/gogf/gf/g/os/gfsnotify"
    "os"
    "sync"
)

// 文件指针池
type Pool struct {
    id         *gtype.Int        // 指针池ID，用以识别指针池是否重建
    pool       *gpool.Pool       // 底层对象池
    inited     *gtype.Bool       // 是否初始化(在执行第一次File方法后初始化，主要用于监听的添加，但是只能添加一次)
    expire     int               // 过期时间
}

// 文件指针池指针
type File struct {
    *os.File                // 底层文件指针
    mu     sync.RWMutex     // 互斥锁
    pool   *Pool            // 所属池
    poolid int              // 所属池ID，如果池ID不同表示池已经重建，那么该文件指针也应当销毁，不能重新丢到原有的池中
    flag   int              // 打开标志
    perm   os.FileMode      // 打开权限
    path   string           // 绝对路径
}

// 全局指针池，expire < 0表示不过期，expire = 0表示使用完立即回收，expire > 0表示超时回收
var pools = gmap.NewStringInterfaceMap()

// 获得文件对象，并自动创建指针池(过期时间单位：毫秒)
func Open(path string, flag int, perm os.FileMode, expire...int) (file *File, err error) {
    fpExpire := 0
    if len(expire) > 0 {
        fpExpire = expire[0]
    }
    pool := pools.GetOrSetFuncLock(fmt.Sprintf("%s&%d&%d&%d", path, flag, expire, perm), func() interface{} {
        return New(path, flag, perm, fpExpire)
    }).(*Pool)

    return pool.File()
}

func OpenFile(path string, flag int, perm os.FileMode, expire...int) (file *File, err error) {
    return Open(path, flag, perm, expire...)
}

// 创建一个文件指针池，expire = 0表示不过期，expire < 0表示使用完立即回收，expire > 0表示超时回收，默认值为0不过期
// 过期时间单位：毫秒
func New(path string, flag int, perm os.FileMode, expire...int) *Pool {
    fpExpire := 0
    if len(expire) > 0 {
        fpExpire = expire[0]
    }
    p := &Pool {
        id     : gtype.NewInt(),
        expire : fpExpire,
        inited : gtype.NewBool(),
    }
    p.pool = newFilePool(p, path, flag, perm, fpExpire)
    return p
}

// 创建文件指针池
func newFilePool(p *Pool, path string, flag int, perm os.FileMode, expire int) *gpool.Pool {
    pool := gpool.New(expire, func() (interface{}, error) {
        file, err := os.OpenFile(path, flag, perm)
        if err != nil {
            return nil, err
        }
        return &File {
            File   : file,
            pool   : p,
            poolid : p.id.Val(),
            flag   : flag,
            perm   : perm,
            path   : path,
        }, nil
    }, func(i interface{}) {
        i.(*File).File.Close()
    })
    return pool
}

// 获得一个文件打开指针
func (p *Pool) File() (*File, error) {
    if v, err := p.pool.Get(); err != nil {
        return nil, err
    } else {
        f         := v.(*File)
        stat, err := os.Stat(f.path)
        if f.flag & os.O_CREATE > 0 {
           if os.IsNotExist(err) {
               if file, err := os.OpenFile(f.path, f.flag, f.perm); err != nil {
                   return nil, err
               } else {
                   f.File = file
                   if stat, err = f.Stat(); err != nil {
                       return nil, err
                   }
               }
           }
        }
        if f.flag & os.O_TRUNC > 0 {
           if stat.Size() > 0 {
               if err := f.Truncate(0); err != nil {
                   return nil, err
               }
           }
        }
        if f.flag & os.O_APPEND > 0 {
           if _, err := f.Seek(0, 2); err != nil {
               return nil, err
           }
        } else {
           if _, err := f.Seek(0, 0); err != nil {
               return nil, err
           }
        }
        if !p.inited.Val() || p.inited.Set(true) == false {
            gfsnotify.Add(f.path, func(event *gfsnotify.Event) {
                // 如果文件被删除或者重命名，立即重建指针池
                if event.IsRemove() || event.IsRename() {
                    // 原有的指针都不要了
                    p.id.Add(1)
                    // Clear相当于重建指针池
                    p.pool.Clear()
                    // 为保证原子操作，但又不想加锁，
                    // 这里再执行一次原子Add，将在两次Add中间可能分配出去的文件指针丢弃掉
                    p.id.Add(1)
                }
            }, false)
        }
        return f, nil
    }
}

// 关闭指针池
func (p *Pool) Close() {
    p.pool.Close()
}

// 获得底层文件指针(返回error是标准库io.ReadWriteCloser接口实现)
func (f *File) Close() error {
    if f.poolid == f.pool.id.Val() {
        f.pool.pool.Put(f)
    }
    return nil
}
<commit_msg>fix issue of "memory leaks" in gfpool<commit_after>// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

// Package gfpool provides io-reusable pool for file pointer.
//
// 文件指针池.
package gfpool

import (
    "fmt"
    "github.com/gogf/gf/g/container/gmap"
    "github.com/gogf/gf/g/container/gpool"
    "github.com/gogf/gf/g/container/gtype"
    "github.com/gogf/gf/g/os/gfsnotify"
    "os"
    "sync"
)

// 文件指针池
type Pool struct {
    id         *gtype.Int        // 指针池ID，用以识别指针池是否重建
    pool       *gpool.Pool       // 底层对象池
    inited     *gtype.Bool       // 是否初始化(在执行第一次File方法后初始化，主要用于监听的添加，但是只能添加一次)
    expire     int               // 过期时间
}

// 文件指针池指针
type File struct {
    *os.File                // 底层文件指针
    mu     sync.RWMutex     // 互斥锁
    pool   *Pool            // 所属池
    poolid int              // 所属池ID，如果池ID不同表示池已经重建，那么该文件指针也应当销毁，不能重新丢到原有的池中
    flag   int              // 打开标志
    perm   os.FileMode      // 打开权限
    path   string           // 绝对路径
}

// 全局指针池，expire < 0表示不过期，expire = 0表示使用完立即回收，expire > 0表示超时回收
var pools = gmap.NewStringInterfaceMap()

// 获得文件对象，并自动创建指针池(过期时间单位：毫秒)
func Open(path string, flag int, perm os.FileMode, expire...int) (file *File, err error) {
    fpExpire := 0
    if len(expire) > 0 {
        fpExpire = expire[0]
    }
    pool := pools.GetOrSetFuncLock(fmt.Sprintf("%s&%d&%d&%d", path, flag, expire, perm), func() interface{} {
        return New(path, flag, perm, fpExpire)
    }).(*Pool)

    return pool.File()
}

func OpenFile(path string, flag int, perm os.FileMode, expire...int) (file *File, err error) {
    return Open(path, flag, perm, expire...)
}

// 创建一个文件指针池，expire = 0表示不过期，expire < 0表示使用完立即回收，expire > 0表示超时回收，默认值为0不过期
// 过期时间单位：毫秒
func New(path string, flag int, perm os.FileMode, expire...int) *Pool {
    fpExpire := 0
    if len(expire) > 0 {
        fpExpire = expire[0]
    }
    p := &Pool {
        id     : gtype.NewInt(),
        expire : fpExpire,
        inited : gtype.NewBool(),
    }
    p.pool = newFilePool(p, path, flag, perm, fpExpire)
    return p
}

// 创建文件指针池
func newFilePool(p *Pool, path string, flag int, perm os.FileMode, expire int) *gpool.Pool {
    pool := gpool.New(expire, func() (interface{}, error) {
        file, err := os.OpenFile(path, flag, perm)
        if err != nil {
            return nil, err
        }
        return &File {
            File   : file,
            pool   : p,
            poolid : p.id.Val(),
            flag   : flag,
            perm   : perm,
            path   : path,
        }, nil
    }, func(i interface{}) {
        i.(*File).File.Close()
    })
    return pool
}

// 获得一个文件打开指针
func (p *Pool) File() (*File, error) {
    if v, err := p.pool.Get(); err != nil {
        return nil, err
    } else {
        f         := v.(*File)
        stat, err := os.Stat(f.path)
        if f.flag & os.O_CREATE > 0 {
           if os.IsNotExist(err) {
               if file, err := os.OpenFile(f.path, f.flag, f.perm); err != nil {
                   return nil, err
               } else {
                   f.File = file
                   if stat, err = f.Stat(); err != nil {
                       return nil, err
                   }
               }
           }
        }
        if f.flag & os.O_TRUNC > 0 {
           if stat.Size() > 0 {
               if err := f.Truncate(0); err != nil {
                   return nil, err
               }
           }
        }
        if f.flag & os.O_APPEND > 0 {
           if _, err := f.Seek(0, 2); err != nil {
               return nil, err
           }
        } else {
           if _, err := f.Seek(0, 0); err != nil {
               return nil, err
           }
        }
        if p.inited.Set(true) == false {
            gfsnotify.Add(f.path, func(event *gfsnotify.Event) {
                // 如果文件被删除或者重命名，立即重建指针池
                if event.IsRemove() || event.IsRename() {
                    // 原有的指针都不要了
                    p.id.Add(1)
                    // Clear相当于重建指针池
                    p.pool.Clear()
                    // 为保证原子操作，但又不想加锁，
                    // 这里再执行一次原子Add，将在两次Add中间可能分配出去的文件指针丢弃掉
                    p.id.Add(1)
                }
            }, false)
        }
        return f, nil
    }
}

// 关闭指针池
func (p *Pool) Close() {
    p.pool.Close()
}

// 获得底层文件指针(返回error是标准库io.ReadWriteCloser接口实现)
func (f *File) Close() error {
    if f.poolid == f.pool.id.Val() {
        f.pool.pool.Put(f)
    }
    return nil
}
<|endoftext|>