<commit_before>// Sequential WARC file reader library supporting record-at-time compression
// this requires go1.4 due to its usage of compress/gzip.Reader#Multistream
//
// Currently only works on record-at-time compressed .gz files
//
// [Example implementation](https://github.com/sebcat/warc-urls)
package warc

import (
	"bufio"
	"bytes"
	"compress/gzip"
	"errors"
	"io"
	"strconv"
	"strings"
)

var (
	ErrMalformedRecord = errors.New("malformed record")
	ErrNonWARCRecord   = errors.New("non-WARC/1.0 record")
)

type byteCounter struct {
	r      *bufio.Reader
	offset int64
}

func (bc *byteCounter) Read(p []byte) (n int, err error) {
	n, err = bc.r.Read(p)
	bc.offset += int64(n)
	return
}

// This is a bit of a bottle neck, as it's called often
func (bc *byteCounter) ReadByte() (c byte, err error) {
	c, err = bc.r.ReadByte()
	if err == nil {
		bc.offset++
	}

	return
}

type Reader struct {
	bc   *byteCounter
	zr   *gzip.Reader
	last int64
}

type NamedField struct {
	Name  string
	Value string
}

type NamedFields []NamedField

type Record struct {
	Fields NamedFields
	Block  []byte
}

func (r *Record) Bytes() []byte {
	var b bytes.Buffer
	b.Write([]byte("WARC/1.0\r\n"))
	for _, field := range r.Fields {
		b.Write([]byte(field.Name + ": " + field.Value + "\r\n"))
	}

	b.Write([]byte("\r\n"))
	b.Write([]byte(r.Block))
	b.Write([]byte("\r\n\r\n"))
	return b.Bytes()
}

func (r *Record) FromBytes(rec []byte) error {
	parts := bytes.SplitN(rec, []byte("\r\n\r\n"), 2)
	if len(parts) != 2 {
		return ErrMalformedRecord
	}

	hdr, block := parts[0], parts[1]
	r.Block = block
	for ix, hdrline := range bytes.Split(hdr, []byte("\r\n")) {
		if ix == 0 {
			if string(hdrline) != "WARC/1.0" {
				return ErrNonWARCRecord
			}

			continue
		}

		l := bytes.SplitN(hdrline, []byte(":"), 2)
		if len(l) != 2 {
			return ErrMalformedRecord
		}

		r.Fields = append(r.Fields, NamedField{
			Name:  string(bytes.Trim(l[0], "\r\n\t ")),
			Value: string(bytes.Trim(l[1], "\r\n\t ")),
		})
	}

	lenStr := r.Fields.Value("content-length")
	if i, err := strconv.Atoi(lenStr); err == nil {
		r.Block = r.Block[:i]
	}

	return nil
}

func (f NamedFields) Value(name string) string {
	for _, el := range f {
		if strings.EqualFold(el.Name, name) {
			return el.Value
		}
	}

	return ""
}

func NewReader(reader io.Reader) *Reader {
	return &Reader{
		bc: &byteCounter{
			r: bufio.NewReader(reader),
		},
	}
}

func NewGZIPReader(reader io.Reader) (r *Reader, err error) {
	r = NewReader(reader)
	r.zr, err = gzip.NewReader(r.bc)
	if err != nil {
		return nil, err
	}

	r.zr.Multistream(false)
	return r, nil
}

func (r *Reader) gzipRecord() ([]byte, error) {
	var rec bytes.Buffer
	_, err := io.Copy(&rec, r.zr)
	if err != nil {
		return nil, err
	}

	if r.last == r.bc.offset {
		return nil, io.EOF
	}

	r.last = r.bc.offset
	r.zr.Reset(r.bc)
	r.zr.Multistream(false)
	return rec.Bytes(), nil
}

func (r *Reader) plainRecord() ([]byte, error) {
	// TODO: Implement
	return nil, errors.New("support for non record-at-time compressed WARC files not yet implemented")
}

func (r *Reader) record() ([]byte, error) {
	if r.zr != nil {
		return r.gzipRecord()
	} else {
		return r.plainRecord()
	}
}

// Scans a stream for a raw WARC record. Doesn't do any
// validation or parsing. Useful for concurrency pipelines
// where the parsing and message handling is fanned out to
// multiple goroutines. See Record#FromBytes
func (r *Reader) NextRaw() ([]byte, error) {
	return r.record()
}

// Scans and parses a WARC record from a stream.
// returns io.EOF when done
func (r *Reader) Next() (*Record, error) {
	var res Record

	rec, err := r.record()
	if err != nil {
		return nil, err
	}

	if err := res.FromBytes(rec); err != nil {
		return nil, err
	}

	return &res, nil
}

type Writer struct {
	w  io.Writer
	zw *gzip.Writer
}

func NewWriter(w io.Writer) *Writer {
	return &Writer{w: w, zw: gzip.NewWriter(w)}
}

// Write a record. No validation of mandatory WARC fields is performed.
// The written record will be an independent GZIP stream.
func (w *Writer) WriteRecord(r *Record) error {
	rec := r.Bytes()
	if _, err := w.zw.Write(rec); err != nil {
		return err
	}

	err := w.zw.Close()
	w.zw.Reset(w.w)
	return err
}
<commit_msg>Implemented internal reader that does not call bufio<commit_after>// Sequential WARC file reader library supporting record-at-time compression
// this requires go1.4 due to its usage of compress/gzip.Reader#Multistream
//
// Currently only works on record-at-time compressed .gz files
//
// [Example implementation](https://github.com/sebcat/warc-urls)
package warc

import (
	"bytes"
	"compress/gzip"
	"errors"
	"io"
	"strconv"
	"strings"
)

var (
	ErrMalformedRecord = errors.New("malformed record")
	ErrNonWARCRecord   = errors.New("non-WARC/1.0 record")
)

// a combo of a buffered reader and an offset counter.
// we're doing this because the alternative approach of
// wrapping bufio steals a bit of time since the reader
// will be called in a tight loop when used as an
// io.ByteReader inside
// compress/flate.(*decompressor).moreBits (go1.4.2)
type reader struct {
	r io.Reader

	rbuf     [4096]byte
	nbufleft int
	rpos     int

	// number of bytes having been read from this reader
	// (i.e., not from r)
	nread int64
}

func (r *reader) next() (n int, err error) {
	n, err = r.r.Read(r.rbuf[:])
	r.nbufleft = n
	r.rpos = 0
	return
}

func (r *reader) Read(p []byte) (n int, err error) {
	ncopy := len(p)
	if r.nbufleft == 0 {
		ncopy, err = r.next()
	}

	if ncopy > r.nbufleft {
		ncopy = r.nbufleft
	}

	n = copy(p, r.rbuf[r.rpos:r.rpos+r.nbufleft])
	r.rpos += n
	r.nread += int64(n)
	r.nbufleft -= n
	return
}

// This is a bit of a bottle neck, as it's called often
func (r *reader) ReadByte() (c byte, err error) {
	if r.nbufleft == 0 {
		if _, err = r.next(); r.nbufleft == 0 {
			return 0, err
		}
	}

	c = r.rbuf[r.rpos]
	r.nread++
	r.rpos++
	r.nbufleft--
	return
}

type Reader struct {
	r    *reader
	zr   *gzip.Reader
	last int64
}

type NamedField struct {
	Name  string
	Value string
}

type NamedFields []NamedField

type Record struct {
	Fields NamedFields
	Block  []byte
}

func (r *Record) Bytes() []byte {
	var b bytes.Buffer
	b.Write([]byte("WARC/1.0\r\n"))
	for _, field := range r.Fields {
		b.Write([]byte(field.Name + ": " + field.Value + "\r\n"))
	}

	b.Write([]byte("\r\n"))
	b.Write([]byte(r.Block))
	b.Write([]byte("\r\n\r\n"))
	return b.Bytes()
}

func (r *Record) FromBytes(rec []byte) error {
	parts := bytes.SplitN(rec, []byte("\r\n\r\n"), 2)
	if len(parts) != 2 {
		return ErrMalformedRecord
	}

	hdr, block := parts[0], parts[1]
	r.Block = block
	for ix, hdrline := range bytes.Split(hdr, []byte("\r\n")) {
		if ix == 0 {
			if string(hdrline) != "WARC/1.0" {
				return ErrNonWARCRecord
			}

			continue
		}

		l := bytes.SplitN(hdrline, []byte(":"), 2)
		if len(l) != 2 {
			return ErrMalformedRecord
		}

		r.Fields = append(r.Fields, NamedField{
			Name:  string(bytes.Trim(l[0], "\r\n\t ")),
			Value: string(bytes.Trim(l[1], "\r\n\t ")),
		})
	}

	lenStr := r.Fields.Value("content-length")
	if i, err := strconv.Atoi(lenStr); err == nil {
		r.Block = r.Block[:i]
	}

	return nil
}

func (f NamedFields) Value(name string) string {
	for _, el := range f {
		if strings.EqualFold(el.Name, name) {
			return el.Value
		}
	}

	return ""
}

func NewReader(r io.Reader) *Reader {
	return &Reader{
		r: &reader{
			r: r,
		},
	}
}

func NewGZIPReader(reader io.Reader) (r *Reader, err error) {
	r = NewReader(reader)
	r.zr, err = gzip.NewReader(r.r)
	if err != nil {
		return nil, err
	}

	r.zr.Multistream(false)
	return r, nil
}

func (r *Reader) gzipRecord() ([]byte, error) {
	var rec bytes.Buffer
	_, err := io.Copy(&rec, r.zr)
	if err != nil {
		return nil, err
	}

	if r.last == r.r.nread {
		return nil, io.EOF
	}

	r.last = r.r.nread
	r.zr.Reset(r.r)
	r.zr.Multistream(false)
	return rec.Bytes(), nil
}

func (r *Reader) plainRecord() ([]byte, error) {
	// TODO: Implement
	return nil, errors.New("support for non record-at-time compressed WARC files not yet implemented")
}

func (r *Reader) record() ([]byte, error) {
	if r.zr != nil {
		return r.gzipRecord()
	} else {
		return r.plainRecord()
	}
}

// Scans a stream for a raw WARC record. Doesn't do any
// validation or parsing. Useful for concurrency pipelines
// where the parsing and message handling is fanned out to
// multiple goroutines. See Record#FromBytes
func (r *Reader) NextRaw() ([]byte, error) {
	return r.record()
}

// Scans and parses a WARC record from a stream.
// returns io.EOF when done
func (r *Reader) Next() (*Record, error) {
	var res Record

	rec, err := r.record()
	if err != nil {
		return nil, err
	}

	if err := res.FromBytes(rec); err != nil {
		return nil, err
	}

	return &res, nil
}

type Writer struct {
	w  io.Writer
	zw *gzip.Writer
}

func NewWriter(w io.Writer) *Writer {
	return &Writer{w: w, zw: gzip.NewWriter(w)}
}

// Write a record. No validation of mandatory WARC fields is performed.
// The written record will be an independent GZIP stream.
func (w *Writer) WriteRecord(r *Record) error {
	rec := r.Bytes()
	if _, err := w.zw.Write(rec); err != nil {
		return err
	}

	err := w.zw.Close()
	w.zw.Reset(w.w)
	return err
}
<|endoftext|>


<commit_before>package wave

import (
	"errors"
	"flag"
	"log"
	"math/rand"
	"runtime"
	"strconv"
	"sync"
	"time"
)

func init() {
	cpus := runtime.NumCPU()
	procs := *flag.Int("wave.procs", cpus, "Number of processors to use")
	runtime.GOMAXPROCS(procs)
	log.Println("GOMAXPROCS = " + strconv.Itoa(procs))
}

const (
	defaultConcurrency  = 10
	defaultWaitInterval = 0
)

// Create a new Wave. Default implementation is the Wave.
func New(vals ...string) Wave {
	return Wave{
		strings:      vals,
		concurrency:  defaultConcurrency,
		waitInterval: defaultWaitInterval,
		name:         "{{ Wave " + strconv.Itoa(rand.Intn(1<<16)) + " }}",
	}
}

