<commit_before>package tail

import (
	"bufio"
	"fmt"
	"io"
	"launchpad.net/tomb"
	"log"
	"os"
	"time"
)

type Line struct {
	Text     string
	UnixTime int64
}

type Config struct {
	Location    int  // -n
	Follow      bool // -f
	ReOpen      bool // -F
	MustExist   bool // if false, wait for the file to exist before beginning to tail.
	Poll        bool // if true, do not use inotify but use polling
	MaxLineSize int  // if > 0, limit the line size (rest of the line would be returned as next lines)
}

type Tail struct {
	Filename string
	Lines    chan *Line
	Config

	file    *os.File
	reader  *bufio.Reader
	watcher FileWatcher

	tomb.Tomb // provides: Done, Kill, Dying
}

// TailFile channels the lines of a logfile along with timestamp. If
// end is true, channel only newly added lines. If retry is true, tail
// the file name (not descriptor) and retry on file open/read errors.
// func TailFile(filename string, maxlinesize int, end bool, retry bool, useinotify bool) (*Tail, error) {
func TailFile(filename string, config Config) (*Tail, error) {
	if !(config.Location == 0 || config.Location == -1) {
		panic("only 0/-1 values are supported for Location.")
	}

	if config.ReOpen && !config.Follow {
		panic("cannot set ReOpen without Follow.")
	}

	if !config.Follow {
		panic("Follow=false is not supported.")
	}

	t := &Tail{
		Filename: filename,
		Lines:    make(chan *Line),
		Config:   config}

	if t.Poll {
		log.Println("Warning: not using inotify; will poll ", filename)
		t.watcher = NewPollingFileWatcher(filename)
	} else {
		t.watcher = NewInotifyFileWatcher(filename)
	}

	if t.MustExist {
		var err error
		t.file, err = os.Open(t.Filename)
		if err != nil {
			return nil, err
		}
	}

	go t.tailFileSync()

	return t, nil
}

func (tail *Tail) Stop() error {
	tail.Kill(nil)
	return tail.Wait()
}

func (tail *Tail) close() {
	close(tail.Lines)
	if tail.file != nil {
		tail.file.Close()
	}
}

func (tail *Tail) reopen() error {
	if tail.file != nil {
		tail.file.Close()
	}
	for {
		var err error
		tail.file, err = os.Open(tail.Filename)
		if err != nil {
			if os.IsNotExist(err) {
				log.Printf("Waiting for %s to appear...", tail.Filename)
				err := tail.watcher.BlockUntilExists()
				if err != nil {
					return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
				}
				continue
			}
			return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
		}
		break
	}
	return nil
}

func (tail *Tail) readLine() ([]byte, error) {
	line, _, err := tail.reader.ReadLine()
	return line, err
}

func (tail *Tail) tailFileSync() {
	defer tail.Done()

	if !tail.MustExist {
		err := tail.reopen()
		if err != nil {
			tail.close()
			tail.Kill(err)
			return
		}
	}

	var changes chan bool

	// Note: seeking to end happens only at the beginning of tail;
	// never during subsequent re-opens.
	if tail.Location == 0 {
		_, err := tail.file.Seek(0, 2) // seek to end of the file
		if err != nil {
			tail.close()
			tail.Killf("Seek error on %s: %s", tail.Filename, err)
			return
		}
	}

	tail.reader = bufio.NewReader(tail.file)

	for {
		line, err := tail.readLine()

		if err == nil {
			if line != nil {
				now := getCurrentTime()
				if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
					for _, line := range partitionString(string(line), tail.MaxLineSize) {
						tail.Lines <- &Line{line, now}
					}
				}else{
					tail.Lines <- &Line{string(line), now}
				}
			}
		} else {
			if err != io.EOF {
				tail.close()
				tail.Killf("Error reading %s: %s", tail.Filename, err)
				return
			}

			// When end of file is reached, wait for more data to
			// become available. Wait strategy is based on the
			// `tail.watcher` implementation (inotify or polling).
			if err == io.EOF {
				if changes == nil {
					changes = tail.watcher.ChangeEvents()
				}

				select {
				case _, ok := <-changes:
					if !ok {
						// File got deleted/renamed
						if tail.ReOpen {
							// TODO: no logging in a library?
							log.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
							err := tail.reopen()
							if err != nil {
								tail.close()
								tail.Kill(err)
								return
							}
							log.Printf("Successfully reopened %s", tail.Filename)
							tail.reader = bufio.NewReader(tail.file)
							changes = nil // XXX: how to kill changes' goroutine?
							continue
						} else {
							log.Printf("Finishing because file has been moved/deleted: %s", tail.Filename)
							tail.close()
							return
						}
					}
				case <-tail.Dying():
					tail.close()
					return
				}
			}
		}

		select {
		case <-tail.Dying():
			tail.close()
			return
		default:
		}
	}
}

// getCurrentTime returns the current time as UNIX timestamp
func getCurrentTime() int64 {
	return time.Now().UTC().Unix()
}

// partitionString partitions the string into chunks of given size,
// with the last chunk of variable size.
func partitionString(s string, chunkSize int) []string {
	if chunkSize <= 0 {
		panic("invalid chunkSize")
	}
	length := len(s)
	chunks := 1 + length/chunkSize
	start := 0
	end := chunkSize
	parts := make([]string, 0, chunks)
	for {
		if end > length {
			end = length
		}
		parts = append(parts, s[start:end])
		if end == length {
			break
		}
		start, end = end, end+chunkSize
	}
	return parts
}
<commit_msg>expose the time object to allow custom formatting ref - http://bugs.activestate.com/show_bug.cgi?id=96009<commit_after>package tail

import (
	"bufio"
	"fmt"
	"io"
	"launchpad.net/tomb"
	"log"
	"os"
	"time"
)

type Line struct {
	Text     string
	Time     time.Time
}

type Config struct {
	Location    int  // -n
	Follow      bool // -f
	ReOpen      bool // -F
	MustExist   bool // if false, wait for the file to exist before beginning to tail.
	Poll        bool // if true, do not use inotify but use polling
	MaxLineSize int  // if > 0, limit the line size (rest of the line would be returned as next lines)
}

type Tail struct {
	Filename string
	Lines    chan *Line
	Config

	file    *os.File
	reader  *bufio.Reader
	watcher FileWatcher

	tomb.Tomb // provides: Done, Kill, Dying
}

// TailFile channels the lines of a logfile along with timestamp. If
// end is true, channel only newly added lines. If retry is true, tail
// the file name (not descriptor) and retry on file open/read errors.
// func TailFile(filename string, maxlinesize int, end bool, retry bool, useinotify bool) (*Tail, error) {
func TailFile(filename string, config Config) (*Tail, error) {
	if !(config.Location == 0 || config.Location == -1) {
		panic("only 0/-1 values are supported for Location.")
	}

	if config.ReOpen && !config.Follow {
		panic("cannot set ReOpen without Follow.")
	}

	if !config.Follow {
		panic("Follow=false is not supported.")
	}

	t := &Tail{
		Filename: filename,
		Lines:    make(chan *Line),
		Config:   config}

	if t.Poll {
		log.Println("Warning: not using inotify; will poll ", filename)
		t.watcher = NewPollingFileWatcher(filename)
	} else {
		t.watcher = NewInotifyFileWatcher(filename)
	}

	if t.MustExist {
		var err error
		t.file, err = os.Open(t.Filename)
		if err != nil {
			return nil, err
		}
	}

	go t.tailFileSync()

	return t, nil
}

func (tail *Tail) Stop() error {
	tail.Kill(nil)
	return tail.Wait()
}

func (tail *Tail) close() {
	close(tail.Lines)
	if tail.file != nil {
		tail.file.Close()
	}
}

func (tail *Tail) reopen() error {
	if tail.file != nil {
		tail.file.Close()
	}
	for {
		var err error
		tail.file, err = os.Open(tail.Filename)
		if err != nil {
			if os.IsNotExist(err) {
				log.Printf("Waiting for %s to appear...", tail.Filename)
				err := tail.watcher.BlockUntilExists()
				if err != nil {
					return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
				}
				continue
			}
			return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
		}
		break
	}
	return nil
}

func (tail *Tail) readLine() ([]byte, error) {
	line, _, err := tail.reader.ReadLine()
	return line, err
}

func (tail *Tail) tailFileSync() {
	defer tail.Done()

	if !tail.MustExist {
		err := tail.reopen()
		if err != nil {
			tail.close()
			tail.Kill(err)
			return
		}
	}

	var changes chan bool

	// Note: seeking to end happens only at the beginning of tail;
	// never during subsequent re-opens.
	if tail.Location == 0 {
		_, err := tail.file.Seek(0, 2) // seek to end of the file
		if err != nil {
			tail.close()
			tail.Killf("Seek error on %s: %s", tail.Filename, err)
			return
		}
	}

	tail.reader = bufio.NewReader(tail.file)

	for {
		line, err := tail.readLine()

		if err == nil {
			if line != nil {
				now := time.Now()
				if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
					for _, line := range partitionString(string(line), tail.MaxLineSize) {
						tail.Lines <- &Line{line, now}
					}
				}else{
					tail.Lines <- &Line{string(line), now}
				}
			}
		} else {
			if err != io.EOF {
				tail.close()
				tail.Killf("Error reading %s: %s", tail.Filename, err)
				return
			}

			// When end of file is reached, wait for more data to
			// become available. Wait strategy is based on the
			// `tail.watcher` implementation (inotify or polling).
			if err == io.EOF {
				if changes == nil {
					changes = tail.watcher.ChangeEvents()
				}

				select {
				case _, ok := <-changes:
					if !ok {
						// File got deleted/renamed
						if tail.ReOpen {
							// TODO: no logging in a library?
							log.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
							err := tail.reopen()
							if err != nil {
								tail.close()
								tail.Kill(err)
								return
							}
							log.Printf("Successfully reopened %s", tail.Filename)
							tail.reader = bufio.NewReader(tail.file)
							changes = nil // XXX: how to kill changes' goroutine?
							continue
						} else {
							log.Printf("Finishing because file has been moved/deleted: %s", tail.Filename)
							tail.close()
							return
						}
					}
				case <-tail.Dying():
					tail.close()
					return
				}
			}
		}

		select {
		case <-tail.Dying():
			tail.close()
			return
		default:
		}
	}
}

// partitionString partitions the string into chunks of given size,
// with the last chunk of variable size.
func partitionString(s string, chunkSize int) []string {
	if chunkSize <= 0 {
		panic("invalid chunkSize")
	}
	length := len(s)
	chunks := 1 + length/chunkSize
	start := 0
	end := chunkSize
	parts := make([]string, 0, chunks)
	for {
		if end > length {
			end = length
		}
		parts = append(parts, s[start:end])
		if end == length {
			break
		}
		start, end = end, end+chunkSize
	}
	return parts
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.

package tail

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"time"

	"github.com/masahide/tail/ratelimiter"
	"github.com/masahide/tail/util"
	"github.com/masahide/tail/watch"
	"gopkg.in/tomb.v1"
)

var (
	ErrStop = fmt.Errorf("tail should now stop")
)

const (
	NewLineNotify int = iota
	NewFileNotify
	TickerNotify
)

type Line struct {
	Time       time.Time
	Text       []byte
	Filename   string
	Offset     int64
	OpenTime   time.Time
	Err        error // Error from tail
	NotifyType int
}

// NewLine returns a Line with present time.
func NewLine(text []byte) *Line {
	return &Line{Text: text, Time: time.Now(), Err: nil}
}

// SeekInfo represents arguments to `os.Seek`
type SeekInfo struct {
	Offset int64
	Whence int // os.SEEK_*
}

// Config is used to specify how a file must be tailed.
type Config struct {
	// File-specifc
	Location    *SeekInfo // Seek to this location before tailing
	ReOpen      bool      // Reopen recreated files (tail -F)
	MustExist   bool      // Fail early if the file does not exist
	Poll        bool      // Poll for file changes instead of using inotify
	RateLimiter *ratelimiter.LeakyBucket

	// Generic IO
	Follow         bool          // Continue looking for new lines (tail -f)
	MaxLineSize    int           // If non-zero, split longer lines into multiple lines
	NotifyInterval time.Duration // Notice interval of the elapsed time

	// Logger, when nil, is set to tail.DefaultLogger
	// To disable logging: set field to tail.DiscardingLogger
	Logger *log.Logger
}

type Tail struct {
	Filename string
	Lines    chan *Line
	Config

	file     *os.File
	reader   *bufio.Reader
	tracker  *watch.InotifyTracker
	ticker   *time.Ticker
	openTime time.Time

	watcher watch.FileWatcher
	changes *watch.FileChanges

	tomb.Tomb // provides: Done, Kill, Dying
}

var (
	// DefaultLogger is used when Config.Logger == nil
	DefaultLogger = log.New(os.Stderr, "", log.LstdFlags)
	// DiscardingLogger can be used to disable logging output
	DiscardingLogger = log.New(ioutil.Discard, "", 0)
)

