<commit_before>package consumergroup

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/Shopify/sarama"
)

type partitionConsumer struct {
	owner      *topicConsumer
	group      string
	topic      string
	partition  int32
	offset     int64
	prevOffset int64

	consumer sarama.PartitionConsumer
}

func newPartitionConsumer(owner *topicConsumer, partition int32) *partitionConsumer {
	return &partitionConsumer{
		owner:      owner,
		topic:      owner.name,
		group:      owner.owner.name,
		partition:  partition,
		offset:     0,
		prevOffset: 0,
	}
}

func (pc *partitionConsumer) start() {
	var wg sync.WaitGroup

	cg := pc.owner.owner
	err := pc.claim()
	if err != nil {
		cg.logger.Errorf("Failed to claim topic[%s] partition[%d] and give up, err %s",
			pc.topic, pc.partition, err)
		goto ERROR
	}
	defer func() {
		err = pc.release()
		if err != nil {
			cg.logger.Errorf("Failed to release topic[%s] partition[%d], err %s",
				pc.topic, pc.partition, err)
		} else {
			cg.logger.Infof("Release topic[%s] partition[%d] success", pc.topic, pc.partition)
		}
	}()

	err = pc.loadOffsetFromZk()
	if err != nil {
		cg.logger.Errorf("Failed to load topic[%s] partition[%d] offset from zk, err %s",
			pc.topic, pc.partition, err)
		goto ERROR
	}
	cg.logger.Debugf("Get topic[%s] partition[%d] offset[%d] from offset storage",
		pc.topic, pc.partition, pc.offset)

	pc.consumer, err = cg.getPartitionConsumer(pc.topic, pc.partition, pc.offset)
	if err != nil {
		cg.logger.Errorf("Failed to topic[%s] partition[%d] message, err %s",
			pc.topic, pc.partition, err)
		goto ERROR
	}
	defer pc.consumer.Close()

	if cg.config.OffsetAutoCommitEnable { // start auto commit-offset thread when enable
		wg.Add(1)
		go func() {
			defer cg.callRecover()
			defer wg.Done()
			cg.logger.Infof("Offset auto-commit topic[%s] partition[%d] thread was started",
				pc.topic, pc.partition)
			pc.autoCommitOffset()
		}()
	}

	pc.fetch()
	if cg.config.OffsetAutoCommitEnable {
		err = pc.commitOffset()
		if err != nil {
			cg.logger.Errorf("Failed to commit topic[%s] partition[%d] offset[%d]",
				pc.topic, pc.partition, pc.offset)
		}
		wg.Wait() // Wait for auto-commit-offset thread
		cg.logger.Infof("Offset auto-commit topic[%s] partition[%d] thread was stoped",
			pc.topic, pc.partition)
	}
	return

ERROR:
	cg.stop()
}

func (pc *partitionConsumer) loadOffsetFromZk() error {
	cg := pc.owner.owner
	offset, err := cg.storage.getOffset(pc.group, pc.topic, pc.partition)
	if err != nil {
		return err
	}
	if offset == -1 {
		offset = cg.config.OffsetAutoReset
	}
	pc.offset = offset
	pc.prevOffset = offset
	return nil
}

func (pc *partitionConsumer) claim() error {
	cg := pc.owner.owner
	timer := time.NewTimer(cg.config.ClaimPartitionRetryInterval)
	defer timer.Stop()
	retry := cg.config.ClaimPartitionRetryTimes
	// Claim partition would retry until success
	for i := 0; i < retry+1 || retry <= 0; i++ {
		err := cg.storage.claimPartition(pc.group, pc.topic, pc.partition, cg.id)
		if err == nil {
			return nil
		}
		if i != 0 && (i%3 == 0 || retry > 0) {
			cg.logger.Warnf("Failed to claim topic[%s] partition[%d] after %d retires, err %s",
				pc.topic, pc.partition, i, err)
		}
		select {
		case <-timer.C:
			timer.Reset(cg.config.ClaimPartitionRetryInterval)
		case <-cg.stopper:
			return errors.New("stop signal was received when claim partition")
		}
	}
	return fmt.Errorf("failed to claim partition after %d retries", retry)
}

func (pc *partitionConsumer) release() error {
	cg := pc.owner.owner
	owner, err := cg.storage.getPartitionOwner(pc.group, pc.topic, pc.partition)
	if err != nil {
		return err
	}
	if cg.id == owner {
		return cg.storage.releasePartition(pc.group, pc.topic, pc.partition)
	}
	return errors.New("partition wasn't ownered by this consumergroup")
}

func (pc *partitionConsumer) fetch() {
	cg := pc.owner.owner
	messageChan := pc.owner.messages
	errorChan := pc.owner.errors

PARTITION_CONSUMER_LOOP:
	for {
		select {
		case <-cg.stopper:
			break PARTITION_CONSUMER_LOOP
		case err := <-pc.consumer.Errors():
			errorChan <- err
		case message := <-pc.consumer.Messages():
			if message == nil {
				cg.logger.Errorf("Sarama partition consumer encounter error, the consumer would be exited")
				close(cg.stopper)
				break PARTITION_CONSUMER_LOOP
			}
			select {
			case messageChan <- message:
				pc.offset = message.Offset + 1
			case <-cg.stopper:
				break PARTITION_CONSUMER_LOOP
			}
		}
	}
}

func (pc *partitionConsumer) autoCommitOffset() {
	cg := pc.owner.owner
	defer cg.callRecover()
	timer := time.NewTimer(cg.config.OffsetAutoCommitInterval)
	for {
		select {
		case <-cg.stopper:
			return
		case <-timer.C:
			err := pc.commitOffset()
			if err != nil {
				cg.logger.Errorf("Failed to auto commit topic[%s] partition[%d] offset[%d], err :%s",
					pc.topic, pc.partition, pc.offset, err)
			}
			timer.Reset(cg.config.OffsetAutoCommitInterval)
		}
	}
}

func (pc *partitionConsumer) commitOffset() error {
	cg := pc.owner.owner
	offset := pc.offset
	if pc.prevOffset == offset {
		return nil
	}
	err := cg.storage.commitOffset(pc.group, pc.topic, pc.partition, offset)
	if err != nil {
		return err
	}
	pc.prevOffset = offset
	return nil
}
<commit_msg>MOD: Improve the partition consumer's log<commit_after>package consumergroup

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/Shopify/sarama"
)

type partitionConsumer struct {
	owner      *topicConsumer
	group      string
	topic      string
	partition  int32
	offset     int64
	prevOffset int64

	consumer sarama.PartitionConsumer
}

func newPartitionConsumer(owner *topicConsumer, partition int32) *partitionConsumer {
	return &partitionConsumer{
		owner:      owner,
		topic:      owner.name,
		group:      owner.owner.name,
		partition:  partition,
		offset:     0,
		prevOffset: 0,
	}
}

func (pc *partitionConsumer) start() {
	var wg sync.WaitGroup

	cg := pc.owner.owner
	err := pc.claim()
	if err != nil {
		cg.logger.Errorf("Failed to claim topic[%s] partition[%d] and give up, err %s",
			pc.topic, pc.partition, err)
		goto ERROR
	}
	defer func() {
		err = pc.release()
		if err != nil {
			cg.logger.Errorf("Failed to release topic[%s] partition[%d], err %s",
				pc.topic, pc.partition, err)
		} else {
			cg.logger.Infof("Release topic[%s] partition[%d] success", pc.topic, pc.partition)
		}
	}()

	err = pc.loadOffsetFromZk()
	if err != nil {
		cg.logger.Errorf("Failed to load topic[%s] partition[%d] offset from zk, err %s",
			pc.topic, pc.partition, err)
		goto ERROR
	}
	cg.logger.Debugf("Get topic[%s] partition[%d] offset[%d] from offset storage",
		pc.topic, pc.partition, pc.offset)

	pc.consumer, err = cg.getPartitionConsumer(pc.topic, pc.partition, pc.offset)
	if err != nil {
		cg.logger.Errorf("Failed to get topic[%s] partition[%d] consumer, err %s",
			pc.topic, pc.partition, err)
		goto ERROR
	}
	defer pc.consumer.Close()

	if cg.config.OffsetAutoCommitEnable { // start auto commit-offset thread when enable
		wg.Add(1)
		go func() {
			defer cg.callRecover()
			defer wg.Done()
			cg.logger.Infof("Offset auto-commit topic[%s] partition[%d] thread was started",
				pc.topic, pc.partition)
			pc.autoCommitOffset()
		}()
	}

	pc.fetch()
	if cg.config.OffsetAutoCommitEnable {
		err = pc.commitOffset()
		if err != nil {
			cg.logger.Errorf("Failed to commit topic[%s] partition[%d] offset[%d]",
				pc.topic, pc.partition, pc.offset)
		}
		wg.Wait() // Wait for auto-commit-offset thread
		cg.logger.Infof("Offset auto-commit topic[%s] partition[%d] thread was stoped",
			pc.topic, pc.partition)
	}
	return

ERROR:
	cg.stop()
}

func (pc *partitionConsumer) loadOffsetFromZk() error {
	cg := pc.owner.owner
	offset, err := cg.storage.getOffset(pc.group, pc.topic, pc.partition)
	if err != nil {
		return err
	}
	if offset == -1 {
		offset = cg.config.OffsetAutoReset
	}
	pc.offset = offset
	pc.prevOffset = offset
	return nil
}

func (pc *partitionConsumer) claim() error {
	cg := pc.owner.owner
	timer := time.NewTimer(cg.config.ClaimPartitionRetryInterval)
	defer timer.Stop()
	retry := cg.config.ClaimPartitionRetryTimes
	// Claim partition would retry until success
	for i := 0; i < retry+1 || retry <= 0; i++ {
		err := cg.storage.claimPartition(pc.group, pc.topic, pc.partition, cg.id)
		if err == nil {
			return nil
		}
		if i != 0 && (i%3 == 0 || retry > 0) {
			cg.logger.Warnf("Failed to claim topic[%s] partition[%d] after %d retires, err %s",
				pc.topic, pc.partition, i, err)
		}
		select {
		case <-timer.C:
			timer.Reset(cg.config.ClaimPartitionRetryInterval)
		case <-cg.stopper:
			return errors.New("stop signal was received when claim partition")
		}
	}
	return fmt.Errorf("failed to claim partition after %d retries", retry)
}

func (pc *partitionConsumer) release() error {
	cg := pc.owner.owner
	owner, err := cg.storage.getPartitionOwner(pc.group, pc.topic, pc.partition)
	if err != nil {
		return err
	}
	if cg.id == owner {
		return cg.storage.releasePartition(pc.group, pc.topic, pc.partition)
	}
	return fmt.Errorf("the owner of topic[%s] partition[%d] expected %s, but got %s",
		pc.topic, pc.partition, owner, cg.id)
}

func (pc *partitionConsumer) fetch() {
	cg := pc.owner.owner
	messageChan := pc.owner.messages
	errorChan := pc.owner.errors

	cg.logger.Infof("Start to fetch topic[%s] partition[%d] messages from offset[%d]",
		pc.topic, pc.partition, pc.offset)
PARTITION_CONSUMER_LOOP:
	for {
		select {
		case <-cg.stopper:
			break PARTITION_CONSUMER_LOOP
		case err := <-pc.consumer.Errors():
			errorChan <- err
		case message := <-pc.consumer.Messages():
			if message == nil {
				cg.logger.Errorf("Sarama partition consumer encounter error, the consumer would be exited")
				close(cg.stopper)
				break PARTITION_CONSUMER_LOOP
			}
			select {
			case messageChan <- message:
				pc.offset = message.Offset + 1
			case <-cg.stopper:
				break PARTITION_CONSUMER_LOOP
			}
		}
	}
}