// Wave concurrently selects some strings, executes a set of Plugins
// for each string, then continues the process for the whole pool of strings.
// Afterward it will pause before repeating, unless it's disabled via SetRepeat.
type Wave struct {
	strings      []string      // List of strings (typically server hostnames).
	concurrency  int           // Number of strings to process simultaneously.
	waitInterval time.Duration // Seconds to wait between consecutive waves.
	repeat       bool          // Set to true to repeat the wave continuously.
	name         string        // A label used for log messages.
	plugins      []Plugin      // Plugins to execute against each string.

	initialized    bool            // True if the wave goroutine was created.
	running        bool            // True if the wave is executing right now.
	waveDone       []chan struct{} // Publishes a signal at the end of waves.
	waveDoneLock   sync.RWMutex    // Guards access to waveDone slice.
	workerControls []chan bool     // Send true to start worker, false to stop.
	waveMasterCtrl chan bool       // Signals the master routine to stop/go.
	isPaused       chan chan bool  // Request/Response channel for querying pause state

	logger *log.Logger // Place to send log messages. Set to nil for no logs.
}

/*
	Setters
*/

func (w *Wave) SetConcurrency(c int)            { w.concurrency = c }
func (w *Wave) SetWaitInterval(t time.Duration) { w.waitInterval = t }
func (w *Wave) SetRepeat(r bool)                { w.repeat = r }
func (w *Wave) SetName(n string)                { w.name = n }

func (w *Wave) SetPlugins(ps ...Plugin) {
	w.plugins = []Plugin{}
	w.AddPlugins(ps...)
}

func (w *Wave) AddPlugins(ps ...Plugin) {
	w.plugins = append(w.plugins, ps...)
}

/*
	Getters
*/

func (w *Wave) Concurrency() int            { return w.concurrency }
func (w *Wave) WaitInterval() time.Duration { return w.waitInterval }
func (w *Wave) Repeat() bool                { return w.repeat }
func (w *Wave) Name() string                { return w.name }

func (w *Wave) Plugins() []Plugin {
	bps := []Plugin{}
	for _, sp := range w.plugins {
		bps = append(bps, sp.(Plugin))
	}
	return bps
}

/*
	Implementation of Wave interface
*/

// Start or resume the wave. Provides a channel which signals whenever a
// full wave is completed. All channels created this way will receive a
// signal via fan-out messaging.
// Returns an error if already running or if the configuration is invalid.
func (w *Wave) Start() (<-chan struct{}, error) {
	if w.concurrency < 1 {
		return nil, errors.New("Concurrency cannot be below 1")
	}
	if w.waitInterval < 0 {
		return nil, errors.New("Wait interval cannot be below 0")
	}
	if w.name == "" {
		return nil, errors.New("Name cannot be an empty string")
	}
	if !w.initialized {
		w.waveDone = []chan struct{}{}
		w.running = true
		w.initialized = true
		w.waveMasterCtrl = make(chan bool)
		w.isPaused = make(chan chan bool)
	}
	done := make(chan struct{})
	w.waveDoneLock.Lock()
	w.waveDone = append(w.waveDone, done)
	w.waveDoneLock.Unlock()

	go func() {
		// Signal: Init
		log.Println(w.name, "Init")
		for _, plugin := range w.plugins {
			plugin.Init(w)
		}

		first := true
		// Repeat the wave if configured to do so.
		for ; first || w.repeat; time.Sleep(w.waitInterval) {
			first = false
			queue := make(chan string, w.concurrency*3)
			wg := &sync.WaitGroup{}

			w.workerControls = make([]chan bool, w.concurrency)

			// Create the workers.
			var preempt, run bool
			select {
			case cRun, ok := <-w.waveMasterCtrl:
				if !ok {
					log.Println(w.name, "Killed during startup")
					return
				}
				log.Println("Received preemptive master signal:", cRun)
				preempt = true
				run = cRun
				w.running = run
			default:
				// No signal received
			}
			for i := 0; i < w.concurrency; i++ {
				newCtrl := make(chan bool, 1)
				if preempt {
					newCtrl <- run
				}
				w.workerControls[i] = newCtrl
				wg.Add(1)
				go w.worker(queue, newCtrl, wg)
			}

			// Feed all of the strings into the queue.
			go func() {
				for _, target := range w.strings {
					log.Println(w.name, "Sending", target)
					queue <- target
				}
				close(queue)
			}()

			// Convert waitgroup signal into a chan signal
			wait := make(chan struct{})
			go func() {
				wg.Wait()
				wait <- struct{}{}
			}()

			// Wave master goroutine
			// Wait for signals or for work to finish
			go func() {
			master:
				for {
					select {
					case run, ok := <-w.waveMasterCtrl:
						if !ok {
							log.Println(w.name, "Killed during wave")
							for _, ctrl := range w.workerControls {
								close(ctrl)
							}
							// KILLED event?
							// Done signal?
						}
						log.Println("Received master signal:", run)
						for _, ctrl := range w.workerControls {
							ctrl <- run
						}
						w.running = run
					case isPaused := <-w.isPaused:
						isPaused <- !w.running
					case <-wait:
						w.waveDoneLock.RLock()
						for _, done := range w.waveDone {
							done <- struct{}{}
						}
						w.waveDoneLock.RUnlock()
						// Signal: End
						log.Println(w.name, "End")
						for _, plugin := range w.plugins {
							plugin.End(w)
						}
						break master
					}
				}
			}()

			w.waveMasterCtrl <- true

			// Signal: Start
			log.Println(w.name, "Start")
			for _, plugin := range w.plugins {
				plugin.Start(w)
			}
		}
	}()

	return (<-chan struct{})(done), nil
}

// Pause the wave, allowing active sessions to finish.
// Returns an error if already paused.
func (w *Wave) Pause() error {
	w.waveMasterCtrl <- false

	// Signal: Pause
	log.Println(w.name, "Pause")
	for _, plugin := range w.plugins {
		plugin.Pause(w)
	}

	return nil
}

func (w *Wave) Unpause() error {
	w.waveMasterCtrl <- true

	// Signal: Unpause
	log.Println(w.name, "Unpause")
	for _, plugin := range w.plugins {
		plugin.Unpause(w)
	}

	return nil
}

func (w *Wave) IsPaused() bool {
	resp := make(chan bool)
	w.isPaused <- resp
	return <-resp
}

func (w *Wave) worker(queue chan string, ctrl chan bool, wg *sync.WaitGroup) {
	defer wg.Done()
	log.Println(w.name, "Worker launched")
	select {
	case running, ok := <-ctrl:
		// There was a command waiting in the control channel
		if !ok {
			log.Println(w.name, "Worker quitting before work started; control channel closed")
			// TODO Report state to master
		}
		if running {
			log.Println(w.name, "Worker entering work mode; preemptive start command received")
			// TODO Report state to master
			goto work
		}
	default:
		// No command waiting, continue as usual...
		// TODO Report state to master
	}
idle:
	for {
		select {
		case running, ok := <-ctrl:
			if !ok {
				log.Println(w.name, "Worker quitting; control channel closed")
				return // Control channel closed; quit.
			}
			if running {
				log.Println(w.name, "Worker entering work mode")
				// TODO Report state to master
				goto work // Change state to working.
			}
		}
	}
work:
	for {
		select {
		case target, ok := <-queue:
			if !ok {
				log.Println(w.name, "Worker quitting; queue exhausted")
				return // Work queue closed; quit.
			}
			for _, plugin := range w.plugins {
				log.Println(w.name, "Worker processing:", target)
				plugin.Session(w, target)
			}
		case running, ok := <-ctrl:
			if !ok {
				log.Println(w.name, "Worker quitting; control channel closed")
				return // Control channel closed; quit.
			}
			if !running {
				log.Println(w.name, "Worker entering idle mode")
				// TODO Report state to master
				goto idle // Change state to idling.
			}
		}
	}
}
<commit_msg>Remove unused preemption code<commit_after>package wave

import (
	"errors"
	"flag"
	"log"
	"math/rand"
	"runtime"
	"strconv"
	"sync"
	"time"
)

func init() {
	cpus := runtime.NumCPU()
	procs := *flag.Int("wave.procs", cpus, "Number of processors to use")
	runtime.GOMAXPROCS(procs)
	log.Println("GOMAXPROCS = " + strconv.Itoa(procs))
}

const (
	defaultConcurrency  = 10
	defaultWaitInterval = 0
)

// Create a new Wave. Default implementation is the Wave.
func New(vals ...string) Wave {
	return Wave{
		strings:      vals,
		concurrency:  defaultConcurrency,
		waitInterval: defaultWaitInterval,
		name:         "{{ Wave " + strconv.Itoa(rand.Intn(1<<16)) + " }}",
	}
}

// Wave concurrently selects some strings, executes a set of Plugins
// for each string, then continues the process for the whole pool of strings.
// Afterward it will pause before repeating, unless it's disabled via SetRepeat.
type Wave struct {
	strings      []string      // List of strings (typically server hostnames).
	concurrency  int           // Number of strings to process simultaneously.
	waitInterval time.Duration // Seconds to wait between consecutive waves.
	repeat       bool          // Set to true to repeat the wave continuously.
	name         string        // A label used for log messages.
	plugins      []Plugin      // Plugins to execute against each string.

	initialized    bool            // True if the wave goroutine was created.
	running        bool            // True if the wave is executing right now.
	waveDone       []chan struct{} // Publishes a signal at the end of waves.
	waveDoneLock   sync.RWMutex    // Guards access to waveDone slice.
	workerControls []chan bool     // Send true to start worker, false to stop.
	waveMasterCtrl chan bool       // Signals the master routine to stop/go.
	isPaused       chan chan bool  // Request/Response channel for querying pause state

	logger *log.Logger // Place to send log messages. Set to nil for no logs.
}

/*
	Setters
*/

func (w *Wave) SetConcurrency(c int)            { w.concurrency = c }
func (w *Wave) SetWaitInterval(t time.Duration) { w.waitInterval = t }
func (w *Wave) SetRepeat(r bool)                { w.repeat = r }
func (w *Wave) SetName(n string)                { w.name = n }

func (w *Wave) SetPlugins(ps ...Plugin) {
	w.plugins = []Plugin{}
	w.AddPlugins(ps...)
}

func (w *Wave) AddPlugins(ps ...Plugin) {
	w.plugins = append(w.plugins, ps...)
}

/*
	Getters
*/

func (w *Wave) Concurrency() int            { return w.concurrency }
func (w *Wave) WaitInterval() time.Duration { return w.waitInterval }
func (w *Wave) Repeat() bool                { return w.repeat }
func (w *Wave) Name() string                { return w.name }

func (w *Wave) Plugins() []Plugin {
	bps := []Plugin{}
	for _, sp := range w.plugins {
		bps = append(bps, sp.(Plugin))
	}
	return bps
}

/*
	Implementation of Wave interface
*/