// TailFile begins tailing the file. Output stream is made available
// via the `Tail.Lines` channel. To handle errors during tailing,
// invoke the `Wait` or `Err` method after finishing reading from the
// `Lines` channel.
func TailFile(filename string, config Config) (*Tail, error) {
	if config.ReOpen && !config.Follow {
		util.Fatal("cannot set ReOpen without Follow.")
	}

	t := &Tail{
		Filename: filename,
		Lines:    make(chan *Line),
		Config:   config,
	}

	// when Logger was not specified in config, use default logger
	if t.Logger == nil {
		t.Logger = log.New(os.Stderr, "", log.LstdFlags)
	}

	if t.Poll {
		t.watcher = watch.NewPollingFileWatcher(filename)
	} else {
		t.tracker = watch.NewInotifyTracker()
		w, err := t.tracker.NewWatcher()
		if err != nil {
			return nil, err
		}
		t.watcher = watch.NewInotifyFileWatcher(filename, w)
	}

	if t.MustExist {
		var err error
		t.file, err = OpenFile(t.Filename)
		if err != nil {
			return nil, err
		}
	}

	t.ticker = &time.Ticker{}
	if t.NotifyInterval != 0 {
		t.ticker = time.NewTicker(t.NotifyInterval)
	}
	go t.tailFileSync()

	return t, nil
}

// Return the file's current position, like stdio's ftell().
// But this value is not very accurate.
// it may readed one line in the chan(tail.Lines),
// so it may lost one line.
func (tail *Tail) Tell() (offset int64, err error) {
	if tail.file == nil {
		return
	}
	offset, err = tail.file.Seek(0, os.SEEK_CUR)
	if err == nil {
		offset -= int64(tail.reader.Buffered())
	}
	return
}

// Stop stops the tailing activity.
func (tail *Tail) Stop() error {
	tail.Kill(nil)
	return tail.Wait()
}

func (tail *Tail) close() {
	close(tail.Lines)
	if tail.file != nil {
		tail.file.Close()
	}
}

func (tail *Tail) reopen() error {
	if tail.file != nil {
		tail.file.Close()
	}
	for {
		var err error
		tail.file, err = OpenFile(tail.Filename)
		if err != nil {
			if os.IsNotExist(err) {
				tail.Logger.Printf("Waiting for %s to appear...", tail.Filename)
				if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil {
					if err == tomb.ErrDying {
						return err
					}
					return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
				}
				continue
			}
			return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
		}
		break
	}
	return nil
}

func (tail *Tail) readLine() ([]byte, error) {
	line, err := tail.reader.ReadBytes(byte('\n'))
	if err != nil {
		// Note ReadString "returns the data read before the error" in
		// case of an error, including EOF, so we return it as is. The
		// caller is expected to process it if err is EOF.
		return line, err
	}

	//line = bytes.TrimRight(line, "\n")

	return line, err
}

func (tail *Tail) tailFileSync() {
	defer tail.Done()
	defer tail.close()

	if !tail.MustExist {
		// deferred first open.
		err := tail.reopen()
		if err != nil {
			if err != tomb.ErrDying {
				tail.Kill(err)
			}
			return
		}
	}

	// Seek to requested location on first open of the file.
	offset := int64(0)
	if tail.Location != nil {
		offset = tail.Location.Offset
		_, err := tail.file.Seek(offset, tail.Location.Whence)
		tail.Logger.Printf("Seeked %s - %+v\n", tail.Filename, tail.Location)
		if err != nil {
			tail.Killf("Seek error on %s: %s", tail.Filename, err)
			return
		}
	}

	tail.openReader()
	tail.Lines <- &Line{NotifyType: NewFileNotify, Filename: tail.Filename, Offset: offset, Time: time.Now(), OpenTime: tail.openTime}

	// Read line by line.
	for {
		// grab the position in case we need to back up in the event of a half-line
		offset, err := tail.Tell()
		if err != nil {
			tail.Kill(err)
			return
		}

		line, err := tail.readLine()

		// Process `line` even if err is EOF.
		if err == nil {
			cooloff := !tail.sendLine(line)
			if cooloff {
				// Wait a second before seeking till the end of
				// file when rate limit is reached.
				msg := fmt.Sprintf(
					"Too much log activity; waiting a second " +
						"before resuming tailing")
				tail.Lines <- &Line{Text: []byte(msg), Time: time.Now(), Filename: tail.Filename, OpenTime: tail.openTime, Offset: offset, Err: fmt.Errorf(msg)}
				select {
				case <-time.After(time.Second):
				case <-tail.Dying():
					return
				}
				err = tail.seekEnd()
				if err != nil {
					tail.Kill(err)
					return
				}
			}
		} else if err == io.EOF {
			if !tail.Follow {
				if len(line) != 0 {
					tail.sendLine(line)
				}
				return
			}

			if tail.Follow && len(line) != 0 {
				// this has the potential to never return the last line if
				// it's not followed by a newline; seems a fair trade here
				err := tail.seekTo(SeekInfo{Offset: offset, Whence: 0})
				if err != nil {
					tail.Kill(err)
					return
				}
			}

			// When EOF is reached, wait for more data to become
			// available. Wait strategy is based on the `tail.watcher`
			// implementation (inotify or polling).
			err := tail.waitForChanges()
			if err != nil {
				if err != ErrStop {
					tail.Kill(err)
				}
				return
			}
		} else {
			// non-EOF error
			tail.Killf("Error reading %s: %s", tail.Filename, err)
			return
		}

		select {
		case <-tail.Dying():
			return
		default:
		}
	}
}

// waitForChanges waits until the file has been appended, deleted,
// moved or truncated. When moved or deleted - the file will be
// reopened if ReOpen is true. Truncated files are always reopened.
func (tail *Tail) waitForChanges() error {
	if tail.changes == nil {
		st, err := tail.file.Stat()
		if err != nil {
			return err
		}
		tail.changes = tail.watcher.ChangeEvents(&tail.Tomb, st)
	}

	for {

		select {
		case <-tail.ticker.C:
			offset, err := tail.Tell()
			if err != nil {
				return err
			}
			tail.Lines <- &Line{NotifyType: TickerNotify, Time: time.Now(), Filename: tail.Filename, OpenTime: tail.openTime, Offset: offset}
			continue
		case <-tail.changes.Modified:
			return nil
		case <-tail.changes.Deleted:
			tail.changes = nil
			if tail.ReOpen {
				// XXX: we must not log from a library.
				tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
				if err := tail.reopen(); err != nil {
					return err
				}
				tail.Logger.Printf("Successfully reopened %s", tail.Filename)
				tail.openReader()
				return nil
			} else {
				tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename)
				return ErrStop
			}
		case <-tail.changes.Truncated:
			// Always reopen truncated files (Follow is true)
			tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename)
			if err := tail.reopen(); err != nil {
				return err
			}
			tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename)
			tail.openReader()
			return nil
		case <-tail.Dying():
			return ErrStop
		}
		panic("unreachable")
	}
}

func (tail *Tail) openReader() {
	if tail.MaxLineSize > 0 {
		// add 2 to account for newline characters
		tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2)
	} else {
		tail.reader = bufio.NewReader(tail.file)
	}
	fi, err := os.Stat(tail.Filename)
	if err != nil {
		tail.openTime = time.Now()
		return
	}
	tail.openTime = fi.ModTime()
}

func (tail *Tail) seekEnd() error {
	return tail.seekTo(SeekInfo{Offset: 0, Whence: 2})
}

func (tail *Tail) seekTo(pos SeekInfo) error {
	_, err := tail.file.Seek(pos.Offset, pos.Whence)
	if err != nil {
		return fmt.Errorf("Seek error on %s: %s", tail.Filename, err)
	}
	// Reset the read buffer whenever the file is re-seek'ed
	tail.reader.Reset(tail.file)
	return nil
}

// sendLine sends the line(s) to Lines channel, splitting longer lines
// if necessary. Return false if rate limit is reached.
func (tail *Tail) sendLine(line []byte) bool {
	now := time.Now()
	offset, err := tail.Tell()
	if err != nil {
		tail.Kill(err)
		return true
	}
	tail.Lines <- &Line{NotifyType: NewLineNotify, Text: line, Time: now, Filename: tail.Filename, OpenTime: tail.openTime, Offset: offset}

	if tail.Config.RateLimiter != nil {
		ok := tail.Config.RateLimiter.Pour(uint16(1))
		if !ok {
			tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.\n",
				tail.Filename)
			return false
		}
	}

	return true
}

// Cleanup removes inotify watches added by the tail package. This function is
// meant to be invoked from a process's exit handler. Linux kernel may not
// automatically remove inotify watches after the process exits.
func (tail *Tail) Cleanup() {
	if tail.tracker != nil {
		tail.tracker.CloseAll()
	}
}
<commit_msg>fix ticker.Stop()<commit_after>// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.

package tail

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"time"

	"github.com/masahide/tail/ratelimiter"
	"github.com/masahide/tail/util"
	"github.com/masahide/tail/watch"
	"gopkg.in/tomb.v1"
)

var (
	ErrStop = fmt.Errorf("tail should now stop")
)

const (
	NewLineNotify int = iota
	NewFileNotify
	TickerNotify
)

type Line struct {
	Time       time.Time
	Text       []byte
	Filename   string
	Offset     int64
	OpenTime   time.Time
	Err        error // Error from tail
	NotifyType int
}

// NewLine returns a Line with present time.
func NewLine(text []byte) *Line {
	return &Line{Text: text, Time: time.Now(), Err: nil}
}

// SeekInfo represents arguments to `os.Seek`
type SeekInfo struct {
	Offset int64
	Whence int // os.SEEK_*
}

// Config is used to specify how a file must be tailed.
type Config struct {
	// File-specifc
	Location    *SeekInfo // Seek to this location before tailing
	ReOpen      bool      // Reopen recreated files (tail -F)
	MustExist   bool      // Fail early if the file does not exist
	Poll        bool      // Poll for file changes instead of using inotify
	RateLimiter *ratelimiter.LeakyBucket

	// Generic IO
	Follow         bool          // Continue looking for new lines (tail -f)
	MaxLineSize    int           // If non-zero, split longer lines into multiple lines
	NotifyInterval time.Duration // Notice interval of the elapsed time

	// Logger, when nil, is set to tail.DefaultLogger
	// To disable logging: set field to tail.DiscardingLogger
	Logger *log.Logger
}

type Tail struct {
	Filename string
	Lines    chan *Line
	Config

	file     *os.File
	reader   *bufio.Reader
	tracker  *watch.InotifyTracker
	ticker   *time.Ticker
	openTime time.Time

	watcher watch.FileWatcher
	changes *watch.FileChanges

	tomb.Tomb // provides: Done, Kill, Dying
}

var (
	// DefaultLogger is used when Config.Logger == nil
	DefaultLogger = log.New(os.Stderr, "", log.LstdFlags)
	// DiscardingLogger can be used to disable logging output
	DiscardingLogger = log.New(ioutil.Discard, "", 0)
)

// TailFile begins tailing the file. Output stream is made available
// via the `Tail.Lines` channel. To handle errors during tailing,
// invoke the `Wait` or `Err` method after finishing reading from the
// `Lines` channel.
func TailFile(filename string, config Config) (*Tail, error) {
	if config.ReOpen && !config.Follow {
		util.Fatal("cannot set ReOpen without Follow.")
	}

	t := &Tail{
		Filename: filename,
		Lines:    make(chan *Line),
		Config:   config,
	}

	// when Logger was not specified in config, use default logger
	if t.Logger == nil {
		t.Logger = log.New(os.Stderr, "", log.LstdFlags)
	}

	if t.Poll {
		t.watcher = watch.NewPollingFileWatcher(filename)
	} else {
		t.tracker = watch.NewInotifyTracker()
		w, err := t.tracker.NewWatcher()
		if err != nil {
			return nil, err
		}
		t.watcher = watch.NewInotifyFileWatcher(filename, w)
	}

	if t.MustExist {
		var err error
		t.file, err = OpenFile(t.Filename)
		if err != nil {
			return nil, err
		}
	}

	go t.tailFileSync()

	return t, nil
}

// Return the file's current position, like stdio's ftell().
// But this value is not very accurate.
// it may readed one line in the chan(tail.Lines),
// so it may lost one line.
func (tail *Tail) Tell() (offset int64, err error) {
	if tail.file == nil {
		return
	}
	offset, err = tail.file.Seek(0, os.SEEK_CUR)
	if err == nil {
		offset -= int64(tail.reader.Buffered())
	}
	return
}

// Stop stops the tailing activity.
func (tail *Tail) Stop() error {
	tail.Kill(nil)
	return tail.Wait()
}

func (tail *Tail) close() {
	close(tail.Lines)
	if tail.file != nil {
		tail.file.Close()
	}
}

func (tail *Tail) reopen() error {
	if tail.file != nil {
		tail.file.Close()
	}
	for {
		var err error
		tail.file, err = OpenFile(tail.Filename)
		if err != nil {
			if os.IsNotExist(err) {
				tail.Logger.Printf("Waiting for %s to appear...", tail.Filename)
				if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil {
					if err == tomb.ErrDying {
						return err
					}
					return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
				}
				continue
			}
			return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
		}
		break
	}
	return nil
}

func (tail *Tail) readLine() ([]byte, error) {
	line, err := tail.reader.ReadBytes(byte('\n'))
	if err != nil {
		// Note ReadString "returns the data read before the error" in
		// case of an error, including EOF, so we return it as is. The
		// caller is expected to process it if err is EOF.
		return line, err
	}

	//line = bytes.TrimRight(line, "\n")

	return line, err
}