func (pc *partitionConsumer) autoCommitOffset() {
	cg := pc.owner.owner
	defer cg.callRecover()
	timer := time.NewTimer(cg.config.OffsetAutoCommitInterval)
	for {
		select {
		case <-cg.stopper:
			return
		case <-timer.C:
			err := pc.commitOffset()
			if err != nil {
				cg.logger.Errorf("Failed to auto commit topic[%s] partition[%d] offset[%d], err :%s",
					pc.topic, pc.partition, pc.offset, err)
			}
			timer.Reset(cg.config.OffsetAutoCommitInterval)
		}
	}
}

func (pc *partitionConsumer) commitOffset() error {
	cg := pc.owner.owner
	offset := pc.offset
	if pc.prevOffset == offset {
		return nil
	}
	err := cg.storage.commitOffset(pc.group, pc.topic, pc.partition, offset)
	if err != nil {
		return err
	}
	pc.prevOffset = offset
	return nil
}
<|endoftext|>


<commit_before>package par2

import (
	"bufio"
	"bytes"
	"crypto/md5"
	"encoding/binary"
	"errors"
	"fmt"
	"hash"
	"io"
	"math/big"
	"os"
)

type Fileset struct {
	setID     [16]byte
	slicelen  uint64
	complete  bool
	files     map[[16]byte]*File
	checksums map[[16]byte]chksum
}

type File struct {
	Name      string
	length    uint64
	checksums [][16]byte
}

func (f *File) numBlocks(fset *Fileset) int {
	blockcount := int(f.length / fset.slicelen)
	if f.length%fset.slicelen != 0 {
		blockcount++
	}
	return blockcount
}

type chksum struct {
	*File
	blockno int
}

// NewFileset reads r and returns a Fileset that can be used for verification and recovery of the files.
func NewFileset(r io.Reader) *Fileset {
	fset := &Fileset{}
	fset.files = make(map[[16]byte]*File)
	fset.checksums = make(map[[16]byte]chksum)
	bufr := bufio.NewReader(r)
	for {
		hdr, err := readHeader(bufr)
		if err != nil {
			break
		}
		if fset.setID == ([16]byte{}) {
			fset.setID = hdr.setID
		} else if hdr.setID != fset.setID {
			// this is weird and shouldn't happen
			return fset
		}
		switch hdr.typ {
		case typeFileDesc:
			f, id := readFileDesc(hdr, bufr)
			if f == nil {
				continue
			}
			fi, ok := fset.files[id]
			if ok {
				if fi.Name == "" {
					// file was discovered through some means
					// that didn't include the file info, usually IFSC
					// pkt. Fill in the information now that we know what file this is.
					f.checksums = fi.checksums
					fset.files[id] = f
				}
			} else {
				fset.files[id] = f
			}
		case typeIFSC:
			chksums, id := readIFSC(hdr, bufr)
			if chksums == nil {
				continue
			}
			fi, ok := fset.files[id]
			if !ok {
				fi = new(File)
				fset.files[id] = fi
			}
			if fi.checksums == nil {
				fi.checksums = chksums
			}
			for i, chk := range fi.checksums {
				fset.checksums[chk] = chksum{
					File:    fi,
					blockno: i,
				}
			}
		case typeMain:
			slicelen, ids := readMain(hdr, bufr)
			for _, id := range ids {
				if _, ok := fset.files[id]; !ok {
					fset.files[id] = new(File)
				}
			}
			fset.slicelen = slicelen
		default:
		}
	}
	fset.CanVerify()
	return fset
}

// CanVerify returns whether the current fileset can be
// used for verification.
func (f *Fileset) CanVerify() bool {
	if f.complete {
		return true
	}
	if f.slicelen == 0 {
		return false
	}
	for _, file := range f.files {
		if file.Name == "" || file.checksums == nil {
			return false
		}
	}
	f.complete = true
	return true
}

// Verify verifies the files at paths against the fileset.
// It returns a list of matches and how many blocks are needed in order to repair.
func (f *Fileset) Verify(paths []string) ([]*FileMatch, int) {
	if !f.complete {
		return nil, 0
	}
	files := make(map[*File]struct{}, len(f.files))
	for _, v := range f.files {
		files[v] = struct{}{}
	}
	matches := make([]*FileMatch, 0, len(paths))
	blocksNeeded := 0
	for _, s := range paths {
		fm, blocksmissing := f.verifyfile(s)
		if fm != nil && fm.File != nil {
			delete(files, fm.File)
			blocksNeeded += blocksmissing
			matches = append(matches, fm)
		}
	}
	for fi := range files {
		matches = append(matches, &FileMatch{Err: ErrMissing, File: fi})
		blocksNeeded += fi.numBlocks(f)
	}
	return matches, blocksNeeded
}

var ErrMissing = errors.New("par2: file missing")

func (fset *Fileset) verifyfile(s string) (*FileMatch, int) {
	file, err := os.Open(s)
	if err != nil {
		return &FileMatch{Err: err}, 0
	}
	defer file.Close()

	match := &FileMatch{}
	for {
		mdchk := md5.New()
		n, err := io.CopyN(mdchk, file, int64(fset.slicelen))
		if n == 0 {
			break
		}
		if uint64(n) < fset.slicelen {
			// we have a partial block. par2 spec says that we should
			// fill the remainder with 0s
			// Ugh.
			for i := uint64(0); i < fset.slicelen-uint64(n); i++ {
				// can't fail.
				mdchk.Write(zero)
			}
		}
		var md5sum [16]byte
		mdchk.Sum(md5sum[:0])
		if f, ok := fset.checksums[md5sum]; ok {
			if match.File == nil {
				// ok we have a match, init the block bitmap
				match.blocks = &big.Int{}
				match.File = f.File
				match.Path = s
			} else if match.File != f.File {
				// we already decided on one file and now we have
				// another file with the same block.
				//
				// Effort.
				continue
			}
			match.blocks.SetBit(match.blocks, f.blockno, 1)
		}
		if err != nil {
			break
		}
	}
	if match.File == nil {
		// not part of the recovery set.
		return nil, 0
	}
	blockcount := match.File.numBlocks(fset)
	blocksmissing := 0
	for i := 0; i < blockcount; i++ {
		if match.blocks.Bit(i) == 0 {
			blocksmissing++
		}
	}
	return match, blocksmissing
}

type FileMatch struct {
	Err    error
	Path   string
	File   *File
	blocks *big.Int
}

type hdr struct {
	length      uint64
	hash        [16]byte
	setID       [16]byte
	typ         typ
	partialhash hash.Hash
}

func (h *hdr) String() string {
	return fmt.Sprintf("{\n\t%d\n\t%x\n\t%x\n\t%s\n}", h.length, h.hash, h.setID, h.typ)
}

func readHeader(r *bufio.Reader) (h hdr, err error) {
	err = findHeader(r)
	if err != nil {
		return h, err
	}
	defer func() {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
	}()
	var buf [56]byte
	b := buf[:]
	_, err = io.ReadFull(r, b)
	if err != nil {
		return h, err
	}
	h.length, b = readint(b)
	// for convenience, turn the length into
	// length after the header has been read.
	h.length -= 64
	h.hash, b = readmd5(b)

	// create a partial hash so that we can create a hash of the
	// entire packet.
	h.partialhash = md5.New()
	h.partialhash.Write(b)

	h.setID, b = readmd5(b)

	// i know, it's not an md5sum
	typbuf, b := readmd5(b)
	switch typbuf {
	case magicMain:
		h.typ = typeMain
	case magicFiledesc:
		h.typ = typeFileDesc
	case magicIFSC:
		h.typ = typeIFSC
	case magicRecvSlic:
		h.typ = typeRecvSlic
	case magicCreator:
		h.typ = typeCreator
	default:
		h.typ = typeUnknown
	}

	return h, nil
}

// These are all the required types.
// Going to leave it at that
var (
	magicMain     = [16]byte{'P', 'A', 'R', ' ', '2', '.', '0', 0, 'M', 'a', 'i', 'n', 0, 0, 0, 0}
	magicFiledesc = [16]byte{'P', 'A', 'R', ' ', '2', '.', '0', 0, 'F', 'i', 'l', 'e', 'D', 'e', 's', 'c'}
	magicIFSC     = [16]byte{'P', 'A', 'R', ' ', '2', '.', '0', 0, 'I', 'F', 'S', 'C', 0, 0, 0, 0}
	magicRecvSlic = [16]byte{'P', 'A', 'R', ' ', '2', '.', '0', 0, 'R', 'e', 'c', 'v', 'S', 'l', 'i', 'c'}
	magicCreator  = [16]byte{'P', 'A', 'R', ' ', '2', '.', '0', 0, 'C', 'r', 'e', 'a', 't', 'o', 'r', 0}
)

// corruption might cause the header
// to become unaligned, so just search for the damn
// header.
func findHeader(r *bufio.Reader) error {
	i := 0
	str := "PAR2\x00PKT"
	for {
		//when completely matched
		if i == len(str) {
			return nil
		}
		c, err := r.ReadByte()
		if err != nil {
			return err
		}
		if str[i] == c {
			i++
			continue
		}
		i = 0
		if str[0] == c {
			i++
		}
	}
}

func readFileDesc(h hdr, r *bufio.Reader) (f *File, id [16]byte) {
	buf, err := readPkt(h, r)
	if err != nil {
		return nil, id
	}
	f = new(File)
	id, buf = readmd5(buf)
	_, buf = readmd5(buf)
	_, buf = readmd5(buf)
	f.length, buf = readint(buf)

	// rest of block is name, trim 0 padding.
	i := bytes.LastIndex(buf, zero)
	if i != -1 {
		buf = buf[:i]
	}
	f.Name = string(buf)
	return f, id
}

var zero = []byte{0}

func readIFSC(h hdr, r *bufio.Reader) (ss [][16]byte, id [16]byte) {
	buf, err := readPkt(h, r)
	if err != nil {
		return nil, id
	}
	id, buf = readmd5(buf)
	ss = make([][16]byte, 0, len(buf)/20)
	for len(buf) > 0 {
		var md5h [16]byte
		md5h, buf = readmd5(buf)
		// don't care about the crc, just that it gets consumed
		_, buf = readcrc(buf)
		ss = append(ss, md5h)
	}
	return ss, id
}

func readMain(h hdr, r *bufio.Reader) (slicesize uint64, ids [][16]byte) {
	buf, err := readPkt(h, r)
	if err != nil {
		return 0, nil
	}
	slicesize, buf = readint(buf)
	numfiles, buf := readcrc(buf)
	ids = make([][16]byte, 0, numfiles)
	for i := uint32(0); i < numfiles; i++ {
		var nid [16]byte
		nid, buf = readmd5(buf)
		ids = append(ids, nid)
	}

	return slicesize, ids
}

func readmd5(b []byte) ([16]byte, []byte) {
	var ret [16]byte
	copy(ret[:], b[:16])
	b = b[16:]
	return ret, b
}

func readint(b []byte) (uint64, []byte) {
	ret := binary.LittleEndian.Uint64(b)
	b = b[8:]
	return ret, b
}

func readcrc(b []byte) (uint32, []byte) {
	ret := binary.LittleEndian.Uint32(b)
	b = b[4:]
	return ret, b
}

// to be used for packets that can be read in a single go. Verifies
// the md5 sum of the packet as well
func readPkt(h hdr, r *bufio.Reader) ([]byte, error) {
	buf := make([]byte, h.length)
	_, err := io.ReadFull(r, buf)
	if err != nil {
		return nil, err
	}
	// check if the packet is valid
	h.partialhash.Write(buf)
	hash := h.partialhash.Sum(nil)
	for i, b := range h.hash {
		if hash[i] != b {
			return nil, errors.New("mismatch packet md5 and packet contents")
		}
	}
	return buf, nil
}
<commit_msg>[par2] fill in name and length instead of copying checksums<commit_after>package par2