// Start or resume the wave. Provides a channel which signals whenever a
// full wave is completed. All channels created this way will receive a
// signal via fan-out messaging.
// Returns an error if already running or if the configuration is invalid.
func (w *Wave) Start() (<-chan struct{}, error) {
	if w.concurrency < 1 {
		return nil, errors.New("Concurrency cannot be below 1")
	}
	if w.waitInterval < 0 {
		return nil, errors.New("Wait interval cannot be below 0")
	}
	if w.name == "" {
		return nil, errors.New("Name cannot be an empty string")
	}
	if !w.initialized {
		w.waveDone = []chan struct{}{}
		w.running = true
		w.initialized = true
		w.waveMasterCtrl = make(chan bool)
		w.isPaused = make(chan chan bool)
	}
	done := make(chan struct{})
	w.waveDoneLock.Lock()
	w.waveDone = append(w.waveDone, done)
	w.waveDoneLock.Unlock()

	go func() {
		// Signal: Init
		log.Println(w.name, "Init")
		for _, plugin := range w.plugins {
			plugin.Init(w)
		}

		first := true
		// Repeat the wave if configured to do so.
		for ; first || w.repeat; time.Sleep(w.waitInterval) {
			first = false
			queue := make(chan string, w.concurrency*3)
			wg := &sync.WaitGroup{}

			w.workerControls = make([]chan bool, w.concurrency)

			for i := 0; i < w.concurrency; i++ {
				newCtrl := make(chan bool, 1)
				w.workerControls[i] = newCtrl
				wg.Add(1)
				go w.worker(queue, newCtrl, wg)
			}

			// Feed all of the strings into the queue.
			go func() {
				for _, target := range w.strings {
					log.Println(w.name, "Sending", target)
					queue <- target
				}
				close(queue)
			}()

			// Convert waitgroup signal into a chan signal
			wait := make(chan struct{})
			go func() {
				wg.Wait()
				wait <- struct{}{}
			}()

			// Wave master goroutine
			// Wait for signals or for work to finish
			go func() {
			master:
				for {
					select {
					case run, ok := <-w.waveMasterCtrl:
						if !ok {
							log.Println(w.name, "Killed during wave")
							for _, ctrl := range w.workerControls {
								close(ctrl)
							}
							// KILLED event?
							// Done signal?
						}
						log.Println("Received master signal:", run)
						for _, ctrl := range w.workerControls {
							ctrl <- run
						}
						w.running = run
					case isPaused := <-w.isPaused:
						isPaused <- !w.running
					case <-wait:
						w.waveDoneLock.RLock()
						for _, done := range w.waveDone {
							done <- struct{}{}
						}
						w.waveDoneLock.RUnlock()
						// Signal: End
						log.Println(w.name, "End")
						for _, plugin := range w.plugins {
							plugin.End(w)
						}
						break master
					}
				}
			}()

			w.waveMasterCtrl <- true

			// Signal: Start
			log.Println(w.name, "Start")
			for _, plugin := range w.plugins {
				plugin.Start(w)
			}
		}
	}()

	return (<-chan struct{})(done), nil
}

// Pause the wave, allowing active sessions to finish.
// Returns an error if already paused.
func (w *Wave) Pause() error {
	w.waveMasterCtrl <- false

	// Signal: Pause
	log.Println(w.name, "Pause")
	for _, plugin := range w.plugins {
		plugin.Pause(w)
	}

	return nil
}

func (w *Wave) Unpause() error {
	w.waveMasterCtrl <- true

	// Signal: Unpause
	log.Println(w.name, "Unpause")
	for _, plugin := range w.plugins {
		plugin.Unpause(w)
	}

	return nil
}

func (w *Wave) IsPaused() bool {
	resp := make(chan bool)
	w.isPaused <- resp
	return <-resp
}