func (tail *Tail) tailFileSync() {
	defer tail.Done()
	defer tail.close()

	tail.ticker = &time.Ticker{}
	if tail.NotifyInterval != 0 {
		tail.ticker = time.NewTicker(tail.NotifyInterval)
	}
	defer tail.ticker.Stop()

	if !tail.MustExist {
		// deferred first open.
		err := tail.reopen()
		if err != nil {
			if err != tomb.ErrDying {
				tail.Kill(err)
			}
			return
		}
	}

	// Seek to requested location on first open of the file.
	offset := int64(0)
	if tail.Location != nil {
		offset = tail.Location.Offset
		_, err := tail.file.Seek(offset, tail.Location.Whence)
		tail.Logger.Printf("Seeked %s - %+v\n", tail.Filename, tail.Location)
		if err != nil {
			tail.Killf("Seek error on %s: %s", tail.Filename, err)
			return
		}
	}

	tail.openReader()
	tail.Lines <- &Line{NotifyType: NewFileNotify, Filename: tail.Filename, Offset: offset, Time: time.Now(), OpenTime: tail.openTime}

	// Read line by line.
	for {
		// grab the position in case we need to back up in the event of a half-line
		offset, err := tail.Tell()
		if err != nil {
			tail.Kill(err)
			return
		}

		line, err := tail.readLine()

		// Process `line` even if err is EOF.
		if err == nil {
			cooloff := !tail.sendLine(line)
			if cooloff {
				// Wait a second before seeking till the end of
				// file when rate limit is reached.
				msg := fmt.Sprintf(
					"Too much log activity; waiting a second " +
						"before resuming tailing")
				tail.Lines <- &Line{Text: []byte(msg), Time: time.Now(), Filename: tail.Filename, OpenTime: tail.openTime, Offset: offset, Err: fmt.Errorf(msg)}
				select {
				case <-time.After(time.Second):
				case <-tail.Dying():
					return
				}
				err = tail.seekEnd()
				if err != nil {
					tail.Kill(err)
					return
				}
			}
		} else if err == io.EOF {
			if !tail.Follow {
				if len(line) != 0 {
					tail.sendLine(line)
				}
				return
			}

			if tail.Follow && len(line) != 0 {
				// this has the potential to never return the last line if
				// it's not followed by a newline; seems a fair trade here
				err := tail.seekTo(SeekInfo{Offset: offset, Whence: 0})
				if err != nil {
					tail.Kill(err)
					return
				}
			}

			// When EOF is reached, wait for more data to become
			// available. Wait strategy is based on the `tail.watcher`
			// implementation (inotify or polling).
			err := tail.waitForChanges()
			if err != nil {
				if err != ErrStop {
					tail.Kill(err)
				}
				return
			}
		} else {
			// non-EOF error
			tail.Killf("Error reading %s: %s", tail.Filename, err)
			return
		}

		select {
		case <-tail.Dying():
			return
		default:
		}
	}
}

// waitForChanges waits until the file has been appended, deleted,
// moved or truncated. When moved or deleted - the file will be
// reopened if ReOpen is true. Truncated files are always reopened.
func (tail *Tail) waitForChanges() error {
	if tail.changes == nil {
		st, err := tail.file.Stat()
		if err != nil {
			return err
		}
		tail.changes = tail.watcher.ChangeEvents(&tail.Tomb, st)
	}

	for {

		select {
		case <-tail.ticker.C:
			offset, err := tail.Tell()
			if err != nil {
				return err
			}
			tail.Lines <- &Line{NotifyType: TickerNotify, Time: time.Now(), Filename: tail.Filename, OpenTime: tail.openTime, Offset: offset}
			continue
		case <-tail.changes.Modified:
			return nil
		case <-tail.changes.Deleted:
			tail.changes = nil
			if tail.ReOpen {
				// XXX: we must not log from a library.
				tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
				if err := tail.reopen(); err != nil {
					return err
				}
				tail.Logger.Printf("Successfully reopened %s", tail.Filename)
				tail.openReader()
				return nil
			} else {
				tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename)
				return ErrStop
			}
		case <-tail.changes.Truncated:
			// Always reopen truncated files (Follow is true)
			tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename)
			if err := tail.reopen(); err != nil {
				return err
			}
			tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename)
			tail.openReader()
			return nil
		case <-tail.Dying():
			return ErrStop
		}
		panic("unreachable")
	}
}

func (tail *Tail) openReader() {
	if tail.MaxLineSize > 0 {
		// add 2 to account for newline characters
		tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2)
	} else {
		tail.reader = bufio.NewReader(tail.file)
	}
	fi, err := os.Stat(tail.Filename)
	if err != nil {
		tail.openTime = time.Now()
		return
	}
	tail.openTime = fi.ModTime()
}

func (tail *Tail) seekEnd() error {
	return tail.seekTo(SeekInfo{Offset: 0, Whence: 2})
}

func (tail *Tail) seekTo(pos SeekInfo) error {
	_, err := tail.file.Seek(pos.Offset, pos.Whence)
	if err != nil {
		return fmt.Errorf("Seek error on %s: %s", tail.Filename, err)
	}
	// Reset the read buffer whenever the file is re-seek'ed
	tail.reader.Reset(tail.file)
	return nil
}

// sendLine sends the line(s) to Lines channel, splitting longer lines
// if necessary. Return false if rate limit is reached.
func (tail *Tail) sendLine(line []byte) bool {
	now := time.Now()
	offset, err := tail.Tell()
	if err != nil {
		tail.Kill(err)
		return true
	}
	tail.Lines <- &Line{NotifyType: NewLineNotify, Text: line, Time: now, Filename: tail.Filename, OpenTime: tail.openTime, Offset: offset}

	if tail.Config.RateLimiter != nil {
		ok := tail.Config.RateLimiter.Pour(uint16(1))
		if !ok {
			tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.\n",
				tail.Filename)
			return false
		}
	}

	return true
}

// Cleanup removes inotify watches added by the tail package. This function is
// meant to be invoked from a process's exit handler. Linux kernel may not
// automatically remove inotify watches after the process exits.
func (tail *Tail) Cleanup() {
	if tail.tracker != nil {
		tail.tracker.CloseAll()
	}
}
<|endoftext|>


<commit_before>package tasker

import (
	"context"
	"errors"
	"time"

	"github.com/NoneBorder/dora"
	"github.com/astaxie/beego/orm"
)

const (
	TaskStatPending = "pending"
	TaskStatRunning = "running"
	TaskStatRetry   = "retry"
	TaskStatFailed  = "failed"
	TaskStatSuccess = "success"
)

const (
	TaskCtxKeyLog       = "stdout"          // normal log record
	TaskCtxKeyExecAfter = "task-exec-after" // plan task exec time
)

// Task is the core object for tasker package.
type Task struct {
	Id         uint64
	Topic      string
	Status     string
	Timeout    int // 超时ms
	ExecAfter  time.Time `orm:"type(datetime);default(1971-01-01 00:00:00)"`
	Retry      int
	Input      string    `orm:"type(text)"`
	WorkerId   uint64
	WorkerFQDN string    `orm:"column(worker_fqdn);type(text)"`
	Created    time.Time `orm:"auto_now_add"`
	Updated    time.Time `orm:"auto_now"`
	Log        string    `orm:"type(text)"`
	Errlog     string    `orm:"type(text)"`
}

// ConsumeFn represents consume func definition.
type ConsumeFn func(ctx *context.Context, Input string, WorkerID uint64) (err error)

func (self *Task) TableIndex() [][]string {
	return [][]string{
		[]string{"Topic", "Status"},
	}
}

func (self *Task) TableEngine() string {
	return "INNODB"
}

// NewSimpleTask is new task with default settings.
func NewSimpleTask(topic, input string) *Task {
	return &Task{
		Topic:     topic,
		Status:    TaskStatPending,
		Timeout:   5000,
		ExecAfter: zeroTime,
		Retry:     3,
		Input:     input,
		WorkerId:  0,
	}
}

// Publish Task to Msg Queue.
func (self *Task) Publish() (err error) {
	o := orm.NewOrm()
	if _, err = o.Insert(self); err == nil {
		Stat(self.Topic, TaskStatPending, time.Duration(0))
	}
	return
}

func (self *Task) exec(ctx *context.Context, fn ConsumeFn) (err error) {
	errC := make(chan error)
	go func() {
		errC <- fn(ctx, self.Input, self.WorkerId)
	}()

	select {
	case err = <-errC:
	case <-(*ctx).Done():
		err = (*ctx).Err()
	}

	return
}

func (self *Task) consume(fn ConsumeFn) bool {
	startT := time.Now().Local()
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(self.Timeout+1)*time.Microsecond)
	defer cancel()

	err := self.exec(&ctx, fn)
	success := err == nil

	if !success {
		// failed
		self.Errlog += err.Error() + "\n"
		self.Status = TaskStatFailed
		self.Retry -= 1
		if self.Retry > 0 {
			self.Status = TaskStatRetry

			// when the job will retry, you can update the next exec time
			if execAfter, ok := ctx.Value(TaskCtxKeyExecAfter).(time.Time); ok {
				self.ExecAfter = execAfter
			}
		}
	} else {
		self.Status = TaskStatSuccess
	}

	// stat task exec
	Stat(self.Topic, self.Status, time.Since(startT))

	// workerid set to 0
	self.WorkerId = 0

	// set stdout log from ctx
	if log, ok := ctx.Value(TaskCtxKeyLog).(string); ok && log != "" {
		self.Log = log
	}

	// update db record
	o := orm.NewOrm()
	for i := 0; i < 3; i++ {
		if _, err := o.Update(self); err != nil {
			dora.Error().Msgf("set task state failed: err=%s, task=%s", err.Error(), self)
		} else {
			break
		}
	}

	return success
}

// Consume a topic task.
func Consume(topic string, fn ConsumeFn) (int, error) {
	o := orm.NewOrm()
	if IsMaster {
		// 仅在 master 上对已经僵死的 task 进行重置，减轻 DB 压力
		if _, err := o.Raw(`UPDATE task SET status=?, retry=retry-1, worker_id=0 WHERE topic=? AND status=?
		AND TIMESTAMPDIFF(SECOND, updated, now())*1000-5000>timeout`,
			TaskStatRetry, topic, TaskStatRunning,
		).Exec(); err != nil {
			dora.Error().Msgf("update dead running task to waiting failed: %s", err.Error())
		}
	}

	maxConsumeTask := cap(RunningTaskChannel[topic]) - len(RunningTaskChannel[topic])
	if maxConsumeTask == 0 {
		return 0, errors.New("reached max running task limit")
	}

	workerID, err := UniqID.NextID()
	if err != nil {
		return 0, err
	}

	res, err := o.Raw(`UPDATE task SET status=?, worker_id=?, worker_fqdn=concat(worker_fqdn, ?) WHERE
		topic=? AND worker_id=0 AND status IN (?,?) AND exec_after > NOW() LIMIT ?`,
		TaskStatRunning, workerID, FQDN()+"\n", topic, TaskStatPending, TaskStatRetry, maxConsumeTask,
	).Exec()
	if err != nil {
		// not update db
		return 0, err
	}

	num, _ := res.RowsAffected()
	if num == 0 {
		// no task for consume
		return 0, nil
	}

	tasks := []*Task{}
	_, err = o.Raw("SELECT * FROM task WHERE `topic`=? AND `status`=? AND `worker_id`=?",
		topic, TaskStatRunning, workerID,
	).QueryRows(&tasks)
	if len(tasks) == 0 || err != nil {
		return 0, err
	}

	// TODO: 如何保证程序退出时正在运行的 task 可用？当前超时后 5s 僵尸 task 会被重新激活，但不能保证原子性
	for _, t := range tasks {
		RunningTaskChannel[topic] <- true
		go func(t *Task) {
			t.consume(fn)
			<-RunningTaskChannel[topic]
		}(t)
	}

	return len(tasks), nil
}

// ConsumeCtxStdout write stdout log to context for record
func ConsumeCtxStdout(ctx *context.Context, log string) {
	*ctx = context.WithValue(*ctx, TaskCtxKeyLog, log)
}

// ConsumeCtxExecAfter update run time for next retry
func ConsumeCtxExecAfter(ctx *context.Context, t time.Time) {
	*ctx = context.WithValue(*ctx, TaskCtxKeyExecAfter, t)
}
<commit_msg>remove useless goroutine<commit_after>package tasker

import (
	"context"
	"errors"
	"time"

	"github.com/NoneBorder/dora"
	"github.com/astaxie/beego/orm"
)

const (
	TaskStatPending = "pending"
	TaskStatRunning = "running"
	TaskStatRetry   = "retry"
	TaskStatFailed  = "failed"
	TaskStatSuccess = "success"
)

const (
	TaskCtxKeyLog       = "stdout"          // normal log record
	TaskCtxKeyExecAfter = "task-exec-after" // plan task exec time
)

// Task is the core object for tasker package.
type Task struct {
	Id         uint64
	Topic      string
	Status     string
	Timeout    int // 超时ms
	ExecAfter  time.Time `orm:"type(datetime);default(1971-01-01 00:00:00)"`
	Retry      int
	Input      string    `orm:"type(text)"`
	WorkerId   uint64
	WorkerFQDN string    `orm:"column(worker_fqdn);type(text)"`
	Created    time.Time `orm:"auto_now_add"`
	Updated    time.Time `orm:"auto_now"`
	Log        string    `orm:"type(text)"`
	Errlog     string    `orm:"type(text)"`
}