import (
	"bufio"
	"bytes"
	"crypto/md5"
	"encoding/binary"
	"errors"
	"fmt"
	"hash"
	"io"
	"math/big"
	"os"
)

type Fileset struct {
	setID     [16]byte
	slicelen  uint64
	complete  bool
	files     map[[16]byte]*File
	checksums map[[16]byte]chksum
}

type File struct {
	Name      string
	length    uint64
	checksums [][16]byte
}

func (f *File) numBlocks(fset *Fileset) int {
	blockcount := int(f.length / fset.slicelen)
	if f.length%fset.slicelen != 0 {
		blockcount++
	}
	return blockcount
}

type chksum struct {
	*File
	blockno int
}

// NewFileset reads r and returns a Fileset that can be used for verification and recovery of the files.
func NewFileset(r io.Reader) *Fileset {
	fset := &Fileset{}
	fset.files = make(map[[16]byte]*File)
	fset.checksums = make(map[[16]byte]chksum)
	bufr := bufio.NewReader(r)
	for {
		hdr, err := readHeader(bufr)
		if err != nil {
			break
		}
		if fset.setID == ([16]byte{}) {
			fset.setID = hdr.setID
		} else if hdr.setID != fset.setID {
			// this is weird and shouldn't happen
			return fset
		}
		switch hdr.typ {
		case typeFileDesc:
			f, id := readFileDesc(hdr, bufr)
			if f == nil {
				continue
			}
			fi, ok := fset.files[id]
			if ok {
				if fi.Name == "" {
					// file was discovered through some means
					// that didn't include the file info, usually IFSC
					// pkt. Fill in the information now that we know what file this is.
					fi.Name = f.Name
					fi.length = fi.length
				}
			} else {
				fset.files[id] = f
			}
		case typeIFSC:
			chksums, id := readIFSC(hdr, bufr)
			if chksums == nil {
				continue
			}
			fi, ok := fset.files[id]
			if !ok {
				fi = new(File)
				fset.files[id] = fi
			}
			if fi.checksums == nil {
				fi.checksums = chksums
			}
			for i, chk := range fi.checksums {
				fset.checksums[chk] = chksum{
					File:    fi,
					blockno: i,
				}
			}
		case typeMain:
			slicelen, ids := readMain(hdr, bufr)
			for _, id := range ids {
				if _, ok := fset.files[id]; !ok {
					fset.files[id] = new(File)
				}
			}
			fset.slicelen = slicelen
		default:
		}
	}
	fset.CanVerify()
	return fset
}

// CanVerify returns whether the current fileset can be
// used for verification.
func (f *Fileset) CanVerify() bool {
	if f.complete {
		return true
	}
	if f.slicelen == 0 {
		return false
	}
	for _, file := range f.files {
		if file.Name == "" || file.checksums == nil {
			return false
		}
	}
	f.complete = true
	return true
}

// Verify verifies the files at paths against the fileset.
// It returns a list of matches and how many blocks are needed in order to repair.
func (f *Fileset) Verify(paths []string) ([]*FileMatch, int) {
	if !f.complete {
		return nil, 0
	}
	files := make(map[*File]struct{}, len(f.files))
	for _, v := range f.files {
		files[v] = struct{}{}
	}
	matches := make([]*FileMatch, 0, len(paths))
	blocksNeeded := 0
	for _, s := range paths {
		fm, blocksmissing := f.verifyfile(s)
		if fm != nil && fm.File != nil {
			delete(files, fm.File)
			blocksNeeded += blocksmissing
			matches = append(matches, fm)
		}
	}
	for fi := range files {
		matches = append(matches, &FileMatch{Err: ErrMissing, File: fi})
		blocksNeeded += fi.numBlocks(f)
	}
	return matches, blocksNeeded
}

var ErrMissing = errors.New("par2: file missing")

func (fset *Fileset) verifyfile(s string) (*FileMatch, int) {
	file, err := os.Open(s)
	if err != nil {
		return &FileMatch{Err: err}, 0
	}
	defer file.Close()

	match := &FileMatch{}
	for {
		mdchk := md5.New()
		n, err := io.CopyN(mdchk, file, int64(fset.slicelen))
		if n == 0 {
			break
		}
		if uint64(n) < fset.slicelen {
			// we have a partial block. par2 spec says that we should
			// fill the remainder with 0s
			// Ugh.
			for i := uint64(0); i < fset.slicelen-uint64(n); i++ {
				// can't fail.
				mdchk.Write(zero)
			}
		}
		var md5sum [16]byte
		mdchk.Sum(md5sum[:0])
		if f, ok := fset.checksums[md5sum]; ok {
			if match.File == nil {
				// ok we have a match, init the block bitmap
				match.blocks = &big.Int{}
				match.File = f.File
				match.Path = s
			} else if match.File != f.File {
				// we already decided on one file and now we have
				// another file with the same block.
				//
				// Effort.
				continue
			}
			match.blocks.SetBit(match.blocks, f.blockno, 1)
		}
		if err != nil {
			break
		}
	}
	if match.File == nil {
		// not part of the recovery set.
		return nil, 0
	}
	blockcount := match.File.numBlocks(fset)
	blocksmissing := 0
	for i := 0; i < blockcount; i++ {
		if match.blocks.Bit(i) == 0 {
			blocksmissing++
		}
	}
	return match, blocksmissing
}

type FileMatch struct {
	Err    error
	Path   string
	File   *File
	blocks *big.Int
}

type hdr struct {
	length      uint64
	hash        [16]byte
	setID       [16]byte
	typ         typ
	partialhash hash.Hash
}

func (h *hdr) String() string {
	return fmt.Sprintf("{\n\t%d\n\t%x\n\t%x\n\t%s\n}", h.length, h.hash, h.setID, h.typ)
}

func readHeader(r *bufio.Reader) (h hdr, err error) {
	err = findHeader(r)
	if err != nil {
		return h, err
	}
	defer func() {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
	}()
	var buf [56]byte
	b := buf[:]
	_, err = io.ReadFull(r, b)
	if err != nil {
		return h, err
	}
	h.length, b = readint(b)
	// for convenience, turn the length into
	// length after the header has been read.
	h.length -= 64
	h.hash, b = readmd5(b)

	// create a partial hash so that we can create a hash of the
	// entire packet.
	h.partialhash = md5.New()
	h.partialhash.Write(b)

	h.setID, b = readmd5(b)

	// i know, it's not an md5sum
	typbuf, b := readmd5(b)
	switch typbuf {
	case magicMain:
		h.typ = typeMain
	case magicFiledesc:
		h.typ = typeFileDesc
	case magicIFSC:
		h.typ = typeIFSC
	case magicRecvSlic:
		h.typ = typeRecvSlic
	case magicCreator:
		h.typ = typeCreator
	default:
		h.typ = typeUnknown
	}

	return h, nil
}

// These are all the required types.
// Going to leave it at that
var (
	magicMain     = [16]byte{'P', 'A', 'R', ' ', '2', '.', '0', 0, 'M', 'a', 'i', 'n', 0, 0, 0, 0}
	magicFiledesc = [16]byte{'P', 'A', 'R', ' ', '2', '.', '0', 0, 'F', 'i', 'l', 'e', 'D', 'e', 's', 'c'}
	magicIFSC     = [16]byte{'P', 'A', 'R', ' ', '2', '.', '0', 0, 'I', 'F', 'S', 'C', 0, 0, 0, 0}
	magicRecvSlic = [16]byte{'P', 'A', 'R', ' ', '2', '.', '0', 0, 'R', 'e', 'c', 'v', 'S', 'l', 'i', 'c'}
	magicCreator  = [16]byte{'P', 'A', 'R', ' ', '2', '.', '0', 0, 'C', 'r', 'e', 'a', 't', 'o', 'r', 0}
)

// corruption might cause the header
// to become unaligned, so just search for the damn
// header.
func findHeader(r *bufio.Reader) error {
	i := 0
	str := "PAR2\x00PKT"
	for {
		//when completely matched
		if i == len(str) {
			return nil
		}
		c, err := r.ReadByte()
		if err != nil {
			return err
		}
		if str[i] == c {
			i++
			continue
		}
		i = 0
		if str[0] == c {
			i++
		}
	}
}

func readFileDesc(h hdr, r *bufio.Reader) (f *File, id [16]byte) {
	buf, err := readPkt(h, r)
	if err != nil {
		return nil, id
	}
	f = new(File)
	id, buf = readmd5(buf)
	_, buf = readmd5(buf)
	_, buf = readmd5(buf)
	f.length, buf = readint(buf)

	// rest of block is name, trim 0 padding.
	i := bytes.LastIndex(buf, zero)
	if i != -1 {
		buf = buf[:i]
	}
	f.Name = string(buf)
	return f, id
}

var zero = []byte{0}

func readIFSC(h hdr, r *bufio.Reader) (ss [][16]byte, id [16]byte) {
	buf, err := readPkt(h, r)
	if err != nil {
		return nil, id
	}
	id, buf = readmd5(buf)
	ss = make([][16]byte, 0, len(buf)/20)
	for len(buf) > 0 {
		var md5h [16]byte
		md5h, buf = readmd5(buf)
		// don't care about the crc, just that it gets consumed
		_, buf = readcrc(buf)
		ss = append(ss, md5h)
	}
	return ss, id
}

func readMain(h hdr, r *bufio.Reader) (slicesize uint64, ids [][16]byte) {
	buf, err := readPkt(h, r)
	if err != nil {
		return 0, nil
	}
	slicesize, buf = readint(buf)
	numfiles, buf := readcrc(buf)
	ids = make([][16]byte, 0, numfiles)
	for i := uint32(0); i < numfiles; i++ {
		var nid [16]byte
		nid, buf = readmd5(buf)
		ids = append(ids, nid)
	}

	return slicesize, ids
}

func readmd5(b []byte) ([16]byte, []byte) {
	var ret [16]byte
	copy(ret[:], b[:16])
	b = b[16:]
	return ret, b
}

func readint(b []byte) (uint64, []byte) {
	ret := binary.LittleEndian.Uint64(b)
	b = b[8:]
	return ret, b
}

func readcrc(b []byte) (uint32, []byte) {
	ret := binary.LittleEndian.Uint32(b)
	b = b[4:]
	return ret, b
}

// to be used for packets that can be read in a single go. Verifies
// the md5 sum of the packet as well
func readPkt(h hdr, r *bufio.Reader) ([]byte, error) {
	buf := make([]byte, h.length)
	_, err := io.ReadFull(r, buf)
	if err != nil {
		return nil, err
	}
	// check if the packet is valid
	h.partialhash.Write(buf)
	hash := h.partialhash.Sum(nil)
	for i, b := range h.hash {
		if hash[i] != b {
			return nil, errors.New("mismatch packet md5 and packet contents")
		}
	}
	return buf, nil
}
<|endoftext|>


<commit_before>// go-rst - A reStructuredText parser for Go
// 2014 (c) The go-rst Authors
// MIT Licensed. See LICENSE for details.

package parse

import (
	"github.com/demizer/go-elog"
	"strings"
	"unicode"
	"unicode/utf8"
)

// The line number of an item in the input string
type Line int

func (l Line) LineNumber() Line { return l }

// The begining location of an item in the input.
type StartPosition int

func (s StartPosition) Position() StartPosition { return s }

// itemElement are the types that are emitted by the lexer.
type itemElement int

const (
	itemEOF itemElement = iota
	itemError
	itemTitle
	itemSectionAdornment
	itemParagraph
	itemBlockquote
	itemLiteralBlock
	itemSystemMessage
	itemSpace
	itemBlankLine
)

var elements = [...]string{
	"itemEOF",
	"itemError",
	"itemTitle",
	"itemSectionAdornment",
	"itemParagraph",
	"itemBlockquote",
	"itemLiteralBlock",
	"itemSystemMessage",
	"itemSpace",
	"itemBlankLine",
}