func (w *Wave) worker(queue chan string, ctrl chan bool, wg *sync.WaitGroup) {
	defer wg.Done()
	log.Println(w.name, "Worker launched")
	select {
	case running, ok := <-ctrl:
		// There was a command waiting in the control channel
		if !ok {
			log.Println(w.name, "Worker quitting before work started; control channel closed")
			// TODO Report state to master
		}
		if running {
			log.Println(w.name, "Worker entering work mode; preemptive start command received")
			// TODO Report state to master
			goto work
		}
	default:
		// No command waiting, continue as usual...
		// TODO Report state to master
	}
idle:
	for {
		select {
		case running, ok := <-ctrl:
			if !ok {
				log.Println(w.name, "Worker quitting; control channel closed")
				return // Control channel closed; quit.
			}
			if running {
				log.Println(w.name, "Worker entering work mode")
				// TODO Report state to master
				goto work // Change state to working.
			}
		}
	}
work:
	for {
		select {
		case target, ok := <-queue:
			if !ok {
				log.Println(w.name, "Worker quitting; queue exhausted")
				return // Work queue closed; quit.
			}
			for _, plugin := range w.plugins {
				log.Println(w.name, "Worker processing:", target)
				plugin.Session(w, target)
			}
		case running, ok := <-ctrl:
			if !ok {
				log.Println(w.name, "Worker quitting; control channel closed")
				return // Control channel closed; quit.
			}
			if !running {
				log.Println(w.name, "Worker entering idle mode")
				// TODO Report state to master
				goto idle // Change state to idling.
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"
	"strings"
)

var clients map[net.Conn]clientData

type clientData struct{ loggedIn bool }

func main() {
	listener, err := net.Listen("tcp", ":23")
	if err != nil {
		panic(err)
	}

	fmt.Println("War Operation Plan Response (WOPR) has initialized.")

	for {
		client, err := listener.Accept()
		if err != nil {
			continue
		}

		go connectClient(client)
	}
}

func connectClient(client net.Conn) {
	client.Write([]byte("LOGON: "))

	for {
		buf := make([]byte, 4096)
		numbytes, err := client.Read(buf)
		if numbytes == 0 || err != nil {
			return
		}
		handleInput(string(buf), client)
	}
}

func handleInput(input string, client net.Conn) {

	fmt.Println("handleInput:", input)

	if strings.Contains(input, "Joshua") {
		clients[client].loggedIn = true
		sendString(client, "GREETING PROFESSOR FALKEN.\n")
	}
}

func sendString(client net.Conn, msg string) {
	client.Write([]byte(msg))
}
<commit_msg>Respect PORT environment variable.  Basic command responses from the movie.<commit_after>package main

import (
	"fmt"
	"net"
	"os"
	"strings"
)

var clients = make(map[net.Conn]bool)

func main() {

	port := "2000"
	if os.Getenv("PORT") != "" {
		port = os.Getenv("PORT")
	}

	listener, err := net.Listen("tcp", ":"+port)
	if err != nil {
		panic(err)
	}

	fmt.Println("War Operation Plan Response (WOPR) has initialized.\nRunning on port: " + port)

	for {
		client, err := listener.Accept()
		if err != nil {
			continue
		}

		go connectClient(client)
	}
}

func connectClient(client net.Conn) {

	// Clear Screen
	sendString(client, "\u001B[2J")
	sendString(client, "LOGON: ")

	for {
		buf := make([]byte, 4096)
		numBytes, err := client.Read(buf)
		if numBytes == 0 || err != nil {
			return
		}

		// If we get telnet commands, ignore them
		if buf[0] == 255 {
			continue
		}

		handleInput(string(buf[0:numBytes]), client)
	}
}

func handleInput(input string, client net.Conn) {

	input = stripInput(input)
	fmt.Println("handleInput:", input)

	if !clientLoggedIn(client) {
		disconnect := true

		switch input {
		case "JOSHUA":
			loginClient(client)
			sendString(client, "GREETING PROFESSOR FALKEN.\r\n")
			disconnect = false
		case "HELP", "HELP LOGON":
			sendString(client, "HELP NOT AVAILABLE.\r\n")
		default:
			sendString(client, "IDENTIFICATION NOT RECOGNIZED BY SYSTEM.\r\n")
		}

		if disconnect {
			sendString(client, "--CONNECTION TERMINATED--\r\n")
			client.Close()
		}
	} else {
		switch input {
		case "EXIT":
			client.Close()
		case "HELLO":
			sendString(client, "HOW ARE YOU FEELING TODAY?\r\n")
		case "I AM FINE. HOW ARE YOU?":
			sendString(client, "EXCELLENT. IT'S BEEN A LONG TIME.\r\n")
			sendString(client, "CAN YOU EXPLAIN THE REMOVAL OF YOUR USER ACCOUNT ON 6/23/73?\r\n")
		case "HELP GAMES":
			sendString(client, "'GAMES' REFERS TO MODELS, SIMULATIONS AND GAMES WHICH HAVE")
			sendString(client, " TACTICAL AND STRATEGIC APPLICATIONS\r\n")
		case "LIST GAMES":
			listGames(client)
		case "HELP SYSTEM":
			sendString(client, "HELP GAMES\r\nLIST GAMES\r\nEXIT\r\n")
		case "PEOPLE SOMETIMES MAKE MISTAKES":
			sendString(client, "YES THEY DO. SHALL WE PLAY A GAME?\r\n")
		case "LOVE TO. HOW ABOUT GLOBAL THERMONUCLEAR WAR":
			sendString(client, "WOULDN'T YOU PREFER A GOOD GAME OF CHESS?\r\n")
		case "LATER. LET'S PLAY GLOBAL THERMONUCLEAR WAR":
			sendString(client, "FINE\r\n")
		case "":
			// Do nothing
		default:
			sendString(client, "UNKNOWN COMMAND: '"+input+"'\r\n")
		}
	}
}

func listGames(client net.Conn) {
	sendString(client, "FALKEN'S MAZE\r\n")
	sendString(client, "BLACK JACK\r\n")
	sendString(client, "GIN RUMMY\r\n")
	sendString(client, "HEARTS\r\n")
	sendString(client, "BRIDGE\r\n")
	sendString(client, "CHECKERS\r\n")
	sendString(client, "CHESS\r\n")
	sendString(client, "POKER\r\n")
	sendString(client, "FIGHTER COMBAT\r\n")
	sendString(client, "GUERILLA ENGAGEMENT\r\n")
	sendString(client, "DESERT WARFARE\r\n")
	sendString(client, "AIR-TO-GROUND ACTIONS\r\n")
	sendString(client, "THEATERWIDE TACTICAL WARFARE\r\n")
	sendString(client, "THEATERWIDE BIOTOXIC AND CHEMICAL WARFARE\r\n\r\n")
	sendString(client, "GLOBAL THERMONUCLEAR WAR\r\n")
}

func loginClient(client net.Conn) {
	clients[client] = true
}

func clientLoggedIn(client net.Conn) bool {
	return clients[client]
}

func sendString(client net.Conn, msg string) {
	client.Write([]byte(msg))
}

func stripInput(input string) string {
	input = strings.Replace(input, "\r", "", -1)
	input = strings.Replace(input, "\n", "", -1)
	input = strings.ToUpper(input)

	return input
}
<|endoftext|>


<commit_before>package game

import (
	"errors"
	"fmt"

	"github.com/ghthor/engine/rpg2d/coord"
	"github.com/ghthor/engine/rpg2d/entity"
	"github.com/ghthor/engine/rpg2d/quad"
	"github.com/ghthor/engine/sim/stime"
)

type narrowPhase struct {
	actorIndex actorIndex

	// Reset at the beginning of every ResolveCollisions call
	solved []quad.Collision
	// Generated at the beginning of every ResolveCollisions call
	collisionIndex quad.CollisionIndex
}

func newNarrowPhase(actorIndex actorIndex) narrowPhase {
	return narrowPhase{actorIndex, make([]quad.Collision, 0, 10), nil}
}

// Returns if the collision exists in the
// slice of collisions that have been
// solved during this narrow phase tick.
func (phase narrowPhase) hasSolved(c quad.Collision) bool {
	for _, solved := range phase.solved {
		if c.IsSameAs(solved) {
			return true
		}
	}

	return false
}

// Implementation of the quad.NarrowPhaseHandler interface.
func (phase narrowPhase) ResolveCollisions(cg *quad.CollisionGroup, now stime.Time) ([]entity.Entity, []entity.Entity) {
	// Reset the resolved slice
	phase.solved = phase.solved[:0]

	// Generate a collision index for the collision group
	phase.collisionIndex = cg.CollisionIndex()

	// A map to store entities that still remain in the world
	remaining := make(map[entity.Id]entity.Entity, len(cg.Entities))
	remainingSlice := func() []entity.Entity {
		// Build a slice from the `remaining` map
		s := make([]entity.Entity, 0, len(remaining))
		for _, e := range remaining {
			s = append(s, e)
		}
		return s
	}

	for _, c := range cg.Collisions {
		if phase.hasSolved(c) {
			continue
		}

		var entities []entity.Entity

		// Resolve type of entity in collision.A
		switch e := c.A.(type) {
		case actorEntity:
			// Resolve the type of entity in collision.B
			entities = phase.resolveActorEntity(phase.actorIndex[e.ActorId()], c.B, c)
		default:
			switch e := c.B.(type) {
			case actorEntity:
				// Resolve the type of entity in collision.B
				entities = phase.resolveActorEntity(phase.actorIndex[e.ActorId()], c.A, c)
			}
		}

		// As collisions are solved they return entities
		// that have been created or modified and we store
		// them in a map by their Id. Multiple collisions
		// may modify and entity, therefor we only will
		// one version of the entity back to engine when
		// we return.
		for _, e := range entities {
			remaining[e.Id()] = e
		}
	}

	return remainingSlice(), nil
}

func (phase *narrowPhase) resolveActorEntity(a *actor, with entity.Entity, collision quad.Collision) []entity.Entity {
	switch e := with.(type) {
	case actorEntity:
		b := phase.actorIndex[e.ActorId()]

		return phase.solveActorActor(&solverActorActor{}, a, b, collision)
	}

	return nil
}

func newActorActorCollision(a, b *actor) (*actor, *actor, coord.Collision) {
	var collision coord.Collision

	switch {
	case a.pathAction == nil && b.pathAction != nil:
		a, b = b, a
		fallthrough
	case a.pathAction != nil && b.pathAction == nil:
		collision = coord.NewCellCollision(*a.pathAction, b.Cell())

		// A or B may have had a previous collision resolved that
		// caused this collision to not be possible anymore.
		// It is more relevant to return nil here then a
		// coord.Collision with type CT_NONE
		if collision.Type() == coord.CT_NONE {
			return a, b, nil
		}

	case a.pathAction != nil && b.pathAction != nil:
		pathCollision := coord.NewPathCollision(*a.pathAction, *b.pathAction)

		// coord.NewPathCollision can flip the,
		// A and B paths to simplify the number
		// of collision types. This normalizes
		// actor A with pathCollision.A
		if *a.pathAction != pathCollision.A {
			a, b = b, a
		}

		collision = pathCollision
	case a.pathAction == nil && b.pathAction == nil:
		// This case handles actors being on the same square,
		// but not moving at all.
		// There isn't a coord.CollisionType for this case.
		// Maybe there should be?
		return a, b, nil

	default:
		panic(fmt.Sprintf("impossible collision between {%v} {%v}", a, b))
	}
	return a, b, collision
}

type node struct {
	actor  *actor
	entity entity.Entity
}

// Move forward in the directed graph. This movement is based on
// which entity is occupying the destination of the other's path action.
func followGraph(a, b *actor, collision quad.Collision) node {
	// normalize a, b to collision.[A, B]
	if a.actorEntity.Id() != collision.A.Id() {
		a, b = b, a
	}

	var actor *actor
	var entity entity.Entity

	switch {
	case a.pathAction.Orig == b.pathAction.Dest:
		entity = collision.A
		actor = a

	case b.pathAction.Orig == a.pathAction.Dest:
		entity = collision.B
		actor = b

	default:
		panic(fmt.Sprintf("unexpected graph state %v between %v & %v", collision, a, b))
	}

	return node{actor, entity}
}

// Used to figure out which actor is "A" if
// the collision was CT_A_INTO_B instead of CT_NONE
func currentNode(a, b *actor, collision quad.Collision) *actor {
	switch {
	case a.pathAction.Orig == b.pathAction.Dest:
		return b

	case b.pathAction.Orig == a.pathAction.Dest:
		return a

	default:
		panic(fmt.Sprintf("unexpected graph state %v between %v & %v", collision, a, b))
	}
}

// Compare entity Id's with the entities in
// a collision and return the one that isn't
// the actor.
func otherEntityIn(a *actor, collision quad.Collision) entity.Entity {
	var e entity.Entity

	// figure out is prioritized actor is A or B in the collision
	switch {
	case a.actorEntity.Id() != collision.A.Id():
		e = collision.A

	case a.actorEntity.Id() != collision.B.Id():
		e = collision.B

	default:
		panic(fmt.Sprintf("unexpected graph state %v actor %v", collision, a))
	}

	return e
}

// Store what actor's have been visited during
// a recursive solve. Used to avoid infinite
// recursion through a cycle in the graph.
type solverActorActor struct {
	visited []*actor
}

func (s solverActorActor) hasVisited(actor *actor) bool {
	for _, a := range s.visited {
		if actor == a {
			return true
		}
	}

	return false
}

func (phase *narrowPhase) solveActorActor(solver *solverActorActor, a, b *actor, collision quad.Collision) []entity.Entity {

	// When this functions returns the
	// collision will have been solved
	defer func() {
		phase.solved = append(phase.solved, collision)
	}()

	var entities []entity.Entity

attemptSolve:
	a, b, coordCollision := newActorActorCollision(a, b)
	if coordCollision == nil {
		goto resolved
	}

	switch coordCollision.Type() {
	case coord.CT_NONE:
		// This may not be entirely accurate.
		// We should walk through the collision index
		// of our partner too see if they should resolve
		// some of there collisions first. They may
		// appear to be moving to us right now, but
		// have a collision that when solved will
		// render them motionless, thus we would become
		// motionless as well.
		e, err := phase.solveDependencies(solver, a, b, collision)

		switch err {
		case nil:
			if len(e) > 0 {
				entities = append(entities, e...)
			}

			// Try solving again
			goto attemptSolve

		case errCycleDetected:
			// Detected a cycle, we can't move
			currentNode(a, b, collision).revertMoveAction()
			goto resolved

		case errNoDependencies:
			// All dependencies have been solved
			// We can move
			goto resolved
		}

	case coord.CT_CELL_DEST:
		a.revertMoveAction()
		goto resolved

	case coord.CT_SWAP:
		a.revertMoveAction()
		b.revertMoveAction()
		goto resolved

	case coord.CT_A_INTO_B_FROM_SIDE:
		// This may not be entirely accurate.
		// We should walk through the collision index
		// of our partner too see if they should resolve
		// some of there collisions first. They may
		// appear to be moving to us right now, but
		// have a collision that when solved will
		// render them motionless, thus we would become
		// motionless as well.
		e, err := phase.solveDependencies(solver, a, b, collision)

		switch err {
		case nil:
			if len(e) > 0 {
				entities = append(entities, e...)
			}

			// Try solving again
			goto attemptSolve

		case errCycleDetected:
			a.revertMoveAction()
			goto resolved

		case errNoDependencies:
			if a.pathAction.End() >= b.pathAction.End() {
				goto resolved
			}

			a.revertMoveAction()
			goto resolved
		}

	case coord.CT_A_INTO_B:
		a.revertMoveAction()
		goto resolved

	case coord.CT_HEAD_TO_HEAD:
		fallthrough

	case coord.CT_FROM_SIDE:
		if a.pathAction.Start() < b.pathAction.Start() {
			// A has already won the destination
			b.revertMoveAction()
			goto resolved

		} else if a.pathAction.Start() > b.pathAction.Start() {
			// B has already won the destination
			a.revertMoveAction()
			goto resolved
		}
		// Start values are equal

		if a.pathAction.End() < b.pathAction.End() {
			// A is moving faster and wins the destination
			b.revertMoveAction()
			goto resolved

		} else if a.pathAction.End() > b.pathAction.End() {
			// B is moving faster and wins the destination
			a.revertMoveAction()
			goto resolved
		}
		// End values are equal

		// Movement direction priority goes in this order
		// N -> E -> S -> W
		if a.facing < b.facing {
			// A's movement direction has a higher priority
			b.revertMoveAction()
			goto resolved

		} else {
			// B's movement direction has a higher priority
			a.revertMoveAction()
			goto resolved
		}
	}

resolved:
	return append(entities, a.Entity(), b.Entity())
}

var errNoDependencies = errors.New("no dependencies")
var errCycleDetected = errors.New("cycle detected")

// Error can be errNoDependencies, errCycleDetected or nil
func (phase *narrowPhase) solveDependencies(solver *solverActorActor, a, b *actor, collision quad.Collision) ([]entity.Entity, error) {
	node := followGraph(a, b, collision)

	// Mark what actors have been visited
	if a != node.actor {
		solver.visited = append(solver.visited, a)
	} else {
		solver.visited = append(solver.visited, b)
	}

	// If the next node only has one collision
	// then there are no dependencies and the
	// collision can be solved
	if len(phase.collisionIndex[node.entity]) == 1 {
		return nil, errNoDependencies
	}

	// Walk through the directed graph of collisions and solve
	// all the collisions that the collision depends on.
	for _, c := range phase.collisionIndex[node.entity] {
		// Ignore the collision that caused us to recurse
		if c.IsSameAs(collision) {
			continue
		}

		// Avoid solving a collision that's already been solving.
		if phase.hasSolved(c) {
			continue
		}

		e := otherEntityIn(node.actor, c)

		switch e := e.(type) {
		case actorEntity:
			actor := phase.actorIndex[e.ActorId()]

			// Detect cycles
			if solver.hasVisited(actor) {
				return nil, errCycleDetected
			}

			// Recurse
			return phase.solveActorActor(solver, node.actor, actor, c), nil
		}
	}

	return nil, errNoDependencies
}
<commit_msg>[game] apply assail damage to an actor it collides with<commit_after>package game

import (
	"errors"
	"fmt"
	"math"

	"github.com/ghthor/engine/rpg2d/coord"
	"github.com/ghthor/engine/rpg2d/entity"
	"github.com/ghthor/engine/rpg2d/quad"
	"github.com/ghthor/engine/sim/stime"
)

type narrowPhase struct {
	actorIndex actorIndex

	// Reset at the beginning of every ResolveCollisions call
	solved []quad.Collision
	// Generated at the beginning of every ResolveCollisions call
	collisionIndex quad.CollisionIndex
}

func newNarrowPhase(actorIndex actorIndex) narrowPhase {
	return narrowPhase{actorIndex, make([]quad.Collision, 0, 10), nil}
}

// Returns if the collision exists in the
// slice of collisions that have been
// solved during this narrow phase tick.
func (phase narrowPhase) hasSolved(c quad.Collision) bool {
	for _, solved := range phase.solved {
		if c.IsSameAs(solved) {
			return true
		}
	}

	return false
}

// Implementation of the quad.NarrowPhaseHandler interface.
func (phase narrowPhase) ResolveCollisions(cg *quad.CollisionGroup, now stime.Time) ([]entity.Entity, []entity.Entity) {
	// Reset the resolved slice
	phase.solved = phase.solved[:0]

	// Generate a collision index for the collision group
	phase.collisionIndex = cg.CollisionIndex()

	// A map to store entities that still remain in the world
	remaining := make(map[entity.Id]entity.Entity, len(cg.Entities))
	remainingSlice := func() []entity.Entity {
		// Build a slice from the `remaining` map
		s := make([]entity.Entity, 0, len(remaining))
		for _, e := range remaining {
			s = append(s, e)
		}
		return s
	}

	for _, c := range cg.Collisions {
		if phase.hasSolved(c) {
			continue
		}

		var entities []entity.Entity

		// Resolve type of entity in collision.A
		switch e := c.A.(type) {
		case actorEntity:
			// Resolve the type of entity in collision.B
			entities = phase.resolveActorEntity(phase.actorIndex[e.ActorId()], c.B, c, now)
		default:
			switch e := c.B.(type) {
			case actorEntity:
				// Resolve the type of entity in collision.B
				entities = phase.resolveActorEntity(phase.actorIndex[e.ActorId()], c.A, c, now)
			}
		}

		// As collisions are solved they return entities
		// that have been created or modified and we store
		// them in a map by their Id. Multiple collisions
		// may modify and entity, therefor we only will
		// one version of the entity back to engine when
		// we return.
		for _, e := range entities {
			remaining[e.Id()] = e
		}
	}

	return remainingSlice(), nil
}

func (phase *narrowPhase) resolveActorEntity(a *actor, with entity.Entity, collision quad.Collision, now stime.Time) []entity.Entity {
	switch e := with.(type) {
	case actorEntity:
		b := phase.actorIndex[e.ActorId()]

		return phase.solveActorActor(&solverActorActor{}, a, b, collision)
	case assailEntity:
		return phase.solveActorAssail(a, e, collision, now)
	}

	return nil
}

func (phase *narrowPhase) solveActorAssail(a *actor, assail assailEntity, collision quad.Collision, now stime.Time) []entity.Entity {
	// Don't damage yourself
	if assail.spawnedBy == a.actorEntity.Id() {
		return []entity.Entity{a.Entity()}
	}

	var percentDamage float64

	switch a.pathAction {
	case nil:
		if a.Cell() == assail.Cell() {
			percentDamage = 1.0
		}
	default:
		coordCollision := coord.NewCellCollision(*a.pathAction, assail.Cell())
		percentDamage = coordCollision.OverlapAt(now)
	}

	damage := int(math.Floor(float64(assail.damage) * percentDamage))

	a.hp -= damage

	if a.hp <= 0 {
		a.hp = 100
	}

	return []entity.Entity{a.Entity()}
}

func newActorActorCollision(a, b *actor) (*actor, *actor, coord.Collision) {
	var collision coord.Collision

	switch {
	case a.pathAction == nil && b.pathAction != nil:
		a, b = b, a
		fallthrough
	case a.pathAction != nil && b.pathAction == nil:
		collision = coord.NewCellCollision(*a.pathAction, b.Cell())

		// A or B may have had a previous collision resolved that
		// caused this collision to not be possible anymore.
		// It is more relevant to return nil here then a
		// coord.Collision with type CT_NONE
		if collision.Type() == coord.CT_NONE {
			return a, b, nil
		}

	case a.pathAction != nil && b.pathAction != nil:
		pathCollision := coord.NewPathCollision(*a.pathAction, *b.pathAction)

		// coord.NewPathCollision can flip the,
		// A and B paths to simplify the number
		// of collision types. This normalizes
		// actor A with pathCollision.A
		if *a.pathAction != pathCollision.A {
			a, b = b, a
		}

		collision = pathCollision
	case a.pathAction == nil && b.pathAction == nil:
		// This case handles actors being on the same square,
		// but not moving at all.
		// There isn't a coord.CollisionType for this case.
		// Maybe there should be?
		return a, b, nil

	default:
		panic(fmt.Sprintf("impossible collision between {%v} {%v}", a, b))
	}
	return a, b, collision
}

type node struct {
	actor  *actor
	entity entity.Entity
}

// Move forward in the directed graph. This movement is based on
// which entity is occupying the destination of the other's path action.
func followGraph(a, b *actor, collision quad.Collision) node {
	// normalize a, b to collision.[A, B]
	if a.actorEntity.Id() != collision.A.Id() {
		a, b = b, a
	}

	var actor *actor
	var entity entity.Entity

	switch {
	case a.pathAction.Orig == b.pathAction.Dest:
		entity = collision.A
		actor = a

	case b.pathAction.Orig == a.pathAction.Dest:
		entity = collision.B
		actor = b

	default:
		panic(fmt.Sprintf("unexpected graph state %v between %v & %v", collision, a, b))
	}

	return node{actor, entity}
}

// Used to figure out which actor is "A" if
// the collision was CT_A_INTO_B instead of CT_NONE
func currentNode(a, b *actor, collision quad.Collision) *actor {
	switch {
	case a.pathAction.Orig == b.pathAction.Dest:
		return b

	case b.pathAction.Orig == a.pathAction.Dest:
		return a

	default:
		panic(fmt.Sprintf("unexpected graph state %v between %v & %v", collision, a, b))
	}
}

// Compare entity Id's with the entities in
// a collision and return the one that isn't
// the actor.
func otherEntityIn(a *actor, collision quad.Collision) entity.Entity {
	var e entity.Entity

	// figure out is prioritized actor is A or B in the collision
	switch {
	case a.actorEntity.Id() != collision.A.Id():
		e = collision.A

	case a.actorEntity.Id() != collision.B.Id():
		e = collision.B

	default:
		panic(fmt.Sprintf("unexpected graph state %v actor %v", collision, a))
	}

	return e
}

// Store what actor's have been visited during
// a recursive solve. Used to avoid infinite
// recursion through a cycle in the graph.
type solverActorActor struct {
	visited []*actor
}

func (s solverActorActor) hasVisited(actor *actor) bool {
	for _, a := range s.visited {
		if actor == a {
			return true
		}
	}

	return false
}

func (phase *narrowPhase) solveActorActor(solver *solverActorActor, a, b *actor, collision quad.Collision) []entity.Entity {

	// When this functions returns the
	// collision will have been solved
	defer func() {
		phase.solved = append(phase.solved, collision)
	}()

	var entities []entity.Entity

attemptSolve:
	a, b, coordCollision := newActorActorCollision(a, b)
	if coordCollision == nil {
		goto resolved
	}

	switch coordCollision.Type() {
	case coord.CT_NONE:
		// This may not be entirely accurate.
		// We should walk through the collision index
		// of our partner too see if they should resolve
		// some of there collisions first. They may
		// appear to be moving to us right now, but
		// have a collision that when solved will
		// render them motionless, thus we would become
		// motionless as well.
		e, err := phase.solveDependencies(solver, a, b, collision)

		switch err {
		case nil:
			if len(e) > 0 {
				entities = append(entities, e...)
			}

			// Try solving again
			goto attemptSolve

		case errCycleDetected:
			// Detected a cycle, we can't move
			currentNode(a, b, collision).revertMoveAction()
			goto resolved

		case errNoDependencies:
			// All dependencies have been solved
			// We can move
			goto resolved
		}

	case coord.CT_CELL_DEST:
		a.revertMoveAction()
		goto resolved

	case coord.CT_SWAP:
		a.revertMoveAction()
		b.revertMoveAction()
		goto resolved

	case coord.CT_A_INTO_B_FROM_SIDE:
		// This may not be entirely accurate.
		// We should walk through the collision index
		// of our partner too see if they should resolve
		// some of there collisions first. They may
		// appear to be moving to us right now, but
		// have a collision that when solved will
		// render them motionless, thus we would become
		// motionless as well.
		e, err := phase.solveDependencies(solver, a, b, collision)

		switch err {
		case nil:
			if len(e) > 0 {
				entities = append(entities, e...)
			}

			// Try solving again
			goto attemptSolve

		case errCycleDetected:
			a.revertMoveAction()
			goto resolved

		case errNoDependencies:
			if a.pathAction.End() >= b.pathAction.End() {
				goto resolved
			}

			a.revertMoveAction()
			goto resolved
		}

	case coord.CT_A_INTO_B:
		a.revertMoveAction()
		goto resolved

	case coord.CT_HEAD_TO_HEAD:
		fallthrough

	case coord.CT_FROM_SIDE:
		if a.pathAction.Start() < b.pathAction.Start() {
			// A has already won the destination
			b.revertMoveAction()
			goto resolved

		} else if a.pathAction.Start() > b.pathAction.Start() {
			// B has already won the destination
			a.revertMoveAction()
			goto resolved
		}
		// Start values are equal

		if a.pathAction.End() < b.pathAction.End() {
			// A is moving faster and wins the destination
			b.revertMoveAction()
			goto resolved

		} else if a.pathAction.End() > b.pathAction.End() {
			// B is moving faster and wins the destination
			a.revertMoveAction()
			goto resolved
		}
		// End values are equal

		// Movement direction priority goes in this order
		// N -> E -> S -> W
		if a.facing < b.facing {
			// A's movement direction has a higher priority
			b.revertMoveAction()
			goto resolved

		} else {
			// B's movement direction has a higher priority
			a.revertMoveAction()
			goto resolved
		}
	}

resolved:
	return append(entities, a.Entity(), b.Entity())
}

var errNoDependencies = errors.New("no dependencies")
var errCycleDetected = errors.New("cycle detected")

// Error can be errNoDependencies, errCycleDetected or nil
func (phase *narrowPhase) solveDependencies(solver *solverActorActor, a, b *actor, collision quad.Collision) ([]entity.Entity, error) {
	node := followGraph(a, b, collision)

	// Mark what actors have been visited
	if a != node.actor {
		solver.visited = append(solver.visited, a)
	} else {
		solver.visited = append(solver.visited, b)
	}

	// If the next node only has one collision
	// then there are no dependencies and the
	// collision can be solved
	if len(phase.collisionIndex[node.entity]) == 1 {
		return nil, errNoDependencies
	}

	// Walk through the directed graph of collisions and solve
	// all the collisions that the collision depends on.
	for _, c := range phase.collisionIndex[node.entity] {
		// Ignore the collision that caused us to recurse
		if c.IsSameAs(collision) {
			continue
		}

		// Avoid solving a collision that's already been solving.
		if phase.hasSolved(c) {
			continue
		}

		e := otherEntityIn(node.actor, c)

		switch e := e.(type) {
		case actorEntity:
			actor := phase.actorIndex[e.ActorId()]

			// Detect cycles
			if solver.hasVisited(actor) {
				return nil, errCycleDetected
			}

			// Recurse
			return phase.solveActorActor(solver, node.actor, actor, c), nil
		}
	}

	return nil, errNoDependencies
}
<|endoftext|>


<commit_before>package game

import (
	"fmt"
	"net"
	"net/http"

	"github.com/ghthor/aodd/game/datastore"
	"github.com/ghthor/engine/net/encoding"
	"github.com/ghthor/engine/net/protocol"
	"golang.org/x/net/websocket"

	"github.com/ghthor/gospec"
	. "github.com/ghthor/gospec"
)

var nextPort = 45456

// Abstracts the internals of creating 2 Websocket endpoints
func twoWebsockets() (*websocket.Conn, *websocket.Conn, chan<- bool, <-chan bool, error) {
	testServerAddr := fmt.Sprintf("localhost:%v", nextPort)
	nextPort++

	listener, err := net.Listen("tcp", testServerAddr)
	if err != nil {
		return nil, nil, nil, nil, err
	}

	ws2Chan := make(chan *websocket.Conn)
	mux := http.NewServeMux()
	mux.Handle("/", websocket.Handler(func(ws *websocket.Conn) {
		ws2Chan <- ws
		// Wait till the server has been signaled to close
		<-ws2Chan
	}))

	server := &http.Server{Handler: mux}

	// Start a Server that signal's when its finished listening
	serverClosed := make(chan bool)
	go func() {
		server.Serve(listener)
		// Signal that server has shutdown
		serverClosed <- true
	}()

	// Get the Second Websocket
	ws1, err := websocket.Dial("ws://"+testServerAddr+"/", "", "http://localhost")
	if err != nil {
		return nil, nil, nil, nil, err
	}
	// Get the First Websocket
	ws2 := <-ws2Chan

	closeServer := make(chan bool)
	go func() {
		// Wait for signal to shutdown
		<-closeServer

		// Close the websocket in the http.Handler
		ws2Chan <- ws2

		// Close the listen server
		listener.Close()
	}()

	return ws1, ws2, closeServer, serverClosed, nil
}

func DescribeActorConn(c gospec.Context) {
	// Setup
	ws, wsServer, closeServer, serverClosed, err := twoWebsockets()

	c.Assume(err, IsNil)
	c.Assume(ws, Not(IsNil))
	c.Assume(wsServer, Not(IsNil))

	defer func() {
		select {
		case closeServer <- true:
			<-serverClosed
		case <-serverClosed:
		}
	}()

	ds := datastore.NewMemDatastore()
	ds.AddActor("actor", "password")

	conn := actorHandler{
		Conn: protocol.NewWebsocketConn(wsServer),

		handlePacket: (actorHandler).loginHandler,

		datastore: ds,
	}

	client := protocol.NewWebsocketConn(ws)

	// Convenience function for sending requests
	login := func() {
		client.SendJson("login", LoginReq{"actor", "password"})

		conn, err = conn.handlePacket(conn)
		c.Assume(err, IsNil)

		packet, err := client.Read()
		c.Assume(err, IsNil)
		c.Assume(packet.Type, Equals, encoding.PT_MESSAGE)
		c.Assume(packet.Msg, Equals, "loginSuccess")
	}

	c.Specify("packet processing should terminate", func() {
		c.Specify("when a client disconnects", func() {
			go func() {
				client.Send(encoding.Packet{})
				ws.Close()
			}()

			err := conn.run()
			c.Assume(err, Not(IsNil))

			c.Expect(err, Equals, ErrWebsocketClientDisconnected)
		})

		c.Specify("when a client disconnects after the actor logs in", func() {
			login()

			go func() {
				client.Send(encoding.Packet{})
				ws.Close()
			}()

			err := conn.run()
			c.Assume(err, Not(IsNil))

			c.Expect(err, Equals, ErrWebsocketClientDisconnected)
		})

		c.Specify("when the connection is lost", func() {
			go func() {
				ws.Close()
			}()

			err := conn.run()
			c.Assume(err, Not(IsNil))

			_, isAnDisconnectionError := err.(*protocol.DisconnectionError)
			c.Expect(isAnDisconnectionError, IsTrue)
		})

		c.Specify("when the connection is lost after the actor logs in", func() {
			login()

			go func() {
				ws.Close()
			}()

			err := conn.run()
			c.Assume(err, Not(IsNil))

			_, isAnDisconnectionError := err.(*protocol.DisconnectionError)
			c.Expect(isAnDisconnectionError, IsTrue)
		})
	})

	c.Specify("when the client sends a request", func() {
		c.Specify("to login", func() {
			c.Specify("the request should fail", func() {
				c.Specify("if the actor doesn't exist", func() {
					client.SendJson("login", LoginReq{"notAnActor", "anything"})

					conn, err = conn.handlePacket(conn)
					c.Assume(err, IsNil)

					packet, err := client.Read()
					c.Assume(err, IsNil)

					c.Expect(packet.Type, Equals, encoding.PT_MESSAGE)
					c.Expect(packet.Msg, Equals, "actorDoesntExist")

					c.Expect(conn.Actor(), Equals, datastore.Actor{})
				})

				c.Specify("if the password is incorrect", func() {
					client.SendJson("login", LoginReq{"actor", "wrongpassword"})

					conn, err = conn.handlePacket(conn)
					c.Assume(err, IsNil)

					packet, err := client.Read()
					c.Assume(err, IsNil)

					c.Expect(packet.Type, Equals, encoding.PT_MESSAGE)
					c.Expect(packet.Msg, Equals, "authFailed")

					c.Expect(conn.Actor(), Equals, datastore.Actor{})
				})

				c.Specify("if the client is already logged in", func() {
					login()
					c.Assume(conn.Actor().Name, Equals, "actor")

					client.SendJson("login", LoginReq{"actor", "password"})

					conn, err = conn.handlePacket(conn)
					c.Assume(err, IsNil)

					packet, err := client.Read()
					c.Assume(err, IsNil)

					c.Expect(packet.Type, Equals, encoding.PT_MESSAGE)
					c.Expect(packet.Msg, Equals, "alreadyLoggedIn")
				})
			})

			c.Specify("the request should succeed", func() {
				client.SendJson("login", LoginReq{"actor", "password"})

				conn, err = conn.handlePacket(conn)
				c.Assume(err, IsNil)

				packet, err := client.Read()
				c.Assume(err, IsNil)

				c.Expect(packet.Type, Equals, encoding.PT_MESSAGE)
				c.Expect(packet.Msg, Equals, "loginSuccess")

				c.Expect(conn.Actor().Name, Equals, "actor")
			})
		})

		c.Specify("to create a new actor", func() {
			c.Specify("the request should fail", func() {
				c.Specify("if the actor already exists", func() {
					client.SendJson("create", LoginReq{"actor", "password"})

					conn, err = conn.handlePacket(conn)
					c.Assume(err, IsNil)

					packet, err := client.Read()
					c.Assume(err, IsNil)

					c.Expect(packet.Type, Equals, encoding.PT_MESSAGE)
					c.Expect(packet.Msg, Equals, "actorAlreadyExists")
				})
			})
		})
	})
}
<commit_msg>Specify that creating an actor will fail if the connection is already logged in<commit_after>package game

import (
	"fmt"
	"net"
	"net/http"

	"github.com/ghthor/aodd/game/datastore"
	"github.com/ghthor/engine/net/encoding"
	"github.com/ghthor/engine/net/protocol"
	"golang.org/x/net/websocket"

	"github.com/ghthor/gospec"
	. "github.com/ghthor/gospec"
)

var nextPort = 45456

// Abstracts the internals of creating 2 Websocket endpoints
func twoWebsockets() (*websocket.Conn, *websocket.Conn, chan<- bool, <-chan bool, error) {
	testServerAddr := fmt.Sprintf("localhost:%v", nextPort)
	nextPort++

	listener, err := net.Listen("tcp", testServerAddr)
	if err != nil {
		return nil, nil, nil, nil, err
	}

	ws2Chan := make(chan *websocket.Conn)
	mux := http.NewServeMux()
	mux.Handle("/", websocket.Handler(func(ws *websocket.Conn) {
		ws2Chan <- ws
		// Wait till the server has been signaled to close
		<-ws2Chan
	}))

	server := &http.Server{Handler: mux}

	// Start a Server that signal's when its finished listening
	serverClosed := make(chan bool)
	go func() {
		server.Serve(listener)
		// Signal that server has shutdown
		serverClosed <- true
	}()

	// Get the Second Websocket
	ws1, err := websocket.Dial("ws://"+testServerAddr+"/", "", "http://localhost")
	if err != nil {
		return nil, nil, nil, nil, err
	}
	// Get the First Websocket
	ws2 := <-ws2Chan

	closeServer := make(chan bool)
	go func() {
		// Wait for signal to shutdown
		<-closeServer

		// Close the websocket in the http.Handler
		ws2Chan <- ws2

		// Close the listen server
		listener.Close()
	}()

	return ws1, ws2, closeServer, serverClosed, nil
}

func DescribeActorConn(c gospec.Context) {
	// Setup
	ws, wsServer, closeServer, serverClosed, err := twoWebsockets()

	c.Assume(err, IsNil)
	c.Assume(ws, Not(IsNil))
	c.Assume(wsServer, Not(IsNil))

	defer func() {
		select {
		case closeServer <- true:
			<-serverClosed
		case <-serverClosed:
		}
	}()

	ds := datastore.NewMemDatastore()
	ds.AddActor("actor", "password")

	conn := actorHandler{
		Conn: protocol.NewWebsocketConn(wsServer),

		handlePacket: (actorHandler).loginHandler,

		datastore: ds,
	}

	client := protocol.NewWebsocketConn(ws)

	// Convenience function for sending requests
	login := func() {
		client.SendJson("login", LoginReq{"actor", "password"})

		conn, err = conn.handlePacket(conn)
		c.Assume(err, IsNil)

		packet, err := client.Read()
		c.Assume(err, IsNil)
		c.Assume(packet.Type, Equals, encoding.PT_MESSAGE)
		c.Assume(packet.Msg, Equals, "loginSuccess")
	}

	c.Specify("packet processing should terminate", func() {
		c.Specify("when a client disconnects", func() {
			go func() {
				client.Send(encoding.Packet{})
				ws.Close()
			}()

			err := conn.run()
			c.Assume(err, Not(IsNil))

			c.Expect(err, Equals, ErrWebsocketClientDisconnected)
		})

		c.Specify("when a client disconnects after the actor logs in", func() {
			login()

			go func() {
				client.Send(encoding.Packet{})
				ws.Close()
			}()

			err := conn.run()
			c.Assume(err, Not(IsNil))

			c.Expect(err, Equals, ErrWebsocketClientDisconnected)
		})

		c.Specify("when the connection is lost", func() {
			go func() {
				ws.Close()
			}()

			err := conn.run()
			c.Assume(err, Not(IsNil))

			_, isAnDisconnectionError := err.(*protocol.DisconnectionError)
			c.Expect(isAnDisconnectionError, IsTrue)
		})

		c.Specify("when the connection is lost after the actor logs in", func() {
			login()

			go func() {
				ws.Close()
			}()

			err := conn.run()
			c.Assume(err, Not(IsNil))

			_, isAnDisconnectionError := err.(*protocol.DisconnectionError)
			c.Expect(isAnDisconnectionError, IsTrue)
		})
	})

	c.Specify("when the client sends a request", func() {
		c.Specify("to login", func() {
			c.Specify("the request should fail", func() {
				c.Specify("if the actor doesn't exist", func() {
					client.SendJson("login", LoginReq{"notAnActor", "anything"})

					conn, err = conn.handlePacket(conn)
					c.Assume(err, IsNil)

					packet, err := client.Read()
					c.Assume(err, IsNil)

					c.Expect(packet.Type, Equals, encoding.PT_MESSAGE)
					c.Expect(packet.Msg, Equals, "actorDoesntExist")

					c.Expect(conn.Actor(), Equals, datastore.Actor{})
				})

				c.Specify("if the password is incorrect", func() {
					client.SendJson("login", LoginReq{"actor", "wrongpassword"})

					conn, err = conn.handlePacket(conn)
					c.Assume(err, IsNil)

					packet, err := client.Read()
					c.Assume(err, IsNil)

					c.Expect(packet.Type, Equals, encoding.PT_MESSAGE)
					c.Expect(packet.Msg, Equals, "authFailed")

					c.Expect(conn.Actor(), Equals, datastore.Actor{})
				})

				c.Specify("if the client is already logged in", func() {
					login()
					c.Assume(conn.Actor().Name, Equals, "actor")

					client.SendJson("login", LoginReq{"actor", "password"})

					conn, err = conn.handlePacket(conn)
					c.Assume(err, IsNil)

					packet, err := client.Read()
					c.Assume(err, IsNil)

					c.Expect(packet.Type, Equals, encoding.PT_MESSAGE)
					c.Expect(packet.Msg, Equals, "alreadyLoggedIn")
				})
			})

			c.Specify("the request should succeed", func() {
				client.SendJson("login", LoginReq{"actor", "password"})

				conn, err = conn.handlePacket(conn)
				c.Assume(err, IsNil)

				packet, err := client.Read()
				c.Assume(err, IsNil)

				c.Expect(packet.Type, Equals, encoding.PT_MESSAGE)
				c.Expect(packet.Msg, Equals, "loginSuccess")

				c.Expect(conn.Actor().Name, Equals, "actor")
			})
		})

		c.Specify("to create a new actor", func() {
			c.Specify("the request should fail", func() {
				c.Specify("if the actor already exists", func() {
					client.SendJson("create", LoginReq{"actor", "password"})

					conn, err = conn.handlePacket(conn)
					c.Assume(err, IsNil)

					packet, err := client.Read()
					c.Assume(err, IsNil)

					c.Expect(packet.Type, Equals, encoding.PT_MESSAGE)
					c.Expect(packet.Msg, Equals, "actorAlreadyExists")
				})

				c.Specify("if the an actor has been logged in", func() {
					login()
					client.SendJson("create", LoginReq{"newActor", "password"})

					conn, err = conn.handlePacket(conn)
					c.Assume(err, IsNil)

					packet, err := client.Read()
					c.Assume(err, IsNil)

					c.Expect(packet.Type, Equals, encoding.PT_MESSAGE)
					c.Expect(packet.Msg, Equals, "alreadyLoggedIn")
				})
			})
		})
	})
}
<|endoftext|>


<commit_before>package binder

import (
	"net/http"
	"reflect"

	"github.com/SoCloz/binder/response"
)

// Action Parameters
type ActionArgs struct {
	Name string
	Type reflect.Type
}

// A controller action wrapper
type Wrapper struct {
	Call       reflect.Value
	IsVariadic bool
	Args       []*ActionArgs
}

// Wraps a controller action
//
// Example :
// 	 func MyAction(id int, param string) {}
//   http.Handle("/my_action", binder.Wrap(MyAction, "id", "param"))
func Wrap(call interface{}, params ...string) *Wrapper {
	w := new(Wrapper)
	w.Call = reflect.ValueOf(call)
	callType := w.Call.Type()
	w.IsVariadic = callType.IsVariadic()
	w.Args = make([]*ActionArgs, callType.NumIn())

	if callType.NumIn() < len(params) {
		panic("Wrong number of params")
	}
	for i := 0; i < callType.NumIn(); i++ {
		typ := callType.In(i)
		var paramName string
		if i < len(params) {
			paramName = params[i]
		} else {
			paramName = "*"
		}
		w.Args[i] = &ActionArgs{Name: paramName, Type: typ}
		if paramName == "*" {
			RegisterStructAttributes(typ)
		}
	}
	return w
}

// The http handler for the wrapped action
func (wr *Wrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	values := r.URL.Query()

	methodArgs := make([]reflect.Value, len(wr.Args))
	for i, a := range wr.Args {
		methodArgs[i] = GetBoundValue(values, a.Name, a.Type)
	}
	var resultValue reflect.Value
	if wr.IsVariadic {
		resultValue = wr.Call.CallSlice(methodArgs)[0]
	} else {
		resultValue = wr.Call.Call(methodArgs)[0]
	}
	resp := resultValue.Interface().(response.Response)
	resp.ApplyTo(w)
}
<commit_msg>godoc comment formatting<commit_after>package binder

import (
	"net/http"
	"reflect"

	"github.com/SoCloz/binder/response"
)

// Action Parameters
type ActionArgs struct {
	Name string
	Type reflect.Type
}

// A controller action wrapper
type Wrapper struct {
	Call       reflect.Value
	IsVariadic bool
	Args       []*ActionArgs
}

// Wraps a controller action
//
// Example :
//   func MyAction(id int, param string) {}
//   http.Handle("/my_action", binder.Wrap(MyAction, "id", "param"))
func Wrap(call interface{}, params ...string) *Wrapper {
	w := new(Wrapper)
	w.Call = reflect.ValueOf(call)
	callType := w.Call.Type()
	w.IsVariadic = callType.IsVariadic()
	w.Args = make([]*ActionArgs, callType.NumIn())

	if callType.NumIn() < len(params) {
		panic("Wrong number of params")
	}
	for i := 0; i < callType.NumIn(); i++ {
		typ := callType.In(i)
		var paramName string
		if i < len(params) {
			paramName = params[i]
		} else {
			paramName = "*"
		}
		w.Args[i] = &ActionArgs{Name: paramName, Type: typ}
		if paramName == "*" {
			RegisterStructAttributes(typ)
		}
	}
	return w
}

// The http handler for the wrapped action
func (wr *Wrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	values := r.URL.Query()

	methodArgs := make([]reflect.Value, len(wr.Args))
	for i, a := range wr.Args {
		methodArgs[i] = GetBoundValue(values, a.Name, a.Type)
	}
	var resultValue reflect.Value
	if wr.IsVariadic {
		resultValue = wr.Call.CallSlice(methodArgs)[0]
	} else {
		resultValue = wr.Call.Call(methodArgs)[0]
	}
	resp := resultValue.Interface().(response.Response)
	resp.ApplyTo(w)
}
<|endoftext|>


<commit_before>// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package git

import (
	"bufio"
	"bytes"
	"context"
	"io"
	"path"
	"sort"
	"strings"

	"github.com/djherbis/buffer"
	"github.com/djherbis/nio/v3"
)

// LogNameStatusRepo opens git log --raw in the provided repo and returns a stdin pipe, a stdout reader and cancel function
func LogNameStatusRepo(repository, head, treepath string, paths ...string) (*bufio.Reader, func()) {
	// We often want to feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
	// so let's create a batch stdin and stdout
	stdoutReader, stdoutWriter := nio.Pipe(buffer.New(32 * 1024))
	cancel := func() {
		_ = stdoutReader.Close()
		_ = stdoutWriter.Close()
	}

	args := make([]string, 0, 8+len(paths))
	args = append(args, "log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z", head, "--")
	if len(paths) < 70 {
		if treepath != "" {
			args = append(args, treepath)
			for _, pth := range paths {
				if pth != "" {
					args = append(args, path.Join(treepath, pth))
				}
			}
		} else {
			for _, pth := range paths {
				if pth != "" {
					args = append(args, pth)
				}
			}
		}
	} else if treepath != "" {
		args = append(args, treepath)
	}

	go func() {
		stderr := strings.Builder{}
		err := NewCommand(args...).RunInDirFullPipeline(repository, stdoutWriter, &stderr, nil)
		if err != nil {
			_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
		} else {
			_ = stdoutWriter.Close()
		}
	}()

	// For simplicities sake we'll us a buffered reader to read from the cat-file --batch
	bufReader := bufio.NewReaderSize(stdoutReader, 32*1024)

	return bufReader, cancel
}

// LogNameStatusRepoParser parses a git log raw output from LogRawRepo
type LogNameStatusRepoParser struct {
	treepath string
	paths    []string
	next     []byte
	buffull  bool
	rd       *bufio.Reader
	cancel   func()
}

// NewLogNameStatusRepoParser returns a new parser for a git log raw output
func NewLogNameStatusRepoParser(repository, head, treepath string, paths ...string) *LogNameStatusRepoParser {
	rd, cancel := LogNameStatusRepo(repository, head, treepath, paths...)
	return &LogNameStatusRepoParser{
		treepath: treepath,
		paths:    paths,
		rd:       rd,
		cancel:   cancel,
	}
}

// LogNameStatusCommitData represents a commit artefact from git log raw
type LogNameStatusCommitData struct {
	CommitID  string
	ParentIDs []string
	Paths     []bool
}

// Next returns the next LogStatusCommitData
func (g *LogNameStatusRepoParser) Next(treepath string, paths2ids map[string]int, changed []bool, maxpathlen int) (*LogNameStatusCommitData, error) {
	var err error
	if g.next == nil || len(g.next) == 0 {
		g.buffull = false
		g.next, err = g.rd.ReadSlice('\x00')
		if err != nil {
			if err == bufio.ErrBufferFull {
				g.buffull = true
			} else if err == io.EOF {
				return nil, nil
			} else {
				return nil, err
			}
		}
	}

	ret := LogNameStatusCommitData{}
	if bytes.Equal(g.next, []byte("commit\000")) {
		g.next, err = g.rd.ReadSlice('\x00')
		if err != nil {
			if err == bufio.ErrBufferFull {
				g.buffull = true
			} else if err == io.EOF {
				return nil, nil
			} else {
				return nil, err
			}
		}
	}

	// Our "line" must look like: <commitid> SP (<parent> SP) * NUL
	ret.CommitID = string(g.next[0:40])
	parents := string(g.next[41:])
	if g.buffull {
		more, err := g.rd.ReadString('\x00')
		if err != nil {
			return nil, err
		}
		parents += more
	}
	parents = parents[:len(parents)-1]
	ret.ParentIDs = strings.Split(parents, " ")

	// now read the next "line"
	g.buffull = false
	g.next, err = g.rd.ReadSlice('\x00')
	if err != nil {
		if err == bufio.ErrBufferFull {
			g.buffull = true
		} else if err != io.EOF {
			return nil, err
		}
	}

	if err == io.EOF || !(g.next[0] == '\n' || g.next[0] == '\000') {
		return &ret, nil
	}

	// Ok we have some changes.
	// This line will look like: NL <fname> NUL
	//
	// Subsequent lines will not have the NL - so drop it here - g.bufffull must also be false at this point too.
	if g.next[0] == '\n' {
		g.next = g.next[1:]
	} else {
		g.buffull = false
		g.next, err = g.rd.ReadSlice('\x00')
		if err != nil {
			if err == bufio.ErrBufferFull {
				g.buffull = true
			} else if err != io.EOF {
				return nil, err
			}
		}
		if g.next[0] == '\x00' {
			g.buffull = false
			g.next, err = g.rd.ReadSlice('\x00')
			if err != nil {
				if err == bufio.ErrBufferFull {
					g.buffull = true
				} else if err != io.EOF {
					return nil, err
				}
			}
		}
	}

	fnameBuf := make([]byte, 4096)

diffloop:
	for {
		if err == io.EOF || bytes.Equal(g.next, []byte("commit\000")) {
			return &ret, nil
		}
		g.next, err = g.rd.ReadSlice('\x00')
		if err != nil {
			if err == bufio.ErrBufferFull {
				g.buffull = true
			} else if err == io.EOF {
				return &ret, nil
			} else {
				return nil, err
			}
		}
		copy(fnameBuf, g.next)
		if len(fnameBuf) < len(g.next) {
			fnameBuf = append(fnameBuf, g.next[len(fnameBuf):]...)
		} else {
			fnameBuf = fnameBuf[:len(g.next)]
		}
		if err != nil {
			if err != bufio.ErrBufferFull {
				return nil, err
			}
			more, err := g.rd.ReadBytes('\x00')
			if err != nil {
				return nil, err
			}
			fnameBuf = append(fnameBuf, more...)
		}

		// read the next line
		g.buffull = false
		g.next, err = g.rd.ReadSlice('\x00')
		if err != nil {
			if err == bufio.ErrBufferFull {
				g.buffull = true
			} else if err != io.EOF {
				return nil, err
			}
		}

		if treepath != "" {
			if !bytes.HasPrefix(fnameBuf, []byte(treepath)) {
				fnameBuf = fnameBuf[:cap(fnameBuf)]
				continue diffloop
			}
		}
		fnameBuf = fnameBuf[len(treepath) : len(fnameBuf)-1]
		if len(fnameBuf) > maxpathlen {
			fnameBuf = fnameBuf[:cap(fnameBuf)]
			continue diffloop
		}
		if len(fnameBuf) > 0 {
			if len(treepath) > 0 {
				if fnameBuf[0] != '/' || bytes.IndexByte(fnameBuf[1:], '/') >= 0 {
					fnameBuf = fnameBuf[:cap(fnameBuf)]
					continue diffloop
				}
				fnameBuf = fnameBuf[1:]
			} else if bytes.IndexByte(fnameBuf, '/') >= 0 {
				fnameBuf = fnameBuf[:cap(fnameBuf)]
				continue diffloop
			}
		}

		idx, ok := paths2ids[string(fnameBuf)]
		if !ok {
			fnameBuf = fnameBuf[:cap(fnameBuf)]
			continue diffloop
		}
		if ret.Paths == nil {
			ret.Paths = changed
		}
		changed[idx] = true
	}
}

// Close closes the parser
func (g *LogNameStatusRepoParser) Close() {
	g.cancel()
}

// WalkGitLog walks the git log --name-status for the head commit in the provided treepath and files
func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath string, paths ...string) (map[string]string, error) {
	tree, err := head.SubTree(treepath)
	if err != nil {
		return nil, err
	}

	entries, err := tree.ListEntries()
	if err != nil {
		return nil, err
	}

	if len(paths) == 0 {
		paths = make([]string, 0, len(entries)+1)
		paths = append(paths, "")
		for _, entry := range entries {
			paths = append(paths, entry.Name())
		}
	} else {
		sort.Strings(paths)
		if paths[0] != "" {
			paths = append([]string{""}, paths...)
		}
		// remove duplicates
		for i := len(paths) - 1; i > 0; i-- {
			if paths[i] == paths[i-1] {
				paths = append(paths[:i-1], paths[i:]...)
			}
		}
	}

	path2idx := map[string]int{}
	maxpathlen := len(treepath)

	for i := range paths {
		path2idx[paths[i]] = i
		pthlen := len(paths[i]) + len(treepath) + 1
		if pthlen > maxpathlen {
			maxpathlen = pthlen
		}
	}

	g := NewLogNameStatusRepoParser(repo.Path, head.ID.String(), treepath, paths...)
	defer g.Close()

	results := make([]string, len(paths))
	remaining := len(paths)
	nextRestart := (len(paths) * 3) / 4
	if nextRestart > 70 {
		nextRestart = 70
	}
	lastEmptyParent := head.ID.String()
	commitSinceLastEmptyParent := uint64(0)
	commitSinceNextRestart := uint64(0)
	parentRemaining := map[string]bool{}

	changed := make([]bool, len(paths))

heaploop:
	for {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		default:
		}
		current, err := g.Next(treepath, path2idx, changed, maxpathlen)
		if err != nil {
			g.Close()
			return nil, err
		}
		if current == nil {
			break heaploop
		}
		delete(parentRemaining, current.CommitID)
		if current.Paths != nil {
			for i, found := range current.Paths {
				if !found {
					continue
				}
				changed[i] = false
				if results[i] == "" {
					results[i] = current.CommitID
					delete(path2idx, paths[i])
					remaining--
					if results[0] == "" {
						results[0] = current.CommitID
						delete(path2idx, "")
						remaining--
					}
				}
			}
		}

		if remaining <= 0 {
			break heaploop
		}
		commitSinceLastEmptyParent++
		if len(parentRemaining) == 0 {
			lastEmptyParent = current.CommitID
			commitSinceLastEmptyParent = 0
		}
		if remaining <= nextRestart {
			commitSinceNextRestart++
			if 4*commitSinceNextRestart > 3*commitSinceLastEmptyParent {
				g.Close()
				remainingPaths := make([]string, 0, len(paths))
				for i, pth := range paths {
					if results[i] == "" {
						remainingPaths = append(remainingPaths, pth)
					}
				}
				g = NewLogNameStatusRepoParser(repo.Path, lastEmptyParent, treepath, remainingPaths...)
				parentRemaining = map[string]bool{}
				nextRestart = (remaining * 3) / 4
				continue heaploop
			}
		}
		for _, parent := range current.ParentIDs {
			parentRemaining[parent] = true
		}
	}
	g.Close()

	resultsMap := map[string]string{}
	for i, pth := range paths {
		resultsMap[pth] = results[i]
	}

	return resultsMap, nil
}
<commit_msg>Prevent NPE on empty commit (#16812)<commit_after>// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package git

import (
	"bufio"
	"bytes"
	"context"
	"io"
	"path"
	"sort"
	"strings"

	"github.com/djherbis/buffer"
	"github.com/djherbis/nio/v3"
)

// LogNameStatusRepo opens git log --raw in the provided repo and returns a stdin pipe, a stdout reader and cancel function
func LogNameStatusRepo(repository, head, treepath string, paths ...string) (*bufio.Reader, func()) {
	// We often want to feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
	// so let's create a batch stdin and stdout
	stdoutReader, stdoutWriter := nio.Pipe(buffer.New(32 * 1024))
	cancel := func() {
		_ = stdoutReader.Close()
		_ = stdoutWriter.Close()
	}

	args := make([]string, 0, 8+len(paths))
	args = append(args, "log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z", head, "--")
	if len(paths) < 70 {
		if treepath != "" {
			args = append(args, treepath)
			for _, pth := range paths {
				if pth != "" {
					args = append(args, path.Join(treepath, pth))
				}
			}
		} else {
			for _, pth := range paths {
				if pth != "" {
					args = append(args, pth)
				}
			}
		}
	} else if treepath != "" {
		args = append(args, treepath)
	}

	go func() {
		stderr := strings.Builder{}
		err := NewCommand(args...).RunInDirFullPipeline(repository, stdoutWriter, &stderr, nil)
		if err != nil {
			_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
		} else {
			_ = stdoutWriter.Close()
		}
	}()

	// For simplicities sake we'll us a buffered reader to read from the cat-file --batch
	bufReader := bufio.NewReaderSize(stdoutReader, 32*1024)

	return bufReader, cancel
}

// LogNameStatusRepoParser parses a git log raw output from LogRawRepo
type LogNameStatusRepoParser struct {
	treepath string
	paths    []string
	next     []byte
	buffull  bool
	rd       *bufio.Reader
	cancel   func()
}

// NewLogNameStatusRepoParser returns a new parser for a git log raw output
func NewLogNameStatusRepoParser(repository, head, treepath string, paths ...string) *LogNameStatusRepoParser {
	rd, cancel := LogNameStatusRepo(repository, head, treepath, paths...)
	return &LogNameStatusRepoParser{
		treepath: treepath,
		paths:    paths,
		rd:       rd,
		cancel:   cancel,
	}
}

// LogNameStatusCommitData represents a commit artefact from git log raw
type LogNameStatusCommitData struct {
	CommitID  string
	ParentIDs []string
	Paths     []bool
}

// Next returns the next LogStatusCommitData
func (g *LogNameStatusRepoParser) Next(treepath string, paths2ids map[string]int, changed []bool, maxpathlen int) (*LogNameStatusCommitData, error) {
	var err error
	if g.next == nil || len(g.next) == 0 {
		g.buffull = false
		g.next, err = g.rd.ReadSlice('\x00')
		if err != nil {
			if err == bufio.ErrBufferFull {
				g.buffull = true
			} else if err == io.EOF {
				return nil, nil
			} else {
				return nil, err
			}
		}
	}

	ret := LogNameStatusCommitData{}
	if bytes.Equal(g.next, []byte("commit\000")) {
		g.next, err = g.rd.ReadSlice('\x00')
		if err != nil {
			if err == bufio.ErrBufferFull {
				g.buffull = true
			} else if err == io.EOF {
				return nil, nil
			} else {
				return nil, err
			}
		}
	}

	// Our "line" must look like: <commitid> SP (<parent> SP) * NUL
	ret.CommitID = string(g.next[0:40])
	parents := string(g.next[41:])
	if g.buffull {
		more, err := g.rd.ReadString('\x00')
		if err != nil {
			return nil, err
		}
		parents += more
	}
	parents = parents[:len(parents)-1]
	ret.ParentIDs = strings.Split(parents, " ")

	// now read the next "line"
	g.buffull = false
	g.next, err = g.rd.ReadSlice('\x00')
	if err != nil {
		if err == bufio.ErrBufferFull {
			g.buffull = true
		} else if err != io.EOF {
			return nil, err
		}
	}

	if err == io.EOF || !(g.next[0] == '\n' || g.next[0] == '\000') {
		return &ret, nil
	}

	// Ok we have some changes.
	// This line will look like: NL <fname> NUL
	//
	// Subsequent lines will not have the NL - so drop it here - g.bufffull must also be false at this point too.
	if g.next[0] == '\n' {
		g.next = g.next[1:]
	} else {
		g.buffull = false
		g.next, err = g.rd.ReadSlice('\x00')
		if err != nil {
			if err == bufio.ErrBufferFull {
				g.buffull = true
			} else if err != io.EOF {
				return nil, err
			}
		}
		if len(g.next) == 0 {
			return &ret, nil
		}
		if g.next[0] == '\x00' {
			g.buffull = false
			g.next, err = g.rd.ReadSlice('\x00')
			if err != nil {
				if err == bufio.ErrBufferFull {
					g.buffull = true
				} else if err != io.EOF {
					return nil, err
				}
			}
		}
	}

	fnameBuf := make([]byte, 4096)

diffloop:
	for {
		if err == io.EOF || bytes.Equal(g.next, []byte("commit\000")) {
			return &ret, nil
		}
		g.next, err = g.rd.ReadSlice('\x00')
		if err != nil {
			if err == bufio.ErrBufferFull {
				g.buffull = true
			} else if err == io.EOF {
				return &ret, nil
			} else {
				return nil, err
			}
		}
		copy(fnameBuf, g.next)
		if len(fnameBuf) < len(g.next) {
			fnameBuf = append(fnameBuf, g.next[len(fnameBuf):]...)
		} else {
			fnameBuf = fnameBuf[:len(g.next)]
		}
		if err != nil {
			if err != bufio.ErrBufferFull {
				return nil, err
			}
			more, err := g.rd.ReadBytes('\x00')
			if err != nil {
				return nil, err
			}
			fnameBuf = append(fnameBuf, more...)
		}

		// read the next line
		g.buffull = false
		g.next, err = g.rd.ReadSlice('\x00')
		if err != nil {
			if err == bufio.ErrBufferFull {
				g.buffull = true
			} else if err != io.EOF {
				return nil, err
			}
		}

		if treepath != "" {
			if !bytes.HasPrefix(fnameBuf, []byte(treepath)) {
				fnameBuf = fnameBuf[:cap(fnameBuf)]
				continue diffloop
			}
		}
		fnameBuf = fnameBuf[len(treepath) : len(fnameBuf)-1]
		if len(fnameBuf) > maxpathlen {
			fnameBuf = fnameBuf[:cap(fnameBuf)]
			continue diffloop
		}
		if len(fnameBuf) > 0 {
			if len(treepath) > 0 {
				if fnameBuf[0] != '/' || bytes.IndexByte(fnameBuf[1:], '/') >= 0 {
					fnameBuf = fnameBuf[:cap(fnameBuf)]
					continue diffloop
				}
				fnameBuf = fnameBuf[1:]
			} else if bytes.IndexByte(fnameBuf, '/') >= 0 {
				fnameBuf = fnameBuf[:cap(fnameBuf)]
				continue diffloop
			}
		}

		idx, ok := paths2ids[string(fnameBuf)]
		if !ok {
			fnameBuf = fnameBuf[:cap(fnameBuf)]
			continue diffloop
		}
		if ret.Paths == nil {
			ret.Paths = changed
		}
		changed[idx] = true
	}
}

// Close closes the parser
func (g *LogNameStatusRepoParser) Close() {
	g.cancel()
}

// WalkGitLog walks the git log --name-status for the head commit in the provided treepath and files
func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath string, paths ...string) (map[string]string, error) {
	tree, err := head.SubTree(treepath)
	if err != nil {
		return nil, err
	}

	entries, err := tree.ListEntries()
	if err != nil {
		return nil, err
	}

	if len(paths) == 0 {
		paths = make([]string, 0, len(entries)+1)
		paths = append(paths, "")
		for _, entry := range entries {
			paths = append(paths, entry.Name())
		}
	} else {
		sort.Strings(paths)
		if paths[0] != "" {
			paths = append([]string{""}, paths...)
		}
		// remove duplicates
		for i := len(paths) - 1; i > 0; i-- {
			if paths[i] == paths[i-1] {
				paths = append(paths[:i-1], paths[i:]...)
			}
		}
	}

	path2idx := map[string]int{}
	maxpathlen := len(treepath)

	for i := range paths {
		path2idx[paths[i]] = i
		pthlen := len(paths[i]) + len(treepath) + 1
		if pthlen > maxpathlen {
			maxpathlen = pthlen
		}
	}

	g := NewLogNameStatusRepoParser(repo.Path, head.ID.String(), treepath, paths...)
	defer g.Close()

	results := make([]string, len(paths))
	remaining := len(paths)
	nextRestart := (len(paths) * 3) / 4
	if nextRestart > 70 {
		nextRestart = 70
	}
	lastEmptyParent := head.ID.String()
	commitSinceLastEmptyParent := uint64(0)
	commitSinceNextRestart := uint64(0)
	parentRemaining := map[string]bool{}

	changed := make([]bool, len(paths))

heaploop:
	for {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		default:
		}
		current, err := g.Next(treepath, path2idx, changed, maxpathlen)
		if err != nil {
			g.Close()
			return nil, err
		}
		if current == nil {
			break heaploop
		}
		delete(parentRemaining, current.CommitID)
		if current.Paths != nil {
			for i, found := range current.Paths {
				if !found {
					continue
				}
				changed[i] = false
				if results[i] == "" {
					results[i] = current.CommitID
					delete(path2idx, paths[i])
					remaining--
					if results[0] == "" {
						results[0] = current.CommitID
						delete(path2idx, "")
						remaining--
					}
				}
			}
		}

		if remaining <= 0 {
			break heaploop
		}
		commitSinceLastEmptyParent++
		if len(parentRemaining) == 0 {
			lastEmptyParent = current.CommitID
			commitSinceLastEmptyParent = 0
		}
		if remaining <= nextRestart {
			commitSinceNextRestart++
			if 4*commitSinceNextRestart > 3*commitSinceLastEmptyParent {
				g.Close()
				remainingPaths := make([]string, 0, len(paths))
				for i, pth := range paths {
					if results[i] == "" {
						remainingPaths = append(remainingPaths, pth)
					}
				}
				g = NewLogNameStatusRepoParser(repo.Path, lastEmptyParent, treepath, remainingPaths...)
				parentRemaining = map[string]bool{}
				nextRestart = (remaining * 3) / 4
				continue heaploop
			}
		}
		for _, parent := range current.ParentIDs {
			parentRemaining[parent] = true
		}
	}
	g.Close()

	resultsMap := map[string]string{}
	for i, pth := range paths {
		resultsMap[pth] = results[i]
	}

	return resultsMap, nil
}
<|endoftext|>