// ConsumeFn represents consume func definition.
type ConsumeFn func(ctx *context.Context, Input string, WorkerID uint64) (err error)

func (self *Task) TableIndex() [][]string {
	return [][]string{
		[]string{"Topic", "Status"},
	}
}

func (self *Task) TableEngine() string {
	return "INNODB"
}

// NewSimpleTask is new task with default settings.
func NewSimpleTask(topic, input string) *Task {
	return &Task{
		Topic:     topic,
		Status:    TaskStatPending,
		Timeout:   5000,
		ExecAfter: zeroTime,
		Retry:     3,
		Input:     input,
		WorkerId:  0,
	}
}

// Publish Task to Msg Queue.
func (self *Task) Publish() (err error) {
	o := orm.NewOrm()
	if _, err = o.Insert(self); err == nil {
		Stat(self.Topic, TaskStatPending, time.Duration(0))
	}
	return
}

func (self *Task) exec(ctx *context.Context, fn ConsumeFn) (err error) {
	errC := make(chan error)
	select {
	case errC <- fn(ctx, self.Input, self.WorkerId):
		err = <-errC

	case <-(*ctx).Done():
		err = (*ctx).Err()
	}

	return
}

func (self *Task) consume(fn ConsumeFn) bool {
	startT := time.Now().Local()
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(self.Timeout+1)*time.Microsecond)
	defer cancel()

	err := self.exec(&ctx, fn)
	success := err == nil

	if !success {
		// failed
		self.Errlog += err.Error() + "\n"
		self.Status = TaskStatFailed
		self.Retry -= 1
		if self.Retry > 0 {
			self.Status = TaskStatRetry

			// when the job will retry, you can update the next exec time
			if execAfter, ok := ctx.Value(TaskCtxKeyExecAfter).(time.Time); ok {
				self.ExecAfter = execAfter
			}
		}
	} else {
		self.Status = TaskStatSuccess
	}

	// stat task exec
	Stat(self.Topic, self.Status, time.Since(startT))

	// workerid set to 0
	self.WorkerId = 0

	// set stdout log from ctx
	if log, ok := ctx.Value(TaskCtxKeyLog).(string); ok && log != "" {
		self.Log = log
	}

	// update db record
	o := orm.NewOrm()
	for i := 0; i < 3; i++ {
		if _, err := o.Update(self); err != nil {
			dora.Error().Msgf("set task state failed: err=%s, task=%s", err.Error(), self)
		} else {
			break
		}
	}

	return success
}

// Consume a topic task.
func Consume(topic string, fn ConsumeFn) (int, error) {
	o := orm.NewOrm()
	if IsMaster {
		// 仅在 master 上对已经僵死的 task 进行重置，减轻 DB 压力
		if _, err := o.Raw(`UPDATE task SET status=?, retry=retry-1, worker_id=0 WHERE topic=? AND status=?
		AND TIMESTAMPDIFF(SECOND, updated, now())*1000-5000>timeout`,
			TaskStatRetry, topic, TaskStatRunning,
		).Exec(); err != nil {
			dora.Error().Msgf("update dead running task to waiting failed: %s", err.Error())
		}
	}

	maxConsumeTask := cap(RunningTaskChannel[topic]) - len(RunningTaskChannel[topic])
	if maxConsumeTask == 0 {
		return 0, errors.New("reached max running task limit")
	}

	workerID, err := UniqID.NextID()
	if err != nil {
		return 0, err
	}

	res, err := o.Raw(`UPDATE task SET status=?, worker_id=?, worker_fqdn=concat(worker_fqdn, ?) WHERE
		topic=? AND worker_id=0 AND status IN (?,?) AND exec_after > NOW() LIMIT ?`,
		TaskStatRunning, workerID, FQDN()+"\n", topic, TaskStatPending, TaskStatRetry, maxConsumeTask,
	).Exec()
	if err != nil {
		// not update db
		return 0, err
	}

	num, _ := res.RowsAffected()
	if num == 0 {
		// no task for consume
		return 0, nil
	}

	tasks := []*Task{}
	_, err = o.Raw("SELECT * FROM task WHERE `topic`=? AND `status`=? AND `worker_id`=?",
		topic, TaskStatRunning, workerID,
	).QueryRows(&tasks)
	if len(tasks) == 0 || err != nil {
		return 0, err
	}

	// TODO: 如何保证程序退出时正在运行的 task 可用？当前超时后 5s 僵尸 task 会被重新激活，但不能保证原子性
	for _, t := range tasks {
		RunningTaskChannel[topic] <- true
		go func(t *Task) {
			t.consume(fn)
			<-RunningTaskChannel[topic]
		}(t)
	}

	return len(tasks), nil
}

// ConsumeCtxStdout write stdout log to context for record
func ConsumeCtxStdout(ctx *context.Context, log string) {
	*ctx = context.WithValue(*ctx, TaskCtxKeyLog, log)
}

// ConsumeCtxExecAfter update run time for next retry
func ConsumeCtxExecAfter(ctx *context.Context, t time.Time) {
	*ctx = context.WithValue(*ctx, TaskCtxKeyExecAfter, t)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"os/exec"
	"sync"
	"time"
)

const (
	taskActionStart  = iota
	taskActionStop   = iota
	taskActionStatus = iota
)

const (
	TaskStatusStopped    = iota
	TaskStatusRunning    = iota
	TaskStatusRestarting = iota
)

const MaxBacklogSize = 1000

const (
	BacklogLineStdout = iota
	BacklogLineStderr = iota
	BacklogLineStatus = iota
)

// A BacklogLine represents a line of output from a task. A line can be a normal line of output, a
// status message, or an error from stdout.
type BacklogLine struct {
	// Type is either BacklogLineStdout, BacklogLineStatus, or BacklogLineStderr.
	Type int

	// Data is the actual message that was output by the task.
	Data string

	// Time is the UNIX timestamp in milliseconds when the message was logged.
	Time int64
}

// A Task runs an executable in the background. Tasks each have their own background loop.
// While a task's background loop is running, its fields should not be modified.
type Task struct {
	Args     []string
	AutoRun  bool
	Dir      string
	Env      map[string]string
	GID      int
	Interval int
	UID      int
	Relaunch bool
	SetGID   bool
	SetUID   bool
	ID       int64

	backlogLock sync.RWMutex
	backlog     []BacklogLine

	actions chan<- taskAction
}

// NewTask creates an empty task. The task's background loop will not be running
// until StartLoop() is called.
func NewTask() *Task {
	return &Task{}
}

// Backlog returns a copy of the command's backlog.
func (t *Task) Backlog() []BacklogLine {
	t.backlogLock.RLock()
	defer t.backlogLock.RUnlock()
	backlog := make([]BacklogLine, len(t.backlog))
	for i, x := range t.backlog {
		backlog[i] = x
	}
	return backlog
}

// Start begins executing a command for the task. If the task is executing, this
// has no effect.
func (t *Task) Start() {
	resp := make(chan interface{})
	t.actions <- taskAction{taskActionStart, resp}
	<-resp
}

// StartLoop starts the task's background Goroutine. You must call this before
// using the Start(), Stop(), and Status() methods.
func (t *Task) StartLoop() {
	if t.actions != nil {
		panic("task's loop is already running")
	}
	ch := make(chan taskAction)
	t.actions = ch
	go t.loop(ch)
}

// Status returns the task's current state. Possible values are
// TaskStatusStopped, TaskStatusRunning, and TaskStatusRestarting.
func (t *Task) Status() int {
	resp := make(chan interface{})
	t.actions <- taskAction{taskActionStatus, resp}
	return (<-resp).(int)
}

// Stop terminates the task's command. If the task is not executing, this has no
// effect. This blocks to wait for the task to stop executing.
func (t *Task) Stop() {
	resp := make(chan interface{})
	t.actions <- taskAction{taskActionStop, resp}
	<-resp
}

// StopLoop stops a task's background Goroutine. You must call this after you
// are done using a task.
//
// If the task is executing, this will terminate the process and block until it
// has stopped.
func (t *Task) StopLoop() {
	if t.actions == nil {
		panic("task's loop is not running")
	}
	t.Stop()
	close(t.actions)
	t.actions = nil
}

func (t *Task) cmd() *exec.Cmd {
	task := exec.Command(t.Args[0], t.Args[1:]...)
	for key, value := range t.Env {
		task.Env = append(task.Env, key+"="+value)
	}
	task.Dir = t.Dir

	// TODO: here, set UID and GID

	return task
}

func (t *Task) generateStreams(cmd *exec.Cmd, doneChan <-chan struct{}) {
	stdoutStream := make(chan string)
	stderrStream := make(chan string)
	stdout := &lineForwarder{sendTo: stdoutStream}
	stderr := &lineForwarder{sendTo: stderrStream}
	cmd.Stdout = stdout
	cmd.Stderr = stderr
	go func() {
	Loop:
		for {
			select {
			case line := <-stdoutStream:
				t.pushBacklog(BacklogLineStdout, line)
			case line := <-stderrStream:
				t.pushBacklog(BacklogLineStderr, line)
			case <-doneChan:
				break Loop
			}
		}
		stdout.FlushIfNotEmpty()
		stderr.FlushIfNotEmpty()
	MissedItemLoop:
		for {
			select {
			case line := <-stdoutStream:
				t.pushBacklog(BacklogLineStdout, line)
			case line := <-stderrStream:
				t.pushBacklog(BacklogLineStderr, line)
			default:
				break MissedItemLoop
			}
		}
	}()
}

func (t *Task) loop(actions <-chan taskAction) {
	for {
		if val, ok := <-actions; !ok {
			return
		} else if val.action == taskActionStatus {
			val.resp <- TaskStatusStopped
		} else if val.action == taskActionStart {
			close(val.resp)
			if t.Relaunch {
				t.runRestart(actions)
			} else {
				t.runOnce(actions)
			}
		} else {
			close(val.resp)
		}
	}
}

func (t *Task) pushBacklog(typeNum int, data string) {
	line := BacklogLine{typeNum, data, time.Now().UnixNano() / 1000000}
	t.backlogLock.Lock()
	if len(t.backlog) < MaxBacklogSize {
		t.backlog = append(t.backlog, line)
	} else {
		for i := 1; i < len(t.backlog); i++ {
			t.backlog[i-1] = t.backlog[i]
		}
		t.backlog[MaxBacklogSize-1] = line
	}
	t.backlogLock.Unlock()
}

func (t *Task) runOnce(actions <-chan taskAction) {
	doneChan := make(chan struct{})
	cmd := t.cmd()
	t.generateStreams(cmd, doneChan)

	if err := cmd.Start(); err != nil {
		t.pushBacklog(BacklogLineStatus, "Error starting task: "+err.Error()+".")
		return
	}

	t.pushBacklog(BacklogLineStatus, "Started task.")

	go func() {
		cmd.Wait()
		close(doneChan)
	}()

	for {
		select {
		case <-doneChan:
			t.pushBacklog(BacklogLineStatus, "Task exited.")
			return
		case val, ok := <-actions:
			if !ok || val.action == taskActionStop {
				t.pushBacklog(BacklogLineStatus, "Task stopped.")
				cmd.Process.Kill()
				// Wait for the task to die before closing the response channel.
				<-doneChan
				if ok {
					close(val.resp)
				}
				return
			} else if val.action == taskActionStatus {
				val.resp <- TaskStatusRunning
			} else {
				close(val.resp)
			}
		}
	}
}

func (t *Task) runRestart(actions <-chan taskAction) {
	doneChan := make(chan struct{})
	cmd := t.cmd()
	t.generateStreams(cmd, doneChan)

	if err := cmd.Start(); err != nil {
		t.pushBacklog(BacklogLineStatus, "Error starting: "+err.Error())
		return
	}

	t.pushBacklog(BacklogLineStatus, "Started task.")

	go func() {
		cmd.Wait()
		close(doneChan)
	}()

	for {
		select {
		case <-doneChan:
			if !t.waitTimeout(actions) {
				return
			}
			cmd = t.cmd()
			doneChan = make(chan struct{})
			t.generateStreams(cmd, doneChan)
			if err := cmd.Start(); err != nil {
				t.pushBacklog(BacklogLineStatus, "Error restarting: "+err.Error()+".")
				close(doneChan)
			} else {
				t.pushBacklog(BacklogLineStatus, "Restarted task.")
				go func() {
					if err := cmd.Wait(); err != nil {
						t.pushBacklog(BacklogLineStatus, "Task exited: "+err.Error()+".")
					}
					close(doneChan)
				}()
			}
		case val, ok := <-actions:
			if !ok || val.action == taskActionStop {
				t.pushBacklog(BacklogLineStatus, "Task stopped.")
				cmd.Process.Kill()
				// Wait for the task to die before closing the response channel.
				<-doneChan
				if ok {
					close(val.resp)
				}
				return
			} else if val.action == taskActionStatus {
				val.resp <- TaskStatusRunning
			} else {
				close(val.resp)
			}
		}
	}
}