// String implements the Stringer interface for printing itemElement types.
func (t itemElement) String() string { return elements[t] }

func (t *itemElement) UnmarshalJSON(data []byte) error {
	for num, elm := range elements {
		if elm == string(data[1:len(data)-1]) {
			*t = itemElement(num)
		}
	}
	return nil
}

// Valid section adornment runes
var sectionAdornments = []rune{'!', '"', '#', '$', '\'', '%', '&', '(', ')', '*',
	'+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\',
	']', '^', '_', '`', '{', '|', '}', '~'}

// Emitted by the lexer on End of File
const eof rune = -1

// Function prototype for scanner functions
type stateFn func(*lexer) stateFn

// Struct for tokens emitted by the scanning process
type item struct {
	Type          itemElement `json:"type"`
	Id            int         `json:"id"`
	Length        int         `json:"length"`
	Text          interface{} `json:"text"`
	Line          `json:"line"`
	StartPosition `json:"startPosition"`
}

// The lexer struct tracks the state of the lexer
type lexer struct {
	name             string    // The name of the current lexer
	input            string    // The input text
	state            stateFn   // The current state of the lexer
	index            int       // Position in input
	start            int       // The start of the current token
	width            int       // The width of the current position
	items            chan item // The channel items are emitted to
	lastItem         *item     // The last item emitted to the channel
	lastItemPosition StartPosition
	id               int // Unique id for each item emitted
}

// lex is the entry point of the lexer
func lex(name, input string) *lexer {
	log.Debugln("Start")
	l := &lexer{
		name:  name,
		input: input,
		items: make(chan item),
	}
	go l.run()
	log.Debugln("End")
	return l
}

// run is the engine of the lexing process.
func (l *lexer) run() {
	log.Debugln("Start")
	for l.state = lexStart; l.state != nil; {
		l.state = l.state(l)
	}
	log.Debugln("End")
}

// emit passes an item back to the client.
func (l *lexer) emit(t itemElement) {
	if l.start == l.index && int(l.index) < len(l.input) {
		l.index += 1
	}
	log.Debugf("#### %s: %q start: %d pos: %d line: %d\n", t,
		l.input[l.start:l.index], l.start, l.index, l.lineNumber())
	l.id++
	nItem := item{
		Type:          t,
		Id:	       l.id,
		Text:          l.input[l.start:l.index],
		Line:          Line(l.lineNumber()),
		StartPosition: StartPosition(l.start + 1),
		Length:        len(l.input[l.start:l.index]),
	}
	l.items <- nItem
	l.lastItem = &nItem
	l.start = l.index
}

// backup backs up the lexer by one position using the width of the last rune retrieved from the
// input.
func (l *lexer) backup() {
	l.index -= l.width
}

// current returns the rune at the current position in the input.
func (l *lexer) current() rune {
	r, _ := utf8.DecodeRuneInString(l.input[l.index:])
	return r
}

// peek looks ahead in the input by one position and returns the rune.
func (l *lexer) peek() rune {
	r := l.next()
	l.backup()
	return r
}

// advance fast forwards the lexer to the next rune in the input specified by "to".
func (l *lexer) advance(to rune) {
	for {
		if l.next() == eof || to == l.current() {
			break
		}
	}
}

// next advances the position of the lexer by one rune and returns that rune.
func (l *lexer) next() rune {
	if int(l.index) >= len(l.input) {
		log.Debugln("Reached eof!")
		l.width = 0
		return eof
	}
	r, w := utf8.DecodeRuneInString(l.input[l.index:])
	l.width = w
	l.index += l.width
	log.Debugf("cur: %q start: %d pos: %d\n", r, l.start, l.index)
	return r
}

// nextItem returns the next item from the input.
func (l *lexer) nextItem() item {
	item := <-l.items
	l.lastItemPosition = item.StartPosition
	return item

}

func (l *lexer) lineNumber() int {
	return strings.Count(l.input[:l.index-1], "\n") + 1
}

// isSpace reports whether r is a space character.
func isSpace(r rune) bool {
	return r == ' ' || r == '\t' || r == '\n' || r == '\r'
}

// isEndOfLine reports whether r is an end-of-line character.
func isEndOfLine(r rune) bool {
	return r == '\r' || r == '\n'
}

// isSection compares a number of positions (skipping whitespace) to
// determine if the runes are sectionAdornments and returns a true if the
// positions match each other. Rune comparison begins at the current lexer
// position. isSection returns false if there is a blank line between the
// positions or if there is a rune mismatch between positions.
func isSection(l *lexer) bool {
	log.Debugln("Start")
	var lookPositions = 2
	var lastAdornment rune
	var newLineNum int
	var runePositions []rune
	var matchCount int
	cPos := l.index

	exit := func(value bool) bool {
		l.index = cPos
		log.Debugln("Returning", value)
		return value
	}

	log.Debugln("Looking ahead", lookPositions, "position(s) for sectionAdornments...")

	// Check two runes to see if they are section adornments, if not, we will check the next
	// line (after whitespace).
	if isSectionAdornment(l.current()) && isSectionAdornment(l.peek()) &&
		l.current() == l.peek() {
		return true
	}

	// Advance to the end of the line
	l.advance('\n')
	if l.current() == eof {
		return false
	}

	for j := 0; j < lookPositions; j++ {
		for isSpace(l.current()) {
			if isEndOfLine(l.current()) {
				if newLineNum == 1 {
					log.Debugln("Too many newlines!")
					return exit(false)
				}
				newLineNum += 1
				log.Debugln("newLineNum:", newLineNum)
			}
			l.next()
		}

		if isSectionAdornment(l.current()) {
			log.Debugf("Found adornment: \"%s\" pos: %d\n", string(l.current()), l.index)
			if lastAdornment != 0 && l.current() != lastAdornment {
				log.Debugf("Adornment mismatch, last: %s current: %s\n",
					string(lastAdornment), string(l.current()))
				return exit(false)
			}
			runePositions = append(runePositions, l.current())
			lastAdornment = l.current()
			matchCount += 1
		}
		l.next()
	}

	if len(runePositions) == 0 || matchCount != lookPositions {
		return exit(false)
	}

	return exit(true)
}

// isSectionAdornment returns true if r matches a section adornment.
func isSectionAdornment(r rune) bool {
	for _, a := range sectionAdornments {
		if a == r {
			return true
		}
	}
	return false
}

// lexStart is the first stateFn called by run(). From here other stateFn's are called depending on
// the input. When this function returns nil, the lexing is finished and run() will exit.
func lexStart(l *lexer) stateFn {
	log.Debugln("Start")
	for {
		var tokenLength = l.index - l.start
		switch r := l.current(); {
		case tokenLength == 1:
			log.Debugln("tokenLength == 1; Start of new token")
			if isEndOfLine(r) {
				l.emit(itemBlankLine)
				l.start += 1
			} else if isSpace(r) {
				return lexSpace
			} else if isSection(l) {
				return lexSection
			}
		case isEndOfLine(r):
			log.Debugln("isEndOfLine == true")
			if l.index > l.start {
				l.emit(itemParagraph)
				l.start += 1 // Skip the new line
			} else if l.start == l.index {
				l.emit(itemBlankLine)
			}

		}
		if l.next() == eof {
			break
		}
	}

	// Correctly reached eof.
	if l.index > l.start {
		l.emit(itemParagraph)
	}

	l.emit(itemEOF)
	log.Debugln("End")
	return nil
}

// lexSpace consumes space characters (space and tab) in the input and emits a itemSpace token.
func lexSpace(l *lexer) stateFn {
	log.Debugln("Start")
	for isSpace(l.current()) {
		l.next()
	}
	if l.start < l.index {
		l.emit(itemSpace)
		l.next()
	}
	log.Debugln("End")
	return lexStart
}

// lexSection is used after isSection() has determined that the next runes of input are section.
// From here, the lexTitle() and lexSectionAdornment() are called based on the input.
func lexSection(l *lexer) stateFn {
	log.Debugln("Start")
	// The order of the case statement matter here
	switch r := l.next(); {
	case isSectionAdornment(r):
		if l.lastItem.Type != itemTitle {
			return lexSectionAdornment
		}
		lexSectionAdornment(l)
	case isSpace(r):
		return lexSpace
	case r <= unicode.MaxASCII && unicode.IsPrint(r):
		return lexTitle
	case isEndOfLine(r):
		l.start += 1
	}
	log.Debugln("Exit")
	return lexStart
}

// lexTitle consumes input until newline and emits an itemTitle token. Once the token is emitted,
// control is returned to lexSection().
func lexTitle(l *lexer) stateFn {
	log.Debugln("Start")
	for {
		l.next()
		if l.peek() == '\n' {
			l.emit(itemTitle)
			l.start += 1
			l.index += 1
			break
		}
	}
	log.Debugln("End")
	return lexSection
}

// lexSectionAdornment advances the lexer until a newline is encountered and emits a
// itemSectionAdornment token. Control is returned to lexSection() on completion.
func lexSectionAdornment(l *lexer) stateFn {
	log.Debugln("Start")
	for {
		//TODO: Add adornment rune check
		l.next()
		if l.peek() == '\n' {
			l.emit(itemSectionAdornment)
			l.start += 1
			l.index += 1
			break
		}
	}
	log.Debugln("End")
	return lexSection
}
<commit_msg>lex.go: Reorder lex.item fields<commit_after>// go-rst - A reStructuredText parser for Go
// 2014 (c) The go-rst Authors
// MIT Licensed. See LICENSE for details.

package parse

import (
	"github.com/demizer/go-elog"
	"strings"
	"unicode"
	"unicode/utf8"
)

// The line number of an item in the input string
type Line int

func (l Line) LineNumber() Line { return l }

// The begining location of an item in the input.
type StartPosition int

func (s StartPosition) Position() StartPosition { return s }

// itemElement are the types that are emitted by the lexer.
type itemElement int

const (
	itemEOF itemElement = iota
	itemError
	itemTitle
	itemSectionAdornment
	itemParagraph
	itemBlockquote
	itemLiteralBlock
	itemSystemMessage
	itemSpace
	itemBlankLine
)

var elements = [...]string{
	"itemEOF",
	"itemError",
	"itemTitle",
	"itemSectionAdornment",
	"itemParagraph",
	"itemBlockquote",
	"itemLiteralBlock",
	"itemSystemMessage",
	"itemSpace",
	"itemBlankLine",
}

// String implements the Stringer interface for printing itemElement types.
func (t itemElement) String() string { return elements[t] }

func (t *itemElement) UnmarshalJSON(data []byte) error {
	for num, elm := range elements {
		if elm == string(data[1:len(data)-1]) {
			*t = itemElement(num)
		}
	}
	return nil
}

// Valid section adornment runes
var sectionAdornments = []rune{'!', '"', '#', '$', '\'', '%', '&', '(', ')', '*',
	'+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\',
	']', '^', '_', '`', '{', '|', '}', '~'}

// Emitted by the lexer on End of File
const eof rune = -1

// Function prototype for scanner functions
type stateFn func(*lexer) stateFn

// Struct for tokens emitted by the scanning process
type item struct {
	Id            int         `json:"id"`
	Type          itemElement `json:"type"`
	Text          interface{} `json:"text"`
	Line          `json:"line"`
	StartPosition `json:"startPosition"`
	Length        int `json:"length"`
}

// The lexer struct tracks the state of the lexer
type lexer struct {
	name             string    // The name of the current lexer
	input            string    // The input text
	state            stateFn   // The current state of the lexer
	index            int       // Position in input
	start            int       // The start of the current token
	width            int       // The width of the current position
	items            chan item // The channel items are emitted to
	lastItem         *item     // The last item emitted to the channel
	lastItemPosition StartPosition
	id               int // Unique id for each item emitted
}