func (t *Task) waitTimeout(actions <-chan taskAction) bool {
	t.pushBacklog(BacklogLineStatus, "Waiting to restart.")
	timeoutChannel := time.After(time.Second * time.Duration(t.Interval))
	for {
		select {
		case <-timeoutChannel:
			return true
		case val, ok := <-actions:
			if !ok || val.action == taskActionStop {
				t.pushBacklog(BacklogLineStatus, "Stopped during wait.")
				if ok {
					close(val.resp)
				}
				return false
			} else if val.action == taskActionStatus {
				val.resp <- TaskStatusRestarting
			} else if val.action == taskActionStart {
				t.pushBacklog(BacklogLineStatus, "Wait bypassed.")
				close(val.resp)
				return true
			}
		}
	}
}

type taskAction struct {
	action int
	resp   chan<- interface{}
}

// A lineForwarder is an io.Writer which buffers lines and sends them over a channel.
type lineForwarder struct {
	sendTo chan<- string
	buffer bytes.Buffer
}

func (l *lineForwarder) FlushIfNotEmpty() {
	if l.buffer.Len() > 0 {
		l.FlushLine()
	}
}

func (l *lineForwarder) FlushLine() {
	l.sendTo <- l.buffer.String()
	l.buffer.Reset()
}

func (l *lineForwarder) Write(p []byte) (n int, err error) {
	for _, ch := range p {
		if ch == '\n' {
			l.FlushLine()
		} else {
			l.buffer.WriteByte(ch)
		}
	}
	return len(p), nil
}
<commit_msg>set UID and set GID<commit_after>package main

import (
	"bytes"
	"os/exec"
	"sync"
	"syscall"
	"time"
)

const (
	taskActionStart  = iota
	taskActionStop   = iota
	taskActionStatus = iota
)

const (
	TaskStatusStopped    = iota
	TaskStatusRunning    = iota
	TaskStatusRestarting = iota
)

const MaxBacklogSize = 1000

const (
	BacklogLineStdout = iota
	BacklogLineStderr = iota
	BacklogLineStatus = iota
)

// A BacklogLine represents a line of output from a task. A line can be a normal line of output, a
// status message, or an error from stdout.
type BacklogLine struct {
	// Type is either BacklogLineStdout, BacklogLineStatus, or BacklogLineStderr.
	Type int

	// Data is the actual message that was output by the task.
	Data string

	// Time is the UNIX timestamp in milliseconds when the message was logged.
	Time int64
}

// A Task runs an executable in the background. Tasks each have their own background loop.
// While a task's background loop is running, its fields should not be modified.
type Task struct {
	Args     []string
	AutoRun  bool
	Dir      string
	Env      map[string]string
	GID      int
	Interval int
	UID      int
	Relaunch bool
	SetGID   bool
	SetUID   bool
	ID       int64

	backlogLock sync.RWMutex
	backlog     []BacklogLine

	actions chan<- taskAction
}

// NewTask creates an empty task. The task's background loop will not be running
// until StartLoop() is called.
func NewTask() *Task {
	return &Task{}
}

// Backlog returns a copy of the command's backlog.
func (t *Task) Backlog() []BacklogLine {
	t.backlogLock.RLock()
	defer t.backlogLock.RUnlock()
	backlog := make([]BacklogLine, len(t.backlog))
	for i, x := range t.backlog {
		backlog[i] = x
	}
	return backlog
}

// Start begins executing a command for the task. If the task is executing, this
// has no effect.
func (t *Task) Start() {
	resp := make(chan interface{})
	t.actions <- taskAction{taskActionStart, resp}
	<-resp
}

// StartLoop starts the task's background Goroutine. You must call this before
// using the Start(), Stop(), and Status() methods.
func (t *Task) StartLoop() {
	if t.actions != nil {
		panic("task's loop is already running")
	}
	ch := make(chan taskAction)
	t.actions = ch
	go t.loop(ch)
}

// Status returns the task's current state. Possible values are
// TaskStatusStopped, TaskStatusRunning, and TaskStatusRestarting.
func (t *Task) Status() int {
	resp := make(chan interface{})
	t.actions <- taskAction{taskActionStatus, resp}
	return (<-resp).(int)
}

// Stop terminates the task's command. If the task is not executing, this has no
// effect. This blocks to wait for the task to stop executing.
func (t *Task) Stop() {
	resp := make(chan interface{})
	t.actions <- taskAction{taskActionStop, resp}
	<-resp
}

// StopLoop stops a task's background Goroutine. You must call this after you
// are done using a task.
//
// If the task is executing, this will terminate the process and block until it
// has stopped.
func (t *Task) StopLoop() {
	if t.actions == nil {
		panic("task's loop is not running")
	}
	t.Stop()
	close(t.actions)
	t.actions = nil
}

func (t *Task) cmd() *exec.Cmd {
	task := exec.Command(t.Args[0], t.Args[1:]...)
	for key, value := range t.Env {
		task.Env = append(task.Env, key+"="+value)
	}
	task.Dir = t.Dir

	if t.SetUID || t.SetGID {
		task.SysProcAttr = &syscall.SysProcAttr{}
		task.SysProcAttr.Credential = &syscall.Credential{}
		if t.SetUID {
			task.SysProcAttr.Credential.Uid = uint32(t.UID)
		} else {
			task.SysProcAttr.Credential.Uid = uint32(syscall.Getuid())
		}
		if t.SetGID {
			task.SysProcAttr.Credential.Gid = uint32(t.GID)
		} else {
			task.SysProcAttr.Credential.Gid = uint32(syscall.Getgid())
		}
	}

	return task
}

func (t *Task) generateStreams(cmd *exec.Cmd, doneChan <-chan struct{}) {
	stdoutStream := make(chan string)
	stderrStream := make(chan string)
	stdout := &lineForwarder{sendTo: stdoutStream}
	stderr := &lineForwarder{sendTo: stderrStream}
	cmd.Stdout = stdout
	cmd.Stderr = stderr
	go func() {
	Loop:
		for {
			select {
			case line := <-stdoutStream:
				t.pushBacklog(BacklogLineStdout, line)
			case line := <-stderrStream:
				t.pushBacklog(BacklogLineStderr, line)
			case <-doneChan:
				break Loop
			}
		}
		stdout.FlushIfNotEmpty()
		stderr.FlushIfNotEmpty()
	MissedItemLoop:
		for {
			select {
			case line := <-stdoutStream:
				t.pushBacklog(BacklogLineStdout, line)
			case line := <-stderrStream:
				t.pushBacklog(BacklogLineStderr, line)
			default:
				break MissedItemLoop
			}
		}
	}()
}

func (t *Task) loop(actions <-chan taskAction) {
	for {
		if val, ok := <-actions; !ok {
			return
		} else if val.action == taskActionStatus {
			val.resp <- TaskStatusStopped
		} else if val.action == taskActionStart {
			close(val.resp)
			if t.Relaunch {
				t.runRestart(actions)
			} else {
				t.runOnce(actions)
			}
		} else {
			close(val.resp)
		}
	}
}

func (t *Task) pushBacklog(typeNum int, data string) {
	line := BacklogLine{typeNum, data, time.Now().UnixNano() / 1000000}
	t.backlogLock.Lock()
	if len(t.backlog) < MaxBacklogSize {
		t.backlog = append(t.backlog, line)
	} else {
		for i := 1; i < len(t.backlog); i++ {
			t.backlog[i-1] = t.backlog[i]
		}
		t.backlog[MaxBacklogSize-1] = line
	}
	t.backlogLock.Unlock()
}

func (t *Task) runOnce(actions <-chan taskAction) {
	doneChan := make(chan struct{})
	cmd := t.cmd()
	t.generateStreams(cmd, doneChan)

	if err := cmd.Start(); err != nil {
		t.pushBacklog(BacklogLineStatus, "Error starting task: "+err.Error()+".")
		return
	}

	t.pushBacklog(BacklogLineStatus, "Started task.")

	go func() {
		cmd.Wait()
		close(doneChan)
	}()

	for {
		select {
		case <-doneChan:
			t.pushBacklog(BacklogLineStatus, "Task exited.")
			return
		case val, ok := <-actions:
			if !ok || val.action == taskActionStop {
				t.pushBacklog(BacklogLineStatus, "Task stopped.")
				cmd.Process.Kill()
				// Wait for the task to die before closing the response channel.
				<-doneChan
				if ok {
					close(val.resp)
				}
				return
			} else if val.action == taskActionStatus {
				val.resp <- TaskStatusRunning
			} else {
				close(val.resp)
			}
		}
	}
}

func (t *Task) runRestart(actions <-chan taskAction) {
	doneChan := make(chan struct{})
	cmd := t.cmd()
	t.generateStreams(cmd, doneChan)

	if err := cmd.Start(); err != nil {
		t.pushBacklog(BacklogLineStatus, "Error starting: "+err.Error())
		return
	}

	t.pushBacklog(BacklogLineStatus, "Started task.")

	go func() {
		cmd.Wait()
		close(doneChan)
	}()

	for {
		select {
		case <-doneChan:
			if !t.waitTimeout(actions) {
				return
			}
			cmd = t.cmd()
			doneChan = make(chan struct{})
			t.generateStreams(cmd, doneChan)
			if err := cmd.Start(); err != nil {
				t.pushBacklog(BacklogLineStatus, "Error restarting: "+err.Error()+".")
				close(doneChan)
			} else {
				t.pushBacklog(BacklogLineStatus, "Restarted task.")
				go func() {
					if err := cmd.Wait(); err != nil {
						t.pushBacklog(BacklogLineStatus, "Task exited: "+err.Error()+".")
					}
					close(doneChan)
				}()
			}
		case val, ok := <-actions:
			if !ok || val.action == taskActionStop {
				t.pushBacklog(BacklogLineStatus, "Task stopped.")
				cmd.Process.Kill()
				// Wait for the task to die before closing the response channel.
				<-doneChan
				if ok {
					close(val.resp)
				}
				return
			} else if val.action == taskActionStatus {
				val.resp <- TaskStatusRunning
			} else {
				close(val.resp)
			}
		}
	}
}

func (t *Task) waitTimeout(actions <-chan taskAction) bool {
	t.pushBacklog(BacklogLineStatus, "Waiting to restart.")
	timeoutChannel := time.After(time.Second * time.Duration(t.Interval))
	for {
		select {
		case <-timeoutChannel:
			return true
		case val, ok := <-actions:
			if !ok || val.action == taskActionStop {
				t.pushBacklog(BacklogLineStatus, "Stopped during wait.")
				if ok {
					close(val.resp)
				}
				return false
			} else if val.action == taskActionStatus {
				val.resp <- TaskStatusRestarting
			} else if val.action == taskActionStart {
				t.pushBacklog(BacklogLineStatus, "Wait bypassed.")
				close(val.resp)
				return true
			}
		}
	}
}

type taskAction struct {
	action int
	resp   chan<- interface{}
}

// A lineForwarder is an io.Writer which buffers lines and sends them over a channel.
type lineForwarder struct {
	sendTo chan<- string
	buffer bytes.Buffer
}

func (l *lineForwarder) FlushIfNotEmpty() {
	if l.buffer.Len() > 0 {
		l.FlushLine()
	}
}

func (l *lineForwarder) FlushLine() {
	l.sendTo <- l.buffer.String()
	l.buffer.Reset()
}

func (l *lineForwarder) Write(p []byte) (n int, err error) {
	for _, ch := range p {
		if ch == '\n' {
			l.FlushLine()
		} else {
			l.buffer.WriteByte(ch)
		}
	}
	return len(p), nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	/*
	"github.com/shurcooL/github_flavored_markdown"
	"github.com/shurcooL/github_flavored_markdown/gfmstyle"
	*/
	"github.com/microcosm-cc/bluemonday"
	"github.com/russross/blackfriday"
	"io"
	"io/ioutil"
	"strings"
    "os"
	"regexp"
)

func uploadHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	//GET displays the upload form.
	case "GET":
		http.NotFound(w, r)

	//POST takes the uploaded file(s) and saves it to disk.
	case "POST":
		//get the multipart reader for the request.
		reader, err := r.MultipartReader()

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

		//copy each part to destination.
		for {
			part, err := reader.NextPart()
			if err == io.EOF {
				break
			}

			//if part.FileName() is empty, skip this iteration.
			if part.FileName() == "" {
				continue
			}
			dst, err := os.Create("./uploads/" + part.FileName())
			defer dst.Close()

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

			if _, err := io.Copy(dst, part); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			log.Println("File Uploaded: "+part.FileName());
		}

		 io.WriteString(w, "success");
	default:
		w.WriteHeader(http.StatusMethodNotAllowed)
	}
}

func markdownHandler(w http.ResponseWriter, request *http.Request) {

	wikibase_b, err := ioutil.ReadFile("html/wikipage.html")

	if(err != nil){
		fmt.Print("Can't load html/wikipage.html\n")
		fmt.Print(err)
		fmt.Print("\n")
		return
	}

	wikibase := string(wikibase_b)

	//url := strings.Replace(request.URL.Path, "/md", "", 1)

	url := request.URL.Path

	markdown, err := ioutil.ReadFile("markdown" + url + ".md")

	if err != nil {
		io.WriteString(w, "an error has occurred.")
		fmt.Print(err)
		fmt.Print("\n")
	} else {

		unsafe := blackfriday.MarkdownCommon(markdown)
		p := bluemonday.UGCPolicy()
		p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
		html := p.SanitizeBytes(unsafe)

		complete := strings.Replace(wikibase, "%MARKDOWN%", string(html), -1)
		complete = strings.Replace(complete, "%NAME%", url, -1)

		w.Write([]byte(complete))

		/*
		io.WriteString(w, `<meta charset="utf-8"><link href="/assets/gfm.css" media="all" rel="stylesheet" type="text/css" /><link href="//cdnjs.cloudflare.com/ajax/libs/octicons/2.1.2/octicons.css" media="all" rel="stylesheet" type="text/css" />
`)
		w.Write(github_flavored_markdown.Markdown(markdown))
		io.WriteString(w, `</article></body></html>`)
		*/
	}
}

func main_start(args []string) {
	fs := http.FileServer(http.Dir("public_html"))
	http.Handle("/", authMW(fs))

	//http.Handle("/assets/", http.StripPrefix("/assets", http.FileServer(gfmstyle.Assets)))

	http.HandleFunc("/login", loginHandler)

	ufs := http.FileServer(http.Dir("uploads"))
	http.Handle("/uploads/", authMW(http.StripPrefix("/uploads", ufs)))

	http.Handle("/upload", authMW(http.HandlerFunc(uploadHandler)))
	http.Handle("/md/", authMW(http.StripPrefix("/md", http.HandlerFunc(markdownHandler))))

	log.Println("Listening...")
	log.Fatal(http.ListenAndServe(":8081", nil))
}

func main_stop(args []string) {
	fmt.Print("Not implemented")
}

func main_restart(args []string) {
	fmt.Print("Not implemented")
}

func main_config(args []string) {
	fmt.Print("Not implemented")
}

func main_help() {
	fmt.Print("Usage: \n$./nekrowiki start")
}

func main() {

	args := os.Args[1:]

	if len(args) == 0 {
		main_help()
		return
	} else {
		function := args[0]

		var additional_args []string

		if len(args) > 1 {
			additional_args = args[1:]
		} else {
			additional_args = make([]string, 0)
		}

		switch function {
		case "start":
			main_start(additional_args)
		case "stop":
			main_stop(additional_args)
		case "restart":
			main_restart(additional_args)
		case "config":
			main_config(additional_args)
		default:
			main_help()
		}

	}

}<commit_msg>renamed main_* to _* . Changed uploadHandler GET response to StatusMethodNotAllowed<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	/*
	"github.com/shurcooL/github_flavored_markdown"
	"github.com/shurcooL/github_flavored_markdown/gfmstyle"
	*/
	"github.com/microcosm-cc/bluemonday"
	"github.com/russross/blackfriday"
	"io"
	"io/ioutil"
	"strings"
    "os"
	"regexp"
)

func uploadHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	//GET displays the upload form.
	case "GET":
		w.WriteHeader(http.StatusMethodNotAllowed)

	//POST takes the uploaded file(s) and saves it to disk.
	case "POST":
		//get the multipart reader for the request.
		reader, err := r.MultipartReader()

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

		//copy each part to destination.
		for {
			part, err := reader.NextPart()
			if err == io.EOF {
				break
			}

			//if part.FileName() is empty, skip this iteration.
			if part.FileName() == "" {
				continue
			}
			dst, err := os.Create("./uploads/" + part.FileName())
			defer dst.Close()

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

			if _, err := io.Copy(dst, part); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			log.Println("File Uploaded: "+part.FileName());
		}

		 io.WriteString(w, "success");
	default:
		w.WriteHeader(http.StatusMethodNotAllowed)
	}
}

func markdownHandler(w http.ResponseWriter, request *http.Request) {

	wikibase_b, err := ioutil.ReadFile("html/wikipage.html")

	if(err != nil){
		fmt.Print("Can't load html/wikipage.html\n")
		fmt.Print(err)
		fmt.Print("\n")
		return
	}

	wikibase := string(wikibase_b)

	//url := strings.Replace(request.URL.Path, "/md", "", 1)

	url := request.URL.Path

	markdown, err := ioutil.ReadFile("markdown" + url + ".md")

	if err != nil {
		io.WriteString(w, "an error has occurred.")
		fmt.Print(err)
		fmt.Print("\n")
	} else {

		unsafe := blackfriday.MarkdownCommon(markdown)
		p := bluemonday.UGCPolicy()
		p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
		html := p.SanitizeBytes(unsafe)

		complete := strings.Replace(wikibase, "%MARKDOWN%", string(html), -1)
		complete = strings.Replace(complete, "%NAME%", url, -1)

		w.Write([]byte(complete))

		/*
		io.WriteString(w, `<meta charset="utf-8"><link href="/assets/gfm.css" media="all" rel="stylesheet" type="text/css" /><link href="//cdnjs.cloudflare.com/ajax/libs/octicons/2.1.2/octicons.css" media="all" rel="stylesheet" type="text/css" />
`)
		w.Write(github_flavored_markdown.Markdown(markdown))
		io.WriteString(w, `</article></body></html>`)
		*/
	}
}

func _start(args []string) {
	fs := http.FileServer(http.Dir("public_html"))
	http.Handle("/", authMW(fs))

	//http.Handle("/assets/", http.StripPrefix("/assets", http.FileServer(gfmstyle.Assets)))

	http.HandleFunc("/login", loginHandler)

	ufs := http.FileServer(http.Dir("uploads"))
	http.Handle("/uploads/", authMW(http.StripPrefix("/uploads", ufs)))

	http.Handle("/upload", authMW(http.HandlerFunc(uploadHandler)))
	http.Handle("/md/", authMW(http.StripPrefix("/md", http.HandlerFunc(markdownHandler))))

	log.Println("Listening...")
	log.Fatal(http.ListenAndServe(":8081", nil))
}

func _stop(args []string) {
	fmt.Print("Not implemented")
}

func _restart(args []string) {
	fmt.Print("Not implemented")
}

func _config(args []string) {
	fmt.Print("Not implemented")
}

func _help() {
	fmt.Print("Usage: \n$./nekrowiki start")
}

func main() {

	args := os.Args[1:]

	if len(args) == 0 {
		_help()
		return
	} else {
		function := args[0]

		var additional_args []string

		if len(args) > 1 {
			additional_args = args[1:]
		} else {
			additional_args = make([]string, 0)
		}

		switch function {
		case "start":
			_start(additional_args)
		case "stop":
			_stop(additional_args)
		case "restart":
			_restart(additional_args)
		case "config":
			_config(additional_args)
		default:
			_help()
		}

	}

}<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
)

type jsonobject struct {
	Cases       []Case
	Draft       int
	Description string
}

type Case struct {
	Seqno   int
	Wire    string
	Headers []map[string]string
}

var TESTCASE = []string{
	"hpack-test-case/haskell-http2-naive/",
	//"hpack-test-case/haskell-http2-naive-huffman",
}

func main() {
	for _, testCase := range TESTCASE {
		files, err := ioutil.ReadDir(testCase)
		if err != nil {
			panic(err)
		}
		for _, f := range files {
			data, err := ioutil.ReadFile(testCase + f.Name())
			if err != nil {
				panic(err)
			}
			var jsontype jsonobject
			json.Unmarshal(data, &jsontype)
			fmt.Printf("%v\n", jsontype)
		}
	}
}
<commit_msg>implement test<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"hpack"
	"io/ioutil"
	"reflect"
)

type jsonobject struct {
	Cases       []Case
	Draft       int
	Description string
}

type Case struct {
	Seqno   int
	Wire    string
	Headers []map[string]string
}

type Header struct {
	Name, Value string
}

var TESTCASE = []string{
	"hpack-test-case/haskell-http2-naive/",
	//"hpack-test-case/haskell-http2-naive-huffman",
}

func main() {
	for _, testCase := range TESTCASE {
		files, err := ioutil.ReadDir(testCase)
		if err != nil {
			panic(err)
		}
		for _, f := range files {
			data, err := ioutil.ReadFile(testCase + f.Name())
			if err != nil {
				panic(err)
			}
			var jsontype jsonobject
			json.Unmarshal(data, &jsontype)
			for _, seq := range jsontype.Cases {
				Headers := hpack.Decode(seq.Wire)
				if reflect.DeepEqual(seq.Headers, Headers) {
					fmt.Println("Pass in", f.Name())
				} else {
					fmt.Println("False in", f.Name(), "at seq", seq.Seqno)
					break
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"runtime"
	"strconv"
	//"strings"
	"github.com/eggfly/gopattern/behavior/templatemethod"
	"github.com/eggfly/gopattern/creation/abstractfactory"
	"github.com/eggfly/gopattern/creation/builder"
	"github.com/eggfly/gopattern/creation/factorymethod"
	"github.com/eggfly/gopattern/creation/prototype"
	"github.com/eggfly/gopattern/creation/singleton"
	"sync"
	"time"
)

const PORT = "8888"

func testGoroutine() {
	const MAXUINT = ^uint(0)
	const LOOP_COUNT = 1000 * 200
	var i uint
	for i = 0; i < LOOP_COUNT; i++ {
		go waitSeconds()
	}
	time.Sleep(time.Second * 10)
	runtime.GC()
	time.Sleep(time.Second * 10)
}

func waitSeconds() {
	time.Sleep(time.Second * 10)
}

func testSocket() {
	pServerMode := flag.Bool("server", false, "true for server mode, otherwise false")
	flag.Parse()
	var mode string
	if *pServerMode {
		mode = "server"
	} else {
		mode = "client"
	}
	fmt.Println("Run Mode:", mode)

	var err error
	if *pServerMode {
		err = startServer()
	} else {
		err = startClient()
	}

	fmt.Println("final:", err)
}
func startServer() error {
	ln, err := net.Listen("tcp", ":"+PORT)
	if err != nil {
		return err
	}
	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Println(err)
			continue
		}
		go handleConnection(conn)
	}
	return err
}

func checkPrintError(err error) {
	if err != nil {
		log.Println(err)
	}
}

func handleConnection(conn net.Conn) error {
	log.Println(conn)

	_, err := io.WriteString(conn, "\033[1;30;41mWelcome to the chat by golang!\033[0m\n")
	checkPrintError(err)
	for {
		reader := bufio.NewReader(conn)
		line, err := reader.ReadString('\n')
		io.WriteString(conn, line)
		if err != nil {
			checkPrintError(err)
			conn.Close()
			break
		}
	}
	return err
}

func startClient() error {
	conn, err := net.Dial("tcp", "localhost:"+PORT)
	if err != nil {
		return err
	}
	fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
	buf := bufio.NewReader(conn)
	line, err := buf.ReadString('\n')
	fmt.Println("now client close connection.")
	conn.Close()
	fmt.Println(line)
	return err
}

func printHelloWorld() {
	fmt.Println("Hello World!")
}

func testGOMAXPROCS() {
	val := runtime.GOMAXPROCS(8)
	fmt.Println("GOMAXPROCS:", val)
	val = runtime.GOMAXPROCS(0)
	fmt.Println("GOMAXPROCS:", val)
	val = runtime.GOMAXPROCS(runtime.NumCPU())
	fmt.Println("GOMAXPROCS:", val)
	a := func() {
		fmt.Println(runtime.CPUProfile())
		time.Sleep(time.Second * 10)
	}
	fmt.Println("NumCPU():", runtime.NumCPU())
	fmt.Println("NumCgoCall():", runtime.NumCgoCall())
	go a()
	go a()
	go a()
	go a()
	go a()
	go a()
	time.Sleep(time.Second * 11)
}

func testReaderWriter() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	fmt.Println("MAXPROCS:", runtime.GOMAXPROCS(0))
	c := make(chan int)
	// lock := sync.RWMutex{}
	var rwLock sync.RWMutex
	if &rwLock == nil {
		fmt.Println()
	}
	read := func(id int) {
		rwLock.RLock()
		fmt.Println("goroutine:", id+1, color("read", FORE_GREEN, BACK_BLACK))
		time.Sleep(time.Millisecond * 20)
		rwLock.RUnlock()
		c <- 0
	}
	write := func(id int) {
		rwLock.Lock()
		fmt.Println("goroutine:", id+1, color("write", FORE_RED, BACK_BLACK))
		time.Sleep(time.Millisecond * 20)
		rwLock.Unlock()
		c <- 0
	}
	num_goroutines := 0
	for i := 0; i < 5; i++ {
		go write(i)
		num_goroutines++
		go read(i)
		num_goroutines++
	}
	for i := 0; i < num_goroutines; i++ {
		<-c
	}

	fmt.Println("\033[32;49;1m [DONE] \033[39;49;0m")
	fmt.Print(color("green on red", FORE_GREEN, BACK_RED))
	fmt.Print(color("black on white", FORE_BLACK, BACK_WHITE))
	fmt.Println()
}

type Color int

const (
	FORE_BLACK Color = 30 + iota
	FORE_RED
	FORE_GREEN
	FORE_YELLOW
	FORE_BLUE
	FORE_MAGENTA
	FORE_CYAN
	FORE_WHITE
)
const (
	BACK_BLACK Color = 40 + iota
	BACK_RED
	BACK_GREEN
	BACK_YELLOW
	BACK_BLUE
	BACK_MAGENTA
	BACK_CYAN
	BACK_WHITE
)

func color(origin string, foreground Color, background Color) string {
	return fmt.Sprintf("\033[%d;%d;1m%s\033[0m", foreground, background, origin)
}
func testConcurrentPrint() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	c := make(chan bool)
	f := func(id int) {
		a := strconv.Itoa(id)
		for i := 0; i < 10; i++ {
			fmt.Print(a)
		}
		fmt.Println()
		c <- true
	}
	for i := 0; i < 10; i++ {
		go f(i)
	}
	for i := 0; i < 10; i++ {
		<-c
	}
}