// lex is the entry point of the lexer
func lex(name, input string) *lexer {
	log.Debugln("Start")
	l := &lexer{
		name:  name,
		input: input,
		items: make(chan item),
	}
	go l.run()
	log.Debugln("End")
	return l
}

// run is the engine of the lexing process.
func (l *lexer) run() {
	log.Debugln("Start")
	for l.state = lexStart; l.state != nil; {
		l.state = l.state(l)
	}
	log.Debugln("End")
}

// emit passes an item back to the client.
func (l *lexer) emit(t itemElement) {
	if l.start == l.index && int(l.index) < len(l.input) {
		l.index += 1
	}
	log.Debugf("#### %s: %q start: %d pos: %d line: %d\n", t,
		l.input[l.start:l.index], l.start, l.index, l.lineNumber())
	l.id++
	nItem := item{
		Id:            l.id,
		Type:          t,
		Text:          l.input[l.start:l.index],
		Line:          Line(l.lineNumber()),
		StartPosition: StartPosition(l.start + 1),
		Length:        len(l.input[l.start:l.index]),
	}
	l.items <- nItem
	l.lastItem = &nItem
	l.start = l.index
}

// backup backs up the lexer by one position using the width of the last rune retrieved from the
// input.
func (l *lexer) backup() {
	l.index -= l.width
}

// current returns the rune at the current position in the input.
func (l *lexer) current() rune {
	r, _ := utf8.DecodeRuneInString(l.input[l.index:])
	return r
}

// peek looks ahead in the input by one position and returns the rune.
func (l *lexer) peek() rune {
	r := l.next()
	l.backup()
	return r
}

// advance fast forwards the lexer to the next rune in the input specified by "to".
func (l *lexer) advance(to rune) {
	for {
		if l.next() == eof || to == l.current() {
			break
		}
	}
}

// next advances the position of the lexer by one rune and returns that rune.
func (l *lexer) next() rune {
	if int(l.index) >= len(l.input) {
		log.Debugln("Reached eof!")
		l.width = 0
		return eof
	}
	r, w := utf8.DecodeRuneInString(l.input[l.index:])
	l.width = w
	l.index += l.width
	log.Debugf("cur: %q start: %d pos: %d\n", r, l.start, l.index)
	return r
}

// nextItem returns the next item from the input.
func (l *lexer) nextItem() item {
	item := <-l.items
	l.lastItemPosition = item.StartPosition
	return item

}

func (l *lexer) lineNumber() int {
	return strings.Count(l.input[:l.index-1], "\n") + 1
}

// isSpace reports whether r is a space character.
func isSpace(r rune) bool {
	return r == ' ' || r == '\t' || r == '\n' || r == '\r'
}

// isEndOfLine reports whether r is an end-of-line character.
func isEndOfLine(r rune) bool {
	return r == '\r' || r == '\n'
}

// isSection compares a number of positions (skipping whitespace) to
// determine if the runes are sectionAdornments and returns a true if the
// positions match each other. Rune comparison begins at the current lexer
// position. isSection returns false if there is a blank line between the
// positions or if there is a rune mismatch between positions.
func isSection(l *lexer) bool {
	log.Debugln("Start")
	var lookPositions = 2
	var lastAdornment rune
	var newLineNum int
	var runePositions []rune
	var matchCount int
	cPos := l.index

	exit := func(value bool) bool {
		l.index = cPos
		log.Debugln("Returning", value)
		return value
	}

	log.Debugln("Looking ahead", lookPositions, "position(s) for sectionAdornments...")

	// Check two runes to see if they are section adornments, if not, we will check the next
	// line (after whitespace).
	if isSectionAdornment(l.current()) && isSectionAdornment(l.peek()) &&
		l.current() == l.peek() {
		return true
	}

	// Advance to the end of the line
	l.advance('\n')
	if l.current() == eof {
		return false
	}

	for j := 0; j < lookPositions; j++ {
		for isSpace(l.current()) {
			if isEndOfLine(l.current()) {
				if newLineNum == 1 {
					log.Debugln("Too many newlines!")
					return exit(false)
				}
				newLineNum += 1
				log.Debugln("newLineNum:", newLineNum)
			}
			l.next()
		}

		if isSectionAdornment(l.current()) {
			log.Debugf("Found adornment: \"%s\" pos: %d\n", string(l.current()), l.index)
			if lastAdornment != 0 && l.current() != lastAdornment {
				log.Debugf("Adornment mismatch, last: %s current: %s\n",
					string(lastAdornment), string(l.current()))
				return exit(false)
			}
			runePositions = append(runePositions, l.current())
			lastAdornment = l.current()
			matchCount += 1
		}
		l.next()
	}

	if len(runePositions) == 0 || matchCount != lookPositions {
		return exit(false)
	}

	return exit(true)
}

// isSectionAdornment returns true if r matches a section adornment.
func isSectionAdornment(r rune) bool {
	for _, a := range sectionAdornments {
		if a == r {
			return true
		}
	}
	return false
}

// lexStart is the first stateFn called by run(). From here other stateFn's are called depending on
// the input. When this function returns nil, the lexing is finished and run() will exit.
func lexStart(l *lexer) stateFn {
	log.Debugln("Start")
	for {
		var tokenLength = l.index - l.start
		switch r := l.current(); {
		case tokenLength == 1:
			log.Debugln("tokenLength == 1; Start of new token")
			if isEndOfLine(r) {
				l.emit(itemBlankLine)
				l.start += 1
			} else if isSpace(r) {
				return lexSpace
			} else if isSection(l) {
				return lexSection
			}
		case isEndOfLine(r):
			log.Debugln("isEndOfLine == true")
			if l.index > l.start {
				l.emit(itemParagraph)
				l.start += 1 // Skip the new line
			} else if l.start == l.index {
				l.emit(itemBlankLine)
			}

		}
		if l.next() == eof {
			break
		}
	}

	// Correctly reached eof.
	if l.index > l.start {
		l.emit(itemParagraph)
	}

	l.emit(itemEOF)
	log.Debugln("End")
	return nil
}

// lexSpace consumes space characters (space and tab) in the input and emits a itemSpace token.
func lexSpace(l *lexer) stateFn {
	log.Debugln("Start")
	for isSpace(l.current()) {
		l.next()
	}
	if l.start < l.index {
		l.emit(itemSpace)
		l.next()
	}
	log.Debugln("End")
	return lexStart
}

// lexSection is used after isSection() has determined that the next runes of input are section.
// From here, the lexTitle() and lexSectionAdornment() are called based on the input.
func lexSection(l *lexer) stateFn {
	log.Debugln("Start")
	// The order of the case statement matter here
	switch r := l.next(); {
	case isSectionAdornment(r):
		if l.lastItem.Type != itemTitle {
			return lexSectionAdornment
		}
		lexSectionAdornment(l)
	case isSpace(r):
		return lexSpace
	case r <= unicode.MaxASCII && unicode.IsPrint(r):
		return lexTitle
	case isEndOfLine(r):
		l.start += 1
	}
	log.Debugln("Exit")
	return lexStart
}

// lexTitle consumes input until newline and emits an itemTitle token. Once the token is emitted,
// control is returned to lexSection().
func lexTitle(l *lexer) stateFn {
	log.Debugln("Start")
	for {
		l.next()
		if l.peek() == '\n' {
			l.emit(itemTitle)
			l.start += 1
			l.index += 1
			break
		}
	}
	log.Debugln("End")
	return lexSection
}

// lexSectionAdornment advances the lexer until a newline is encountered and emits a
// itemSectionAdornment token. Control is returned to lexSection() on completion.
func lexSectionAdornment(l *lexer) stateFn {
	log.Debugln("Start")
	for {
		//TODO: Add adornment rune check
		l.next()
		if l.peek() == '\n' {
			l.emit(itemSectionAdornment)
			l.start += 1
			l.index += 1
			break
		}
	}
	log.Debugln("End")
	return lexSection
}
<|endoftext|>


<commit_before>package parsemail

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"mime/multipart"
	"net/mail"
	"strings"
	"time"
)

const contentTypeMultipartMixed = "multipart/mixed"
const contentTypeMultipartAlternative = "multipart/alternative"
const contentTypeMultipartRelated = "multipart/related"
const contentTypeTextHtml = "text/html"
const contentTypeTextPlain = "text/plain"

// Parse an email message read from io.Reader into parsemail.Email struct
func Parse(r io.Reader) (email Email, err error) {
	msg, err := mail.ReadMessage(r)
	if err != nil {
		return
	}

	email, err = createEmailFromHeader(msg.Header)
	if err != nil {
		return
	}

	contentType, params, err := parseContentType(msg.Header.Get("Content-Type"))
	if err != nil {
		return
	}

	switch contentType {
	case contentTypeMultipartMixed:
		email.TextBody, email.HTMLBody, email.Attachments, email.EmbeddedFiles, err = parseMultipartMixed(msg.Body, params["boundary"])
	case contentTypeMultipartAlternative:
		email.TextBody, email.HTMLBody, email.EmbeddedFiles, err = parseMultipartAlternative(msg.Body, params["boundary"])
	case contentTypeTextPlain:
		message, _ := ioutil.ReadAll(msg.Body)
		email.TextBody = strings.TrimSuffix(string(message[:]), "\n")
	case contentTypeTextHtml:
		message, _ := ioutil.ReadAll(msg.Body)
		email.HTMLBody = strings.TrimSuffix(string(message[:]), "\n")
	default:
		err = fmt.Errorf("Unknown top level mime type: %s", contentType)
	}

	return
}

func createEmailFromHeader(header mail.Header) (email Email, err error) {
	email.Subject = header.Get("Subject")

	email.From, err = parseAddressList(header.Get("From"))
	if err != nil {
		return
	}

	email.Sender, err = parseAddress(header.Get("Sender"))
	if err != nil {
		return
	}

	email.ReplyTo, err = parseAddressList(header.Get("Reply-To"))
	if err != nil {
		return
	}

	email.To, err = parseAddressList(header.Get("To"))
	if err != nil {
		return
	}

	email.Cc, err = parseAddressList(header.Get("Cc"))
	if err != nil {
		return
	}

	email.Bcc, err = parseAddressList(header.Get("Bcc"))
	if err != nil {
		return
	}

	email.Date, err = parseTime(header.Get("Date"))
	if err != nil {
		return
	}

	email.ResentFrom, err = parseAddressList(header.Get("Resent-From"))
	if err != nil {
		return
	}

	email.ResentSender, err = parseAddress(header.Get("Resent-Sender"))
	if err != nil {
		return
	}

	email.ResentTo, err = parseAddressList(header.Get("Resent-To"))
	if err != nil {
		return
	}

	email.ResentCc, err = parseAddressList(header.Get("Resent-Cc"))
	if err != nil {
		return
	}

	email.ResentBcc, err = parseAddressList(header.Get("Resent-Bcc"))
	if err != nil {
		return
	}

	if header.Get("Resent-Date") == "" {
		email.ResentDate = time.Time{}
	} else {
		email.ResentDate, err = parseTime(header.Get("Resent-Date"))
		if err != nil {
			return
		}
	}

	email.ResentMessageID = parseMessageId(header.Get("Resent-Message-ID"))
	email.MessageID = parseMessageId(header.Get("Message-ID"))
	email.InReplyTo = parseMessageIdList(header.Get("In-Reply-To"))
	email.References = parseMessageIdList(header.Get("References"))

	//decode whole header for easier access to extra fields
	//todo: should we decode? aren't only standard fields mime encoded?
	email.Header, err = decodeHeaderMime(header)
	if err != nil {
		return
	}

	return
}

func parseContentType(contentTypeHeader string) (contentType string, params map[string]string, err error) {
	if contentTypeHeader == "" {
		contentType = contentTypeTextPlain
		return
	}

	return mime.ParseMediaType(contentTypeHeader)
}

func parseAddress(s string) (*mail.Address, error) {
	if strings.Trim(s, " \n") != "" {
		return mail.ParseAddress(s)
	}

	return nil, nil
}