type Task struct {
	channel chan interface{}
}

func (this Task) New() *Task {
	return &Task{}
}
func NewTask(f func(args []interface{}) interface{}, args []interface{}) *Task {
	t := Task{}
	t.channel = make(chan interface{}, 1)
	wrapperFunc := func() {
		t.channel <- f(args)
	}
	go wrapperFunc()
	return &t
}

func (this *Task) Get() interface{} {
	return <-this.channel
}

func printMem() {
	m := runtime.MemStats{}
	runtime.ReadMemStats(&m)
	fmt.Println(m.TotalAlloc, m.EnableGC, m.NumGC, m.Sys)
}

func testTask() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	printMem()
	f := func(args []interface{}) interface{} {
		fmt.Println("args:", args)
		return true
	}
	tasks := []*Task{}
	for i := 0; i < 17; i++ {
		task := NewTask(f, []interface{}{i})
		tasks = append(tasks, task)
	}
	for i, task := range tasks {
		fmt.Println("result", i, "is", task.Get())
		printMem()
	}
	fmt.Println("len of tasks:", len(tasks), "cap of tasks", cap(tasks))
	t := Task.New(Task{})
	fmt.Println(t)
}
func testSwitch() {
	ch := make(chan int)
	ch <- 1
	select {
	case v := <-ch:
		fmt.Println(v)
	}
}

func testOneWayChannel() {
	go func(<-chan interface{}) chan<- int {
		return make(chan<- int)
	}(make(<-chan interface{}))
}
func test2B() {
	a := 1
	a = a
}

type Request int

func Serve(queue chan *Request) {
	for req := range queue {
		//<-sem
		// Buggy
		req := req // Create new instance of req for the goroutine.
		go func() {
			//process(req)
			func(req *Request) {
			}(req)
			//sem <- 1
		}()
	}
}

// var s interface{} = nil
// var singletonLock = sync.RWMutex{}

func testRightSingleton() {
	// global value auto thread safe?
	c := make(chan bool)
	f := func() {
		if singleton.S == nil {
			log.Println("init value")
			singleton.S = "value"
		}
		c <- true
	}
	const COUNT = 10
	for i := 0; i < COUNT; i++ {
		go f()
	}
	for i := 0; i < COUNT; i++ {
		<-c
	}
}
func testWrongSingleton() {
	c := make(chan bool)
	var s interface{} = nil
	f := func() {
		if s == nil {
			log.Println("init value")
			s = "value"
		}
		c <- true
	}
	const COUNT = 10
	for i := 0; i < COUNT; i++ {
		go f()
	}
	for i := 0; i < COUNT; i++ {
		<-c
	}
	// result may like this:
	// 2014/01/16 23:07:00 init value
	// 2014/01/16 23:07:00 init value
	// 2014/01/16 23:07:00 init value
	// 2014/01/16 23:07:00 init value
	// 2014/01/16 23:07:00 init value
	// 2014/01/16 23:07:00 init value
	// 2014/01/16 23:07:00 init value
}
func testSingleton() {
	printTestHeader("testSingleton")
	runtime.GOMAXPROCS(runtime.NumCPU())
	testRightSingleton()
	testWrongSingleton()
}
func testPackageVarAccessSpeed() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	// a local var
	var s interface{} = nil

	// access package var
	accessPackageVar := func() {
		_ = singleton.S
	}
	// access local var
	accessLocalVar := func() {
		_ = s
	}

	ch := make(chan bool)
	// benchmark function
	benchmark := func(accessFunc func()) {
		t := time.Now()
		const COUNT = 100000
		for i := 0; i < COUNT; i++ {
			go func(accessFunc func()) {
				accessFunc()
				ch <- true
			}(accessFunc)
		}
		// wait for end
		for i := 0; i < COUNT; i++ {
			<-ch
		}

		fmt.Println("time:", time.Now().Sub(t))
	}

	benchmark(accessPackageVar)
	benchmark(accessLocalVar)
}

func testPackageVarThreadSafe() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	ch := make(chan bool)

	const COUNT = 100000
	for i := 0; i < COUNT; i++ {
		go func() {
			singleton.I++
			ch <- true
		}()
	}
	// wait for end
	for i := 0; i < COUNT; i++ {
		<-ch
	}

	fmt.Println("singleton.I:", singleton.I)
	// finally it's not safe
}

func testAbstractFactory() {
	printTestHeader("testAbstractFactory")
	var ibm abstractfactory.IComputerFactory = abstractfactory.IBMFactory{}
	desktop := ibm.CreateDesktopProduct()
	laptop := ibm.CreateLaptopProduct()
	desktop.PlayDesktop()
	laptop.PlayLaptop()
	desktop = abstractfactory.AppleFactory{}.CreateDesktopProduct()
	desktop.PlayDesktop()
}

func testBuilder() {
	printTestHeader("testBuilder")
	director := builder.BuildHouseDirector{}
	director.SetHouseBuilder(&builder.YoungHouseBuilder{})
	director.BuildHouse()
	fmt.Println(director.GetHouse())
}

func testFactoryMethod() {
	printTestHeader("testFactoryMethod")
	var factory factorymethod.IButtonFactory = factorymethod.WindowsButtonFactory{}
	factory.CreateButton().Click()
	factory = factorymethod.MacButtonFactory{}
	factory.CreateButton().Click()
}

func testPrototype() {
	printTestHeader("testPrototype")
	templateBullet := prototype.Bullet{}
	templateBullet.Init()
	templateBullet.X = 2

	bullet1 := prototype.Bullet{}
	bullet1.CopyFrom(templateBullet)
	fmt.Println(bullet1)
	bullet2 := bullet1.Clone()
	fmt.Println(bullet2)
}

func testCreatePrivate() {
	_ = templatemethod.CreatePrivate()
}

func testTemplateMethod() {
	monopoly := templatemethod.Monopoly{}
	monopoly.IGame = monopoly // this
	monopoly.PlayOneGame(3)
	game := templatemethod.Chess{}
	game.IGame = game // this
	game.PlayOneGame(4)
}
func printTestHeader(name string) {
	const SIGN = " **** "
	println(SIGN + name + SIGN)
}

func main() {
	//testSocket()
	//testGoroutine()
	//printHelloWorld()
	//testGOMAXPROCS()
	//testReaderWriter()
	//testConcurrentPrint()
	//testTask()
	//testSwitch()
	//testOneWayChannel()
	//test2B()
	//testPackageVarAccessSpeed()
	//testPackageVarThreadSafe()
	testCreatePrivate()

	testSingleton()
	testAbstractFactory()
	testBuilder()
	testFactoryMethod()
	testPrototype()
	testTemplateMethod()
}
<commit_msg>Fibonacci and Int<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"runtime"
	"strconv"
	//"strings"
	"github.com/eggfly/gopattern/behavior/templatemethod"
	"github.com/eggfly/gopattern/creation/abstractfactory"
	"github.com/eggfly/gopattern/creation/builder"
	"github.com/eggfly/gopattern/creation/factorymethod"
	"github.com/eggfly/gopattern/creation/prototype"
	"github.com/eggfly/gopattern/creation/singleton"
	"math"
	"math/big"
	"sync"
	"time"
)

const PORT = "8888"

func testGoroutine() {
	const MAXUINT = ^uint(0)
	const LOOP_COUNT = 1000 * 200
	var i uint
	for i = 0; i < LOOP_COUNT; i++ {
		go waitSeconds()
	}
	time.Sleep(time.Second * 10)
	runtime.GC()
	time.Sleep(time.Second * 10)
}

func waitSeconds() {
	time.Sleep(time.Second * 10)
}

func testSocket() {
	pServerMode := flag.Bool("server", false, "true for server mode, otherwise false")
	flag.Parse()
	var mode string
	if *pServerMode {
		mode = "server"
	} else {
		mode = "client"
	}
	fmt.Println("Run Mode:", mode)

	var err error
	if *pServerMode {
		err = startServer()
	} else {
		err = startClient()
	}

	fmt.Println("final:", err)
}
func startServer() error {
	ln, err := net.Listen("tcp", ":"+PORT)
	if err != nil {
		return err
	}
	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Println(err)
			continue
		}
		go handleConnection(conn)
	}
	return err
}

func checkPrintError(err error) {
	if err != nil {
		log.Println(err)
	}
}

func handleConnection(conn net.Conn) error {
	log.Println(conn)

	_, err := io.WriteString(conn, "\033[1;30;41mWelcome to the chat by golang!\033[0m\n")
	checkPrintError(err)
	for {
		reader := bufio.NewReader(conn)
		line, err := reader.ReadString('\n')
		io.WriteString(conn, line)
		if err != nil {
			checkPrintError(err)
			conn.Close()
			break
		}
	}
	return err
}

func startClient() error {
	conn, err := net.Dial("tcp", "localhost:"+PORT)
	if err != nil {
		return err
	}
	fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
	buf := bufio.NewReader(conn)
	line, err := buf.ReadString('\n')
	fmt.Println("now client close connection.")
	conn.Close()
	fmt.Println(line)
	return err
}

func printHelloWorld() {
	fmt.Println("Hello World!")
}

func testGOMAXPROCS() {
	val := runtime.GOMAXPROCS(8)
	fmt.Println("GOMAXPROCS:", val)
	val = runtime.GOMAXPROCS(0)
	fmt.Println("GOMAXPROCS:", val)
	val = runtime.GOMAXPROCS(runtime.NumCPU())
	fmt.Println("GOMAXPROCS:", val)
	a := func() {
		fmt.Println(runtime.CPUProfile())
		time.Sleep(time.Second * 10)
	}
	fmt.Println("NumCPU():", runtime.NumCPU())
	fmt.Println("NumCgoCall():", runtime.NumCgoCall())
	go a()
	go a()
	go a()
	go a()
	go a()
	go a()
	time.Sleep(time.Second * 11)
}

func testReaderWriter() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	fmt.Println("MAXPROCS:", runtime.GOMAXPROCS(0))
	c := make(chan int)
	// lock := sync.RWMutex{}
	var rwLock sync.RWMutex
	if &rwLock == nil {
		fmt.Println()
	}
	read := func(id int) {
		rwLock.RLock()
		fmt.Println("goroutine:", id+1, color("read", FORE_GREEN, BACK_BLACK))
		time.Sleep(time.Millisecond * 20)
		rwLock.RUnlock()
		c <- 0
	}
	write := func(id int) {
		rwLock.Lock()
		fmt.Println("goroutine:", id+1, color("write", FORE_RED, BACK_BLACK))
		time.Sleep(time.Millisecond * 20)
		rwLock.Unlock()
		c <- 0
	}
	num_goroutines := 0
	for i := 0; i < 5; i++ {
		go write(i)
		num_goroutines++
		go read(i)
		num_goroutines++
	}
	for i := 0; i < num_goroutines; i++ {
		<-c
	}

	fmt.Println("\033[32;49;1m [DONE] \033[39;49;0m")
	fmt.Print(color("green on red", FORE_GREEN, BACK_RED))
	fmt.Print(color("black on white", FORE_BLACK, BACK_WHITE))
	fmt.Println()
}

type Color int

const (
	FORE_BLACK Color = 30 + iota
	FORE_RED
	FORE_GREEN
	FORE_YELLOW
	FORE_BLUE
	FORE_MAGENTA
	FORE_CYAN
	FORE_WHITE
)
const (
	BACK_BLACK Color = 40 + iota
	BACK_RED
	BACK_GREEN
	BACK_YELLOW
	BACK_BLUE
	BACK_MAGENTA
	BACK_CYAN
	BACK_WHITE
)

func color(origin string, foreground Color, background Color) string {
	return fmt.Sprintf("\033[%d;%d;1m%s\033[0m", foreground, background, origin)
}
func testConcurrentPrint() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	c := make(chan bool)
	f := func(id int) {
		a := strconv.Itoa(id)
		for i := 0; i < 10; i++ {
			fmt.Print(a)
		}
		fmt.Println()
		c <- true
	}
	for i := 0; i < 10; i++ {
		go f(i)
	}
	for i := 0; i < 10; i++ {
		<-c
	}
}

type Task struct {
	channel chan interface{}
}

func (this Task) New() *Task {
	return &Task{}
}
func NewTask(f func(args []interface{}) interface{}, args []interface{}) *Task {
	t := Task{}
	t.channel = make(chan interface{}, 1)
	wrapperFunc := func() {
		t.channel <- f(args)
	}
	go wrapperFunc()
	return &t
}

func (this *Task) Get() interface{} {
	return <-this.channel
}

func printMem() {
	m := runtime.MemStats{}
	runtime.ReadMemStats(&m)
	fmt.Println(m.TotalAlloc, m.EnableGC, m.NumGC, m.Sys)
}