func parseAddressList(s string) ([]*mail.Address, error) {
	if strings.Trim(s, " \n") != "" {
		return mail.ParseAddressList(s)
	}

	return []*mail.Address{}, nil
}

func parseTime(s string) (time.Time, error) {
	t, err := time.Parse(time.RFC1123Z, s)
	if err == nil {
		return t, err
	}

	return time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", s)
}

func parseMessageId(s string) string {
	return strings.Trim(s, "<> ")
}

func parseMessageIdList(s string) (result []string) {
	for _, p := range strings.Split(s, " ") {
		if strings.Trim(p, " \n") != "" {
			result = append(result, parseMessageId(p))
		}
	}

	return
}

func parseMultipartAlternative(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) {
	pmr := multipart.NewReader(msg, boundary)
	for {
		part, err := pmr.NextPart()

		if err == io.EOF {
			break
		} else if err != nil {
			return textBody, htmlBody, embeddedFiles, err
		}

		contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
		if err != nil {
			return textBody, htmlBody, embeddedFiles, err
		}

		switch contentType {
		case contentTypeTextPlain:
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		case contentTypeTextHtml:
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		case contentTypeMultipartRelated:
			tb, hb, ef, err := parseMultipartAlternative(part, params["boundary"])
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			htmlBody += hb
			textBody += tb
			embeddedFiles = append(embeddedFiles, ef...)
		default:
			if isEmbeddedFile(part) {
				ef, err := decodeEmbeddedFile(part)
				if err != nil {
					return textBody, htmlBody, embeddedFiles, err
				}

				embeddedFiles = append(embeddedFiles, ef)
			} else {
				return textBody, htmlBody, embeddedFiles, fmt.Errorf("Can't process multipart/alternative inner mime type: %s", contentType)
			}
		}
	}

	return textBody, htmlBody, embeddedFiles, err
}

func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody string, attachments []Attachment, embeddedFiles []EmbeddedFile, err error) {
	mr := multipart.NewReader(msg, boundary)
	for {
		part, err := mr.NextPart()
		if err == io.EOF {
			break
		} else if err != nil {
			return textBody, htmlBody, attachments, embeddedFiles, err
		}

		contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
		if err != nil {
			return textBody, htmlBody, attachments, embeddedFiles, err
		}

		if contentType == contentTypeMultipartAlternative {
			textBody, htmlBody, embeddedFiles, err = parseMultipartAlternative(part, params["boundary"])
			if err != nil {
				return textBody, htmlBody, attachments, embeddedFiles, err
			}
		} else if isAttachment(part) {
			at, err := decodeAttachment(part)
			if err != nil {
				return textBody, htmlBody, attachments, embeddedFiles, err
			}

			attachments = append(attachments, at)
		} else {
			return textBody, htmlBody, attachments, embeddedFiles, fmt.Errorf("Unknown multipart/mixed nested mime type: %s", contentType)
		}
	}

	return textBody, htmlBody, attachments, embeddedFiles, err
}

func decodeMimeSentence(s string) (string, error) {
	result := []string{}
	ss := strings.Split(s, " ")

	for _, word := range ss {
		dec := new(mime.WordDecoder)
		w, err := dec.Decode(word)
		if err != nil {
			if len(result) == 0 {
				w = word
			} else {
				w = " " + word
			}
		}

		result = append(result, w)
	}

	return strings.Join(result, ""), nil
}

func decodeHeaderMime(header mail.Header) (mail.Header, error) {
	parsedHeader := map[string][]string{}

	for headerName, headerData := range header {

		parsedHeaderData := []string{}
		for _, headerValue := range headerData {
			decodedHeaderValue, err := decodeMimeSentence(headerValue)
			if err != nil {
				return mail.Header{}, err
			}
			parsedHeaderData = append(parsedHeaderData, decodedHeaderValue)
		}

		parsedHeader[headerName] = parsedHeaderData
	}

	return mail.Header(parsedHeader), nil
}

func decodePartData(part *multipart.Part) (io.Reader, error) {
	encoding := part.Header.Get("Content-Transfer-Encoding")

	if encoding == "base64" {
		dr := base64.NewDecoder(base64.StdEncoding, part)
		dd, err := ioutil.ReadAll(dr)
		if err != nil {
			return nil, err
		}

		return bytes.NewReader(dd), nil
	}

	return nil, fmt.Errorf("Unknown encoding: %s", encoding)
}

func isEmbeddedFile(part *multipart.Part) bool {
	return part.Header.Get("Content-Transfer-Encoding") != ""
}

func decodeEmbeddedFile(part *multipart.Part) (ef EmbeddedFile, err error) {
	cid, err := decodeMimeSentence(part.Header.Get("Content-Id"))
	if err != nil {
		return
	}

	decoded, err := decodePartData(part)
	if err != nil {
		return
	}

	ef.CID = strings.Trim(cid, "<>")
	ef.Data = decoded
	ef.ContentType = part.Header.Get("Content-Type")

	return
}

func isAttachment(part *multipart.Part) bool {
	return part.FileName() != ""
}

func decodeAttachment(part *multipart.Part) (at Attachment, err error) {
	filename, err := decodeMimeSentence(part.FileName())
	if err != nil {
		return
	}

	decoded, err := decodePartData(part)
	if err != nil {
		return
	}

	at.Filename = filename
	at.Data = decoded
	at.ContentType = strings.Split(part.Header.Get("Content-Type"), ";")[0]

	return
}

// Attachment with filename, content type and data (as a io.Reader)
type Attachment struct {
	Filename    string
	ContentType string
	Data        io.Reader
}

// EmbeddedFile with content id, content type and data (as a io.Reader)
type EmbeddedFile struct {
	CID         string
	ContentType string
	Data        io.Reader
}

// Email with fields for all the headers defined in RFC5322 with it's attachments and
type Email struct {
	Header mail.Header

	Subject    string
	Sender     *mail.Address
	From       []*mail.Address
	ReplyTo    []*mail.Address
	To         []*mail.Address
	Cc         []*mail.Address
	Bcc        []*mail.Address
	Date       time.Time
	MessageID  string
	InReplyTo  []string
	References []string

	ResentFrom      []*mail.Address
	ResentSender    *mail.Address
	ResentTo        []*mail.Address
	ResentDate      time.Time
	ResentCc        []*mail.Address
	ResentBcc       []*mail.Address
	ResentMessageID string

	HTMLBody string
	TextBody string

	Attachments   []Attachment
	EmbeddedFiles []EmbeddedFile
}
<commit_msg>Simplified error handling<commit_after>package parsemail

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"mime/multipart"
	"net/mail"
	"strings"
	"time"
)

const contentTypeMultipartMixed = "multipart/mixed"
const contentTypeMultipartAlternative = "multipart/alternative"
const contentTypeMultipartRelated = "multipart/related"
const contentTypeTextHtml = "text/html"
const contentTypeTextPlain = "text/plain"

// Parse an email message read from io.Reader into parsemail.Email struct
func Parse(r io.Reader) (email Email, err error) {
	msg, err := mail.ReadMessage(r)
	if err != nil {
		return
	}

	email, err = createEmailFromHeader(msg.Header)
	if err != nil {
		return
	}

	contentType, params, err := parseContentType(msg.Header.Get("Content-Type"))
	if err != nil {
		return
	}

	switch contentType {
	case contentTypeMultipartMixed:
		email.TextBody, email.HTMLBody, email.Attachments, email.EmbeddedFiles, err = parseMultipartMixed(msg.Body, params["boundary"])
	case contentTypeMultipartAlternative:
		email.TextBody, email.HTMLBody, email.EmbeddedFiles, err = parseMultipartAlternative(msg.Body, params["boundary"])
	case contentTypeTextPlain:
		message, _ := ioutil.ReadAll(msg.Body)
		email.TextBody = strings.TrimSuffix(string(message[:]), "\n")
	case contentTypeTextHtml:
		message, _ := ioutil.ReadAll(msg.Body)
		email.HTMLBody = strings.TrimSuffix(string(message[:]), "\n")
	default:
		err = fmt.Errorf("Unknown top level mime type: %s", contentType)
	}

	return
}

func createEmailFromHeader(header mail.Header) (email Email, err error) {
	hp := headerParser{header: &header}

	email.Subject = header.Get("Subject")
	email.From = hp.parseAddressList(header.Get("From"))
	email.Sender = hp.parseAddress(header.Get("Sender"))
	email.ReplyTo = hp.parseAddressList(header.Get("Reply-To"))
	email.To = hp.parseAddressList(header.Get("To"))
	email.Cc = hp.parseAddressList(header.Get("Cc"))
	email.Bcc = hp.parseAddressList(header.Get("Bcc"))
	email.Date = hp.parseTime(header.Get("Date"))
	email.ResentFrom = hp.parseAddressList(header.Get("Resent-From"))
	email.ResentSender = hp.parseAddress(header.Get("Resent-Sender"))
	email.ResentTo = hp.parseAddressList(header.Get("Resent-To"))
	email.ResentCc = hp.parseAddressList(header.Get("Resent-Cc"))
	email.ResentBcc = hp.parseAddressList(header.Get("Resent-Bcc"))
	email.ResentMessageID = hp.parseMessageId(header.Get("Resent-Message-ID"))
	email.MessageID = hp.parseMessageId(header.Get("Message-ID"))
	email.InReplyTo = hp.parseMessageIdList(header.Get("In-Reply-To"))
	email.References = hp.parseMessageIdList(header.Get("References"))
	email.ResentDate = hp.parseTime(header.Get("Resent-Date"))

	if hp.err != nil {
		err = hp.err
		return
	}

	//decode whole header for easier access to extra fields
	//todo: should we decode? aren't only standard fields mime encoded?
	email.Header, err = decodeHeaderMime(header)
	if err != nil {
		return
	}

	return
}

func parseContentType(contentTypeHeader string) (contentType string, params map[string]string, err error) {
	if contentTypeHeader == "" {
		contentType = contentTypeTextPlain
		return
	}

	return mime.ParseMediaType(contentTypeHeader)
}

func parseMultipartAlternative(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) {
	pmr := multipart.NewReader(msg, boundary)
	for {
		part, err := pmr.NextPart()

		if err == io.EOF {
			break
		} else if err != nil {
			return textBody, htmlBody, embeddedFiles, err
		}

		contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
		if err != nil {
			return textBody, htmlBody, embeddedFiles, err
		}

		switch contentType {
		case contentTypeTextPlain:
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		case contentTypeTextHtml:
			ppContent, err := ioutil.ReadAll(part)
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")
		case contentTypeMultipartRelated:
			tb, hb, ef, err := parseMultipartAlternative(part, params["boundary"])
			if err != nil {
				return textBody, htmlBody, embeddedFiles, err
			}

			htmlBody += hb
			textBody += tb
			embeddedFiles = append(embeddedFiles, ef...)
		default:
			if isEmbeddedFile(part) {
				ef, err := decodeEmbeddedFile(part)
				if err != nil {
					return textBody, htmlBody, embeddedFiles, err
				}

				embeddedFiles = append(embeddedFiles, ef)
			} else {
				return textBody, htmlBody, embeddedFiles, fmt.Errorf("Can't process multipart/alternative inner mime type: %s", contentType)
			}
		}
	}

	return textBody, htmlBody, embeddedFiles, err
}