func testTask() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	printMem()
	f := func(args []interface{}) interface{} {
		fmt.Println("args:", args)
		return true
	}
	tasks := []*Task{}
	for i := 0; i < 17; i++ {
		task := NewTask(f, []interface{}{i})
		tasks = append(tasks, task)
	}
	for i, task := range tasks {
		fmt.Println("result", i, "is", task.Get())
		printMem()
	}
	fmt.Println("len of tasks:", len(tasks), "cap of tasks", cap(tasks))
	t := Task.New(Task{})
	fmt.Println(t)
}
func testSwitch() {
	ch := make(chan int)
	ch <- 1
	select {
	case v := <-ch:
		fmt.Println(v)
	}
}

func testOneWayChannel() {
	go func(<-chan interface{}) chan<- int {
		return make(chan<- int)
	}(make(<-chan interface{}))
}
func test2B() {
	a := 1
	a = a
}

type Request int

func Serve(queue chan *Request) {
	for req := range queue {
		//<-sem
		// Buggy
		req := req // Create new instance of req for the goroutine.
		go func() {
			//process(req)
			func(req *Request) {
			}(req)
			//sem <- 1
		}()
	}
}

func testFibonacci() {
	printTestHeader("testFibonacci")
	sqrt5 := math.Sqrt(5)
	const1 := sqrt5 / 5
	const2 := (sqrt5 + 1) / 2
	const3 := (1 - sqrt5) / 2
	round := func(f float64) int64 {
		return int64(math.Floor(f + 0.5))
	}
	fibonacciFormula := func(n int64) int64 {
		nn := float64(n)
		return round(const1 * (math.Pow(const2, nn) - math.Pow(const3, nn)))
	}
	t := time.Now()
	for i := 0; i < 10; i++ {
		fmt.Println(fibonacciFormula(int64(i)))
	}
	fmt.Println(time.Now().Sub(t))
}

func testDecimal() {
	printTestHeader("testDecimal")
	i := big.NewInt(math.MaxInt64)
	i.Mul(i, i)
	i.Mul(i, i)
	i.Mul(i, i)
	i.Mul(i, i)
	i.Mul(i, i)
	i.Mul(i, i)
	fmt.Println(i)
	ii := new(big.Int)
	fmt.Println(ii)
}

// var s interface{} = nil
// var singletonLock = sync.RWMutex{}

func testRightSingleton() {
	// global value auto thread safe?
	c := make(chan bool)
	f := func() {
		if singleton.S == nil {
			log.Println("init value")
			singleton.S = "value"
		}
		c <- true
	}
	const COUNT = 10
	for i := 0; i < COUNT; i++ {
		go f()
	}
	for i := 0; i < COUNT; i++ {
		<-c
	}
}
func testWrongSingleton() {
	c := make(chan bool)
	var s interface{} = nil
	f := func() {
		if s == nil {
			log.Println("init value")
			s = "value"
		}
		c <- true
	}
	const COUNT = 10
	for i := 0; i < COUNT; i++ {
		go f()
	}
	for i := 0; i < COUNT; i++ {
		<-c
	}
	// result may like this:
	// 2014/01/16 23:07:00 init value
	// 2014/01/16 23:07:00 init value
	// 2014/01/16 23:07:00 init value
	// 2014/01/16 23:07:00 init value
	// 2014/01/16 23:07:00 init value
	// 2014/01/16 23:07:00 init value
	// 2014/01/16 23:07:00 init value
}
func testSingleton() {
	printTestHeader("testSingleton")
	runtime.GOMAXPROCS(runtime.NumCPU())
	testRightSingleton()
	testWrongSingleton()
}
func testPackageVarAccessSpeed() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	// a local var
	var s interface{} = nil

	// access package var
	accessPackageVar := func() {
		_ = singleton.S
	}
	// access local var
	accessLocalVar := func() {
		_ = s
	}

	ch := make(chan bool)
	// benchmark function
	benchmark := func(accessFunc func()) {
		t := time.Now()
		const COUNT = 100000
		for i := 0; i < COUNT; i++ {
			go func(accessFunc func()) {
				accessFunc()
				ch <- true
			}(accessFunc)
		}
		// wait for end
		for i := 0; i < COUNT; i++ {
			<-ch
		}

		fmt.Println("time:", time.Now().Sub(t))
	}

	benchmark(accessPackageVar)
	benchmark(accessLocalVar)
}

func testPackageVarThreadSafe() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	ch := make(chan bool)

	const COUNT = 100000
	for i := 0; i < COUNT; i++ {
		go func() {
			singleton.I++
			ch <- true
		}()
	}
	// wait for end
	for i := 0; i < COUNT; i++ {
		<-ch
	}

	fmt.Println("singleton.I:", singleton.I)
	// finally it's not safe
}

func testAbstractFactory() {
	printTestHeader("testAbstractFactory")
	var ibm abstractfactory.IComputerFactory = abstractfactory.IBMFactory{}
	desktop := ibm.CreateDesktopProduct()
	laptop := ibm.CreateLaptopProduct()
	desktop.PlayDesktop()
	laptop.PlayLaptop()
	desktop = abstractfactory.AppleFactory{}.CreateDesktopProduct()
	desktop.PlayDesktop()
}

func testBuilder() {
	printTestHeader("testBuilder")
	director := builder.BuildHouseDirector{}
	director.SetHouseBuilder(&builder.YoungHouseBuilder{})
	director.BuildHouse()
	fmt.Println(director.GetHouse())
}

func testFactoryMethod() {
	printTestHeader("testFactoryMethod")
	var factory factorymethod.IButtonFactory = factorymethod.WindowsButtonFactory{}
	factory.CreateButton().Click()
	factory = factorymethod.MacButtonFactory{}
	factory.CreateButton().Click()
}

func testPrototype() {
	printTestHeader("testPrototype")
	templateBullet := prototype.Bullet{}
	templateBullet.Init()
	templateBullet.X = 2

	bullet1 := prototype.Bullet{}
	bullet1.CopyFrom(templateBullet)
	fmt.Println(bullet1)
	bullet2 := bullet1.Clone()
	fmt.Println(bullet2)
}

func testCreatePrivate() {
	_ = templatemethod.CreatePrivate()
}

func testTemplateMethod() {
	monopoly := templatemethod.Monopoly{}
	monopoly.IGame = monopoly // this
	monopoly.PlayOneGame(3)
	game := templatemethod.Chess{}
	game.IGame = game // this
	game.PlayOneGame(4)
}

func printTestHeader(name string) {
	const SIGN = " **** "
	println(SIGN + name + SIGN)
}

func main() {
	//testSocket()
	//testGoroutine()
	//printHelloWorld()
	//testGOMAXPROCS()
	//testReaderWriter()
	//testConcurrentPrint()
	//testTask()
	//testSwitch()
	//testOneWayChannel()
	//test2B()
	//testPackageVarAccessSpeed()
	//testPackageVarThreadSafe()
	testFibonacci()
	testDecimal()
	testCreatePrivate()

	testSingleton()
	testAbstractFactory()
	testBuilder()
	testFactoryMethod()
	testPrototype()
	testTemplateMethod()
}
<|endoftext|>


<commit_before>package main

import (
	"math"

	"gopkg.in/qml.v1"
)

type Tile struct {
	Object   qml.Object
	diagonal bool
}

func (t *Tile) Pos() (float64, float64) {
	i := t.Object.Int("index")
	x := float64(i % grid.ColCount)
	y := float64(i / grid.ColCount)
	return x, y
}

func (t *Tile) Neighbors() []Node {
	nodes := make([]Node, 0, 8)
	i := t.Object.Int("index")

	add := func(i int) {
		if grid.Tiles[i].Object.Int("type") != 1 {
			nodes = append(nodes, grid.Tiles[i])
		}
	}

	top := i - grid.ColCount
	if top >= 0 { //Up
		add(top)
	}
	mod := i % grid.ColCount
	if mod != grid.ColCount-1 { //Right
		add(i + 1)
	}
	if mod != 0 && i > 0 { //Left
		add(i - 1)
	}
	bottom := i + grid.ColCount
	if bottom < len(grid.Tiles) { //Bottom
		add(bottom)
	}
	if t.diagonal {
		if top >= 0 { //Top
			if mod != grid.ColCount-1 { //right
				add(i + 1 - grid.ColCount)
			}
			if mod != 0 && i > 0 { //left
				add(i - 1 - grid.ColCount)
			}
		}
		if bottom < len(grid.Tiles) { //Bottom
			if mod != grid.ColCount-1 { //right
				add(i + 1 + grid.ColCount)
			}
			if mod != 0 && i > 0 { //left
				add(i - 1 + grid.ColCount)
			}
		}
	}
	return nodes
}

func (t *Tile) Dist(n Node) float64 {
	return 1
}

func (t *Tile) EstimatedCost(g Node) float64 {
	tx, ty := t.Pos()
	gx, gy := g.(*Tile).Pos()
	if t.diagonal {
		return math.Max(math.Abs(tx-gx), math.Abs(ty-gy))
	}
	return math.Abs(tx-gx) + math.Abs(ty-gy)
}
<commit_msg>add neighbor cache to Tile<commit_after>package main

import (
	"math"

	"gopkg.in/qml.v1"
)

type Tile struct {
	Object     qml.Object
	diagonal   bool
	dneighbors map[Node]float64
}

func (t *Tile) Pos() (float64, float64) {
	i := t.Object.Int("index")
	x := float64(i % grid.ColCount)
	y := float64(i / grid.ColCount)
	return x, y
}

func (t *Tile) Neighbors() []Node {
	neighbors := make([]Node, 0, 8)
	t.dneighbors = make(map[Node]float64)
	cost := 1.0
	i := t.Object.Int("index")

	add := func(i int) {
		if grid.Tiles[i].Object.Int("type") != 1 {
			neighbors = append(neighbors, grid.Tiles[i])
			t.dneighbors[grid.Tiles[i]] = cost
		}
	}

	top := i - grid.ColCount
	if top >= 0 { //Up
		add(top)
	}
	mod := i % grid.ColCount
	if mod != grid.ColCount-1 { //Right
		add(i + 1)
	}
	if mod != 0 && i > 0 { //Left
		add(i - 1)
	}
	bottom := i + grid.ColCount
	if bottom < len(grid.Tiles) { //Bottom
		add(bottom)
	}
	if t.diagonal {
		//playing with diagonal cost.
		//cost = math.sqrt(2)
		if top >= 0 { //Top
			if mod != grid.ColCount-1 { //right
				add(i + 1 - grid.ColCount)
			}
			if mod != 0 && i > 0 { //left
				add(i - 1 - grid.ColCount)
			}
		}
		if bottom < len(grid.Tiles) { //Bottom
			if mod != grid.ColCount-1 { //right
				add(i + 1 + grid.ColCount)
			}
			if mod != 0 && i > 0 { //left
				add(i - 1 + grid.ColCount)
			}
		}
	}
	return neighbors
}

func (t *Tile) Dist(n Node) float64 {
	if v, ok := t.dneighbors[n]; ok {
		return v
	}
	panic("Incorrect non neighbor node")
}

func (t *Tile) EstimatedCost(g Node) float64 {
	tx, ty := t.Pos()
	gx, gy := g.(*Tile).Pos()
	if t.diagonal {
		return math.Max(math.Abs(tx-gx), math.Abs(ty-gy))
	}
	return math.Abs(tx-gx) + math.Abs(ty-gy)
}
<|endoftext|>


<commit_before>package dns

// TLSA support functions

// Sign creates a TLSA record from a SSL certifcate.
func (t *RR_TLSA) Sign() {

}

// Verify verifies a TLSA record against a SSL certificate.
func (t *RR_TLSA) Verify() {

}
<commit_msg>Create TLSA (DANE) Helper function<commit_after>package dns

import (
	"crypto/sha256"
	"crypto/sha512"
	"crypto/x509"
	"encoding/hex"
	"io"
	"net"
	"strconv"
)

// TLSA support functions

// Sign creates a TLSA record from a SSL certificate.
func (r *RR_TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) error {
	r.Hdr.Rrtype = TypeTLSA
	r.Usage = uint8(usage)
	r.Selector = uint8(selector)
	r.MatchingType = uint8(matchingType)

	switch r.MatchingType {
	case 0:
		switch s.Selector {
		case 0:
			r.Certificate = hex.EncodeToString(cert.Raw)
		case 1:
			r.Certificate = hex.EncodeToString(cert.RawSubjectPublicKeyInfo)
		}
	case 1:
		h := sha256.New()
		switch s.Selector {
		case 0:
			r.Certificate = hex.EncodeToString(cert.Raw)
		case 1:
			io.WriteString(h, string(cert.RawSubjectPublicKeyInfo))
			r.Certificate = hex.EncodeToString(h.Sum(nil))
		}
	case 2:
		h := sha512.New()
		switch s.Selector {
		case 0:
			r.Certificate = hex.EncodeToString(cert.Raw)
		case 1:
			io.WriteString(h, string(cert.RawSubjectPublicKeyInfo))
			r.Certificate = hex.EncodeToString(h.Sum(nil))
		}
	}
	return nil
}

// Verify verifies a TLSA record against a SSL certificate.
func (r *RR_TLSA) Verify() error {
	return nil

}

// Name set the ownername of the TLSA record according to the
// rules specified in RFC 6698, Section 3.
func (r *RR_TLSA) Name(name, service, network string) bool {
	if !IsFqdn(name) {
		return false
	}
	p, e := net.LookupPort(network, service)
	if e != nil {
		return false
	}
	r.Hdr.Name = "_" + strconv.Itoa(p) + "_" + network + "." + name
	return true
}
<|endoftext|>