func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody string, attachments []Attachment, embeddedFiles []EmbeddedFile, err error) {
	mr := multipart.NewReader(msg, boundary)
	for {
		part, err := mr.NextPart()
		if err == io.EOF {
			break
		} else if err != nil {
			return textBody, htmlBody, attachments, embeddedFiles, err
		}

		contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
		if err != nil {
			return textBody, htmlBody, attachments, embeddedFiles, err
		}

		if contentType == contentTypeMultipartAlternative {
			textBody, htmlBody, embeddedFiles, err = parseMultipartAlternative(part, params["boundary"])
			if err != nil {
				return textBody, htmlBody, attachments, embeddedFiles, err
			}
		} else if isAttachment(part) {
			at, err := decodeAttachment(part)
			if err != nil {
				return textBody, htmlBody, attachments, embeddedFiles, err
			}

			attachments = append(attachments, at)
		} else {
			return textBody, htmlBody, attachments, embeddedFiles, fmt.Errorf("Unknown multipart/mixed nested mime type: %s", contentType)
		}
	}

	return textBody, htmlBody, attachments, embeddedFiles, err
}

func decodeMimeSentence(s string) (string, error) {
	result := []string{}
	ss := strings.Split(s, " ")

	for _, word := range ss {
		dec := new(mime.WordDecoder)
		w, err := dec.Decode(word)
		if err != nil {
			if len(result) == 0 {
				w = word
			} else {
				w = " " + word
			}
		}

		result = append(result, w)
	}

	return strings.Join(result, ""), nil
}

func decodeHeaderMime(header mail.Header) (mail.Header, error) {
	parsedHeader := map[string][]string{}

	for headerName, headerData := range header {

		parsedHeaderData := []string{}
		for _, headerValue := range headerData {
			decodedHeaderValue, err := decodeMimeSentence(headerValue)
			if err != nil {
				return mail.Header{}, err
			}
			parsedHeaderData = append(parsedHeaderData, decodedHeaderValue)
		}

		parsedHeader[headerName] = parsedHeaderData
	}

	return mail.Header(parsedHeader), nil
}

func decodePartData(part *multipart.Part) (io.Reader, error) {
	encoding := part.Header.Get("Content-Transfer-Encoding")

	if encoding == "base64" {
		dr := base64.NewDecoder(base64.StdEncoding, part)
		dd, err := ioutil.ReadAll(dr)
		if err != nil {
			return nil, err
		}

		return bytes.NewReader(dd), nil
	}

	return nil, fmt.Errorf("Unknown encoding: %s", encoding)
}

func isEmbeddedFile(part *multipart.Part) bool {
	return part.Header.Get("Content-Transfer-Encoding") != ""
}

func decodeEmbeddedFile(part *multipart.Part) (ef EmbeddedFile, err error) {
	cid, err := decodeMimeSentence(part.Header.Get("Content-Id"))
	if err != nil {
		return
	}

	decoded, err := decodePartData(part)
	if err != nil {
		return
	}

	ef.CID = strings.Trim(cid, "<>")
	ef.Data = decoded
	ef.ContentType = part.Header.Get("Content-Type")

	return
}

func isAttachment(part *multipart.Part) bool {
	return part.FileName() != ""
}

func decodeAttachment(part *multipart.Part) (at Attachment, err error) {
	filename, err := decodeMimeSentence(part.FileName())
	if err != nil {
		return
	}

	decoded, err := decodePartData(part)
	if err != nil {
		return
	}

	at.Filename = filename
	at.Data = decoded
	at.ContentType = strings.Split(part.Header.Get("Content-Type"), ";")[0]

	return
}

type headerParser struct {
	header *mail.Header
	err error
}

func (hp headerParser) parseAddress(s string) (ma *mail.Address) {
	if hp.err != nil {
		return nil
	}

	if strings.Trim(s, " \n") != "" {
		ma, hp.err = mail.ParseAddress(s)

		return ma
	}

	return nil
}

func (hp headerParser) parseAddressList(s string) (ma []*mail.Address) {
	if hp.err != nil {
		return
	}

	if strings.Trim(s, " \n") != "" {
		ma, hp.err = mail.ParseAddressList(s)
		return
	}

	return
}

func (hp headerParser) parseTime(s string) (t time.Time) {
	if hp.err != nil || s == ""{
		return
	}

	t, hp.err = time.Parse(time.RFC1123Z, s)
	if hp.err == nil {
		return t
	}

	t, hp.err = time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", s)

	return
}

func (hp headerParser) parseMessageId(s string) string {
	if hp.err != nil {
		return ""
	}

	return strings.Trim(s, "<> ")
}

func (hp headerParser) parseMessageIdList(s string) (result []string) {
	if hp.err != nil {
		return
	}

	for _, p := range strings.Split(s, " ") {
		if strings.Trim(p, " \n") != "" {
			result = append(result, hp.parseMessageId(p))
		}
	}

	return
}

// Attachment with filename, content type and data (as a io.Reader)
type Attachment struct {
	Filename    string
	ContentType string
	Data        io.Reader
}

// EmbeddedFile with content id, content type and data (as a io.Reader)
type EmbeddedFile struct {
	CID         string
	ContentType string
	Data        io.Reader
}

// Email with fields for all the headers defined in RFC5322 with it's attachments and
type Email struct {
	Header mail.Header

	Subject    string
	Sender     *mail.Address
	From       []*mail.Address
	ReplyTo    []*mail.Address
	To         []*mail.Address
	Cc         []*mail.Address
	Bcc        []*mail.Address
	Date       time.Time
	MessageID  string
	InReplyTo  []string
	References []string

	ResentFrom      []*mail.Address
	ResentSender    *mail.Address
	ResentTo        []*mail.Address
	ResentDate      time.Time
	ResentCc        []*mail.Address
	ResentBcc       []*mail.Address
	ResentMessageID string

	HTMLBody string
	TextBody string

	Attachments   []Attachment
	EmbeddedFiles []EmbeddedFile
}
<|endoftext|>


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

package path

import (
	"fmt"
	"strings"
)

type ContextPath struct {
	Path []interface{}
	Tag  string
}

// New returns a new ContextPath with the given tag and path. It's a helper since go's
// literal syntax is quite verbose, especially with []interface{}{...}.
func New(tag string, path ...interface{}) ContextPath {
	return ContextPath{
		Tag:  tag,
		Path: path,
	}
}

func (c ContextPath) String() string {
	strs := []string{"$"}
	for _, e := range c.Path {
		strs = append(strs, fmt.Sprintf("%v", e))
	}
	return strings.Join(strs, ".")
}

func (c ContextPath) Append(e ...interface{}) ContextPath {
	return ContextPath{
		Path: append(c.Path, e...),
		Tag:  c.Tag,
	}
}

func (c ContextPath) Copy() ContextPath {
	return ContextPath{
		Path: append([]interface{}{}, c.Path...),
		Tag:  c.Tag,
	}
}

// Head returns the first element in the path, panics if empty.
func (c ContextPath) Head() interface{} {
	return c.Path[0]
}

func (c ContextPath) Tail() ContextPath {
	if len(c.Path) == 0 {
		return ContextPath{}
	}
	return ContextPath{
		Path: c.Path[1:],
		Tag:  c.Tag,
	}
}

func (c ContextPath) Pop() ContextPath {
	if len(c.Path) == 0 {
		return ContextPath{}
	}
	return ContextPath{
		Path: c.Path[:c.Len()-1],
		Tag:  c.Tag,
	}
}

func (c ContextPath) Len() int {
	return len(c.Path)
}
<commit_msg>path: preserve nil path slices in ContextPath.Copy()<commit_after>// Copyright 2019 Red Hat, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.)

package path

import (
	"fmt"
	"strings"
)

type ContextPath struct {
	Path []interface{}
	Tag  string
}

// New returns a new ContextPath with the given tag and path. It's a helper since go's
// literal syntax is quite verbose, especially with []interface{}{...}.
func New(tag string, path ...interface{}) ContextPath {
	return ContextPath{
		Tag:  tag,
		Path: path,
	}
}

func (c ContextPath) String() string {
	strs := []string{"$"}
	for _, e := range c.Path {
		strs = append(strs, fmt.Sprintf("%v", e))
	}
	return strings.Join(strs, ".")
}

func (c ContextPath) Append(e ...interface{}) ContextPath {
	return ContextPath{
		Path: append(c.Path, e...),
		Tag:  c.Tag,
	}
}

func (c ContextPath) Copy() ContextPath {
	// make sure to preserve reflect.DeepEqual() equality
	var path []interface{}
	if c.Path != nil {
		path = append(path, c.Path...)
	}
	return ContextPath{
		Path: path,
		Tag:  c.Tag,
	}
}

// Head returns the first element in the path, panics if empty.
func (c ContextPath) Head() interface{} {
	return c.Path[0]
}

func (c ContextPath) Tail() ContextPath {
	if len(c.Path) == 0 {
		return ContextPath{}
	}
	return ContextPath{
		Path: c.Path[1:],
		Tag:  c.Tag,
	}
}

func (c ContextPath) Pop() ContextPath {
	if len(c.Path) == 0 {
		return ContextPath{}
	}
	return ContextPath{
		Path: c.Path[:c.Len()-1],
		Tag:  c.Tag,
	}
}

func (c ContextPath) Len() int {
	return len(c.Path)
}
<|endoftext|>


<commit_before>package bogus

import (
	"io/ioutil"
	"net"
	"net/http"
	"testing"

	. "github.com/franela/goblin"
	. "github.com/onsi/gomega"
)

func TestPaths(t *testing.T) {
	g := Goblin(t)
	RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) })

	g.Describe("Paths", func() {
		var server *Bogus
		var payload = "some return payload"
		var status = http.StatusOK

		g.BeforeEach(func() {
			server = New()
			server.SetPayload([]byte(payload))
			server.SetStatus(status)
			server.Start()
		})

		g.It("should allow setting the payload for the root path", func() {
			host, port := server.HostPort()

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			body, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			Expect(string(body)).To(Equal(payload))
			Expect(server.Hits()).To(Equal(1))
		})

		g.It("should allow setting the return status for the root path", func() {
			host, port := server.HostPort()

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(status))
			Expect(server.Hits()).To(Equal(1))
		})

		g.It("should allow adding a new path", func() {
			host, port := server.HostPort()
			server.AddPath("/foo/bar").SetStatus(http.StatusCreated)

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(status))
			Expect(server.Hits()).To(Equal(1))

			resp, err = http.Get("http://" + net.JoinHostPort(host, port) + "/foo/bar")
			Expect(err).NotTo(HaveOccurred())

			Expect(resp.StatusCode).To(Equal(http.StatusCreated))
			Expect(server.Hits()).To(Equal(2))
		})

		g.It("should return unique payloads per path", func() {
			host, port := server.HostPort()
			fooload := "foobar"
			server.AddPath("/foo/bar").SetPayload([]byte(fooload))

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			body, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			Expect(string(body)).To(Equal(payload))
			Expect(server.Hits()).To(Equal(1))

			resp, err = http.Get("http://" + net.JoinHostPort(host, port) + "/foo/bar")
			Expect(err).NotTo(HaveOccurred())

			body, err = ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			Expect(string(body)).To(Equal(fooload))
			Expect(server.Hits()).To(Equal(2))
		})

		g.It("should return the number of times a path has been hit", func() {
			host, port := server.HostPort()
			fooload := "foobar"
			server.AddPath("/foo/bar").SetPayload([]byte(fooload))

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(server.Hits()).To(Equal(1))
			Expect(server.paths["/"].Hits()).To(Equal(1))

			resp, err = http.Get("http://" + net.JoinHostPort(host, port) + "/foo/bar")
			Expect(err).NotTo(HaveOccurred())

			Expect(server.Hits()).To(Equal(2))
			Expect(server.paths["/"].Hits()).To(Equal(1))
			Expect(server.paths["/foo/bar"].Hits()).To(Equal(1))
		})
		g.It("should return / for any path if only that is registered", func() {
			server.SetPayload([]byte("this is / payload"))
			host, port := server.HostPort()

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			bodyBytes, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			resp.Body.Close()
			Expect(bodyBytes).To(Equal([]byte("this is / payload")))

			// now try with /foo/bar not registered
			resp, err = http.Get("http://" + net.JoinHostPort(host, port) + "/foo/bar")
			Expect(err).NotTo(HaveOccurred())
			bodyBytes, err = ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			resp.Body.Close()
			Expect(bodyBytes).To(Equal([]byte("this is / payload")))
		})
		g.It("should respect registered paths", func() {
			server.SetPayload([]byte("this is / payload"))
			path := server.AddPath("/foo/bar")
			path.SetPayload([]byte("this is /foo/bar payload"))
			host, port := server.HostPort()

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			bodyBytes, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			resp.Body.Close()
			Expect(string(bodyBytes)).To(Equal("this is / payload"))

			resp, err = http.Get("http://" + net.JoinHostPort(host, port) + "/foo/bar")
			Expect(err).NotTo(HaveOccurred())
			bodyBytes, err = ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			resp.Body.Close()
			Expect(string(bodyBytes)).To(Equal("this is /foo/bar payload"))

		})
		g.It("should return 404 on an unregistered path when there is more than one registration", func() {
			server.SetPayload([]byte("this is / payload"))
			path := server.AddPath("/foo/bar")
			path.SetPayload([]byte("this is /foo/bar payload"))
			host, port := server.HostPort()

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			bodyBytes, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			resp.Body.Close()
			Expect(string(bodyBytes)).To(Equal("this is / payload"))

			resp, err = http.Get("http://" + net.JoinHostPort(host, port) + "/foo/bar/baz")
			Expect(err).NotTo(HaveOccurred())
			Expect(resp.StatusCode).To(Equal(http.StatusNotFound))
			bodyBytes, err = ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			resp.Body.Close()
			Expect(string(bodyBytes)).To(Equal("Not Found"))

		})
	})
}
<commit_msg>moving host port vars to global<commit_after>package bogus

import (
	"io/ioutil"
	"net"
	"net/http"
	"testing"

	. "github.com/franela/goblin"
	. "github.com/onsi/gomega"
)

func TestPaths(t *testing.T) {
	g := Goblin(t)
	RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) })

	g.Describe("Paths", func() {
		var server *Bogus
		var payload = "some return payload"
		var status = http.StatusOK
		var host, port string

		g.BeforeEach(func() {
			server = New()
			server.SetPayload([]byte(payload))
			server.SetStatus(status)
			server.Start()
			host, port = server.HostPort()
		})

		g.It("should allow setting the payload for the root path", func() {
			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			body, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			Expect(string(body)).To(Equal(payload))
			Expect(server.Hits()).To(Equal(1))
		})

		g.It("should allow setting the return status for the root path", func() {
			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(status))
			Expect(server.Hits()).To(Equal(1))
		})

		g.It("should allow adding a new path", func() {
			server.AddPath("/foo/bar").SetStatus(http.StatusCreated)

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(status))
			Expect(server.Hits()).To(Equal(1))

			resp, err = http.Get("http://" + net.JoinHostPort(host, port) + "/foo/bar")
			Expect(err).NotTo(HaveOccurred())

			Expect(resp.StatusCode).To(Equal(http.StatusCreated))
			Expect(server.Hits()).To(Equal(2))
		})

		g.It("should return unique payloads per path", func() {
			fooload := "foobar"
			server.AddPath("/foo/bar").SetPayload([]byte(fooload))

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			body, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			Expect(string(body)).To(Equal(payload))
			Expect(server.Hits()).To(Equal(1))

			resp, err = http.Get("http://" + net.JoinHostPort(host, port) + "/foo/bar")
			Expect(err).NotTo(HaveOccurred())

			body, err = ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			Expect(string(body)).To(Equal(fooload))
			Expect(server.Hits()).To(Equal(2))
		})

		g.It("should return the number of times a path has been hit", func() {
			fooload := "foobar"
			server.AddPath("/foo/bar").SetPayload([]byte(fooload))

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(server.Hits()).To(Equal(1))
			Expect(server.paths["/"].Hits()).To(Equal(1))

			resp, err = http.Get("http://" + net.JoinHostPort(host, port) + "/foo/bar")
			Expect(err).NotTo(HaveOccurred())

			Expect(server.Hits()).To(Equal(2))
			Expect(server.paths["/"].Hits()).To(Equal(1))
			Expect(server.paths["/foo/bar"].Hits()).To(Equal(1))
		})
		g.It("should return / for any path if only that is registered", func() {
			server.SetPayload([]byte("this is / payload"))

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			bodyBytes, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			resp.Body.Close()
			Expect(bodyBytes).To(Equal([]byte("this is / payload")))

			// now try with /foo/bar not registered
			resp, err = http.Get("http://" + net.JoinHostPort(host, port) + "/foo/bar")
			Expect(err).NotTo(HaveOccurred())
			bodyBytes, err = ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			resp.Body.Close()
			Expect(bodyBytes).To(Equal([]byte("this is / payload")))
		})
		g.It("should respect registered paths", func() {
			server.SetPayload([]byte("this is / payload"))
			path := server.AddPath("/foo/bar")
			path.SetPayload([]byte("this is /foo/bar payload"))

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			bodyBytes, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			resp.Body.Close()
			Expect(string(bodyBytes)).To(Equal("this is / payload"))

			resp, err = http.Get("http://" + net.JoinHostPort(host, port) + "/foo/bar")
			Expect(err).NotTo(HaveOccurred())
			bodyBytes, err = ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			resp.Body.Close()
			Expect(string(bodyBytes)).To(Equal("this is /foo/bar payload"))

		})
		g.It("should return 404 on an unregistered path when there is more than one registration", func() {
			server.SetPayload([]byte("this is / payload"))
			path := server.AddPath("/foo/bar")
			path.SetPayload([]byte("this is /foo/bar payload"))

			resp, err := http.Get("http://" + net.JoinHostPort(host, port))
			Expect(err).NotTo(HaveOccurred())
			bodyBytes, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			resp.Body.Close()
			Expect(string(bodyBytes)).To(Equal("this is / payload"))

			resp, err = http.Get("http://" + net.JoinHostPort(host, port) + "/foo/bar/baz")
			Expect(err).NotTo(HaveOccurred())
			Expect(resp.StatusCode).To(Equal(http.StatusNotFound))
			bodyBytes, err = ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			resp.Body.Close()
			Expect(string(bodyBytes)).To(Equal("Not Found"))

		})
	})
}
<|endoftext|>


<commit_before>package path

import (
	"github.com/andreaskoch/docs/repository"
	"os"
	"strings"
)

const (
	FilesystemPathSeperator = os.PathSeparator
	WebPathSeperator        = "/"
)

func NewProvider(basePath string) *Provider {
	return &Provider{
		basePath: basePath,
	}
}

type Provider struct {
	basePath string
}

func (provider *Provider) GetWebRoute(pather repository.Pather) string {
	pathSeperator := string(FilesystemPathSeperator)
	relativePath := strings.Replace(pather.Path(), provider.basePath, "", 1)
	relativePath = pathSeperator + strings.TrimLeft(relativePath, pathSeperator)
	return relativePath
}
<commit_msg>Bug fix: web routes must use forward slashes instead of the filesystem path sepeator. The code worked on unix based systems but not on Windows.<commit_after>package path

import (
	"github.com/andreaskoch/docs/repository"
	"os"
	"strings"
)

const (
	FilesystemPathSeperator = os.PathSeparator
	WebPathSeperator        = "/"
)

func NewProvider(basePath string) *Provider {
	return &Provider{
		basePath: basePath,
	}
}

type Provider struct {
	basePath string
}

func (provider *Provider) GetWebRoute(pather repository.Pather) string {
	filesystemSeperatorString := string(FilesystemPathSeperator)

	relativeFilepath := strings.Replace(pather.Path(), provider.basePath, "", 1)
	relativeFilepath = filesystemSeperatorString + strings.TrimLeft(relativeFilepath, filesystemSeperatorString)

	webRoute := strings.Replace(relativeFilepath, filesystemSeperatorString, "/", -1)
	return webRoute
}
<|endoftext|>


<commit_before>// Photobomb conducts workflow tests triggered by requests to its web server.
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	"github.com/Sirupsen/logrus"
	sdk "github.com/dysolution/espsdk"
	"github.com/x-cray/logrus-prefixed-formatter"
)

const NAME = "photobomb"
const VERSION = "0.0.1"

var appID = fmt.Sprintf("%s %s", NAME, VERSION)

var client sdk.Client
var token sdk.Token
var config Raid
var enabled bool
var inception time.Time
var interval int
var intervalDelta = make(chan float64, 1)
var log *logrus.Logger
var raidCount, requestCount int
var toggle = make(chan bool, 1)
var token sdk.Token

func init() {
	inception = time.Now()
	enabled = false
	interval = 5
	log = sdk.Log
	log.Formatter = &prefixed.TextFormatter{TimestampFormat: time.RFC3339}
}

// round returns the nearest integer. This implementation doesn't work for
// negative numbers, but that doesn't matter in this context.
func round(a float64) int {
	val := int(a + 0.5)
	if val < 1 {
		val = 1
	}
	return val
}

func setInterval(d float64) {
	log.Debugf("changing interval by %v seconds", d)
	interval = round(float64(interval) + d)
	log.Debugf("new interval: %v", interval)
}

func check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}

func loadConfig(path string) Raid {
	fi, err := os.Stat(path)
	if err != nil {
		return ExampleConfig()
	}
	if fi.Size() == 0 {
		// The user has probably tried to redirect "example" output, e.g.,
		// photobomb example > config.json, which zeroes out config.json, so
		// we shouldn't bother trying to read it.
		return ExampleConfig()
	}

	log.Debugf("reading configuration from: %s", path)
	file, err := ioutil.ReadFile(path)
	if err != nil {
		log.Debugf("could not read config: %s", path)
		return ExampleConfig()
	}

	var data Raid
	if err := json.Unmarshal(file, &data); err != nil {
		log.Fatal(err)
	}
	check(err)
	return data
}
<commit_msg>use single instance of Client<commit_after>// Photobomb conducts workflow tests triggered by requests to its web server.
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	"github.com/Sirupsen/logrus"
	sdk "github.com/dysolution/espsdk"
	"github.com/x-cray/logrus-prefixed-formatter"
)

const NAME = "photobomb"
const VERSION = "0.0.1"

var appID = fmt.Sprintf("%s %s", NAME, VERSION)

var client *sdk.Client
var config Raid
var enabled bool
var inception time.Time
var interval int
var intervalDelta = make(chan float64, 1)
var log *logrus.Logger
var raidCount, requestCount int
var toggle = make(chan bool, 1)
var token sdk.Token

func init() {
	inception = time.Now()
	enabled = false
	interval = 5
	log = sdk.Log
	log.Formatter = &prefixed.TextFormatter{TimestampFormat: time.RFC3339}
}

// round returns the nearest integer. This implementation doesn't work for
// negative numbers, but that doesn't matter in this context.
func round(a float64) int {
	val := int(a + 0.5)
	if val < 1 {
		val = 1
	}
	return val
}

func setInterval(d float64) {
	log.Debugf("changing interval by %v seconds", d)
	interval = round(float64(interval) + d)
	log.Debugf("new interval: %v", interval)
}

func check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}

func loadConfig(path string) Raid {
	fi, err := os.Stat(path)
	if err != nil {
		return ExampleConfig()
	}
	if fi.Size() == 0 {
		// The user has probably tried to redirect "example" output, e.g.,
		// photobomb example > config.json, which zeroes out config.json, so
		// we shouldn't bother trying to read it.
		return ExampleConfig()
	}

	log.Debugf("reading configuration from: %s", path)
	file, err := ioutil.ReadFile(path)
	if err != nil {
		log.Debugf("could not read config: %s", path)
		return ExampleConfig()
	}

	var data Raid
	if err := json.Unmarshal(file, &data); err != nil {
		log.Fatal(err)
	}
	check(err)
	return data
}
<|endoftext|>