<commit_before>package handlers

import (
	"net/http"
	"socialapi/workers/api/modules/account"
	"socialapi/workers/api/modules/activity"
	"socialapi/workers/api/modules/channel"
	"socialapi/workers/api/modules/interaction"
	"socialapi/workers/api/modules/message"
	"socialapi/workers/api/modules/messagelist"
	"socialapi/workers/api/modules/participant"
	"socialapi/workers/api/modules/popular"
	"socialapi/workers/api/modules/privatemessage"
	"socialapi/workers/api/modules/reply"

	"github.com/rcrowley/go-tigertonic"
)

var (
	cors = tigertonic.NewCORSBuilder().AddAllowedOrigins("*")
)

func handlerWrapper(handler interface{}, logName string) http.Handler {
	return cors.Build(
		tigertonic.Timed(
			tigertonic.Marshaled(handler),
			logName,
			nil,
		))
}

// todo implement context support here for requests
func Inject(mux *tigertonic.TrieServeMux) *tigertonic.TrieServeMux {

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Message Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/message/{id}", handlerWrapper(message.Update, "message-update"))
	mux.Handle("DELETE", "/message/{id}", handlerWrapper(message.Delete, "message-delete"))
	mux.Handle("GET", "/message/{id}", handlerWrapper(message.Get, "message-get"))
	mux.Handle("GET", "/message/{id}/related", handlerWrapper(message.GetWithRelated, "message-get"))

	////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////// Message Reply Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/message/{id}/reply", handlerWrapper(reply.Create, "reply-create"))
	mux.Handle("GET", "/message/{id}/reply", handlerWrapper(reply.List, "reply-list"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////// Message Interaction Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/message/{id}/interaction/{type}/add", handlerWrapper(interaction.Add, "interactions-add"))
	mux.Handle("POST", "/message/{id}/interaction/{type}/delete", handlerWrapper(interaction.Delete, "interactions-delete"))
	// get all the interactions for message
	mux.Handle("GET", "/message/{id}/interaction/{type}", handlerWrapper(interaction.List, "interactions-list-typed"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Channel Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/channel", handlerWrapper(channel.Create, "channel-create"))
	mux.Handle("GET", "/channel", handlerWrapper(channel.List, "channel-list"))
	mux.Handle("GET", "/channel/search", handlerWrapper(channel.Search, "channel-search"))
	// deprecated, here for socialworker
	mux.Handle("POST", "/channel/{id}", handlerWrapper(channel.Update, "channel-update"))
	mux.Handle("POST", "/channel/{id}/update", handlerWrapper(channel.Update, "channel-update"))
	mux.Handle("POST", "/channel/{id}/delete", handlerWrapper(channel.Delete, "channel-delete"))
	mux.Handle("GET", "/channel/{id}", handlerWrapper(channel.Get, "channel-get"))
	// add a new messages to the channel
	mux.Handle("POST", "/channel/{id}/message", handlerWrapper(message.Create, "channel-message-create"))
	// list participants of the channel
	mux.Handle("GET", "/channel/{id}/participant", handlerWrapper(participant.List, "participant-list"))
	// add participant to the channel
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/add", handlerWrapper(participant.Add, "participant-list"))
	// remove participant from the channel
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/delete", handlerWrapper(participant.Delete, "participant-list"))
	// list messages of the channel
	mux.Handle("GET", "/channel/{id}/history", handlerWrapper(messagelist.List, "channel-history-list"))
	// register an account
	mux.Handle("POST", "/account", handlerWrapper(account.Register, "account-create"))
	// list channels of the account
	mux.Handle("GET", "/account/{id}/channels", handlerWrapper(account.ListChannels, "account-channel-list"))
	// list posts of the account
	mux.Handle("GET", "/account/{id}/posts", handlerWrapper(account.ListPosts, "account-post-list"))
	// follow the account
	mux.Handle("POST", "/account/{id}/follow", handlerWrapper(account.Follow, "account-follow"))
	// un-follow the account
	mux.Handle("POST", "/account/{id}/unfollow", handlerWrapper(account.Unfollow, "account-unfollow"))
	// mark as troll
	mux.Handle("POST", "/account/{id}/markastroll", handlerWrapper(account.MarkAsTroll, "account-mark-as-troll"))

	// fetch profile feed
	// mux.Handle("GET", "/account/{id}/profile/feed", handlerWrapper(account.ListProfileFeed, "list-profile-feed"))
	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/channel", handlerWrapper(activity.GetPinnedActivityChannel, "activity-pin-get-channel"))
	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/list", handlerWrapper(activity.List, "activity-pin-list-message"))
	// pin a new status update
	mux.Handle("POST", "/activity/pin/add", handlerWrapper(activity.PinMessage, "activity-add-pinned-message"))
	// unpin a status update
	mux.Handle("POST", "/activity/pin/remove", handlerWrapper(activity.UnpinMessage, "activity-remove-pinned-message"))
	// get popular topics
	mux.Handle("GET", "/popular/topics/{statisticName}", handlerWrapper(popular.ListTopics, "list-popular-topics"))
	mux.Handle("GET", "/popular/posts/{channelName}/{statisticName}", handlerWrapper(popular.ListPosts, "list-popular-posts"))

	mux.Handle("POST", "/privatemessage/send", handlerWrapper(privatemessage.Send, "privatemessage-send"))
	mux.Handle("GET", "/privatemessage/list", handlerWrapper(privatemessage.List, "privatemessage-list"))

	return mux
}

// to-do list
// get current account from context for future
// like client.connection.delegate
<commit_msg>Social: use handler package from `common` directory<commit_after>package handlers

import (
	"socialapi/workers/api/modules/account"
	"socialapi/workers/api/modules/activity"
	"socialapi/workers/api/modules/channel"
	"socialapi/workers/api/modules/interaction"
	"socialapi/workers/api/modules/message"
	"socialapi/workers/api/modules/messagelist"
	"socialapi/workers/api/modules/participant"
	"socialapi/workers/api/modules/popular"
	"socialapi/workers/api/modules/privatemessage"
	"socialapi/workers/api/modules/reply"
	"socialapi/workers/common/handler"

	"github.com/rcrowley/go-tigertonic"
)

// todo implement context support here for requests
func Inject(mux *tigertonic.TrieServeMux) *tigertonic.TrieServeMux {

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Message Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/message/{id}", handler.Wrapper(message.Update, "message-update"))
	mux.Handle("DELETE", "/message/{id}", handler.Wrapper(message.Delete, "message-delete"))
	mux.Handle("GET", "/message/{id}", handler.Wrapper(message.Get, "message-get"))
	mux.Handle("GET", "/message/{id}/related", handler.Wrapper(message.GetWithRelated, "message-get"))

	////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////// Message Reply Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/message/{id}/reply", handler.Wrapper(reply.Create, "reply-create"))
	mux.Handle("GET", "/message/{id}/reply", handler.Wrapper(reply.List, "reply-list"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////// Message Interaction Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/message/{id}/interaction/{type}/add", handler.Wrapper(interaction.Add, "interactions-add"))
	mux.Handle("POST", "/message/{id}/interaction/{type}/delete", handler.Wrapper(interaction.Delete, "interactions-delete"))
	// get all the interactions for message
	mux.Handle("GET", "/message/{id}/interaction/{type}", handler.Wrapper(interaction.List, "interactions-list-typed"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Channel Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/channel", handler.Wrapper(channel.Create, "channel-create"))
	mux.Handle("GET", "/channel", handler.Wrapper(channel.List, "channel-list"))
	mux.Handle("GET", "/channel/search", handler.Wrapper(channel.Search, "channel-search"))
	// deprecated, here for socialworker
	mux.Handle("POST", "/channel/{id}", handler.Wrapper(channel.Update, "channel-update"))
	mux.Handle("POST", "/channel/{id}/update", handler.Wrapper(channel.Update, "channel-update"))
	mux.Handle("POST", "/channel/{id}/delete", handler.Wrapper(channel.Delete, "channel-delete"))
	mux.Handle("GET", "/channel/{id}", handler.Wrapper(channel.Get, "channel-get"))
	// add a new messages to the channel
	mux.Handle("POST", "/channel/{id}/message", handler.Wrapper(message.Create, "channel-message-create"))
	// list participants of the channel
	mux.Handle("GET", "/channel/{id}/participant", handler.Wrapper(participant.List, "participant-list"))
	// add participant to the channel
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/add", handler.Wrapper(participant.Add, "participant-list"))
	// remove participant from the channel
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/delete", handler.Wrapper(participant.Delete, "participant-list"))
	// list messages of the channel
	mux.Handle("GET", "/channel/{id}/history", handler.Wrapper(messagelist.List, "channel-history-list"))
	// register an account
	mux.Handle("POST", "/account", handler.Wrapper(account.Register, "account-create"))
	// list channels of the account
	mux.Handle("GET", "/account/{id}/channels", handler.Wrapper(account.ListChannels, "account-channel-list"))
	// list posts of the account
	mux.Handle("GET", "/account/{id}/posts", handler.Wrapper(account.ListPosts, "account-post-list"))
	// follow the account
	mux.Handle("POST", "/account/{id}/follow", handler.Wrapper(account.Follow, "account-follow"))
	// un-follow the account
	mux.Handle("POST", "/account/{id}/unfollow", handler.Wrapper(account.Unfollow, "account-unfollow"))

	// fetch profile feed
	// mux.Handle("GET", "/account/{id}/profile/feed", handler.Wrapper(account.ListProfileFeed, "list-profile-feed"))
	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/channel", handler.Wrapper(activity.GetPinnedActivityChannel, "activity-pin-get-channel"))
	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/list", handler.Wrapper(activity.List, "activity-pin-list-message"))
	// pin a new status update
	mux.Handle("POST", "/activity/pin/add", handler.Wrapper(activity.PinMessage, "activity-add-pinned-message"))
	// unpin a status update
	mux.Handle("POST", "/activity/pin/remove", handler.Wrapper(activity.UnpinMessage, "activity-remove-pinned-message"))
	// get popular topics
	mux.Handle("GET", "/popular/topics/{statisticName}", handler.Wrapper(popular.ListTopics, "list-popular-topics"))
	mux.Handle("GET", "/popular/posts/{channelName}/{statisticName}", handler.Wrapper(popular.ListPosts, "list-popular-posts"))

	mux.Handle("POST", "/privatemessage/send", handler.Wrapper(privatemessage.Send, "privatemessage-send"))
	mux.Handle("GET", "/privatemessage/list", handler.Wrapper(privatemessage.List, "privatemessage-list"))

	return mux
}

// to-do list
// get current account from context for future
// like client.connection.delegate
<|endoftext|>


<commit_before>package fluentd_forwarder

import (
	"bufio"
	"compress/gzip"
	"crypto/md5"
	"errors"
	td_client "github.com/treasure-data/td-client-go"
	"hash"
	"io"
	"os"
)

type CompressingBlob struct {
	inner       td_client.Blob
	level       int
	bufferSize  int
	reader      *CompressingBlobReader
	tempFactory RandomAccessStoreFactory
	md5sum      []byte
	size        int64
}

type CompressingBlobReader struct {
	buf         []byte // ring buffer
	o           int
	src         io.ReadCloser
	dst         *StoreReadWriter
	s           SizedRandomAccessStore
	w           *StoreReadWriter
	bw          *bufio.Writer
	cw          *gzip.Writer
	h           hash.Hash
	eof         bool
	md5SumAvailable   bool
	closeNotify func(*CompressingBlobReader)
}

func (reader *CompressingBlobReader) drainAll() error {
	rn := 0
	err := (error)(nil)
	for reader.cw != nil && err == nil {
		o := reader.o
		if !reader.eof {
			rn, err = reader.src.Read(reader.buf[reader.o:cap(reader.buf)])
			o += rn
			if err == io.EOF {
				reader.eof = true
			}
		} else {
			if o == 0 {
				break
			}
		}
		if o > 0 {
			wn, werr := reader.cw.Write(reader.buf[0:o])
			if werr != nil {
				err = werr
			}
			copy(reader.buf[0:], reader.buf[wn:])
			reader.o = o - wn
		}
		if err != nil {
			reader.cw.Close()
			reader.cw = nil
			werr := reader.bw.Flush()
			if werr != nil {
				return werr
			}
			reader.bw = nil
		}
	}
	if err == io.EOF {
		err = nil
	}
	return err
}

func (reader *CompressingBlobReader) Read(p []byte) (int, error) {
	n := int(0)
	err := (error)(nil)
	rn := int(0)
	rpos, err := reader.dst.Seek(0, os.SEEK_CUR)
	if err != nil {
		return 0, err // should never happen
	}
	wpos, err := reader.w.Seek(0, os.SEEK_CUR)
	if err != nil {
		return 0, err // should never happen
	}
	for {
		n = int(wpos - rpos) // XXX: may underflow, but it should be ok
		if err != nil || len(p) <= n || reader.cw == nil {
			break
		}
		o := reader.o
		if !reader.eof {
			rn, err = reader.src.Read(reader.buf[reader.o:cap(reader.buf)])
			o += rn
			if err == io.EOF {
				reader.eof = true
			}
		}
		if o > 0 {
			wn, werr := reader.cw.Write(reader.buf[0:o])
			copy(reader.buf[0:], reader.buf[wn:o])
			reader.o = o - wn
			if werr != nil {
				return 0, werr
			}
		} else {
			if reader.eof && reader.cw != nil {
				reader.cw.Close()
				reader.cw = nil
				werr := reader.bw.Flush()
				if werr != nil {
					return 0, werr
				}
				reader.bw = nil
			}
		}
		var werr error
		wpos, werr = reader.w.Seek(0, os.SEEK_CUR)
		if werr != nil {
			return 0, werr // should never happen
		}
	}
	if len(p) > 0 && n == 0 && reader.eof {
		return 0, io.EOF
	}
	if n > len(p) {
		n = len(p)
	}
	n, err = reader.dst.Read(p[0:n])
	reader.h.Write(p[0:n])
	if err == io.EOF {
		if !reader.eof {
			panic("something went wrong!")
		}
		reader.md5SumAvailable = true
	} else if err == nil {
		if n == 0 && reader.eof {
			err = io.EOF
		}
	}
	return n, err
}

func (reader *CompressingBlobReader) size() (int64, error) {
	if reader.s == nil {
		return -1, errors.New("already closed")
	}
	if reader.cw != nil {
		err := reader.drainAll()
		if err != nil {
			return -1, err
		}
	}
	size, err := reader.s.Size()
	if err != nil {
		return -1, err // should never happen
	}
	return size, nil
}

func (reader *CompressingBlobReader) Close() error {
	bwerr := (error)(nil)
	errs := make([]error, 0, 4)
	err := reader.ensureMD5SumAvailble()
	if err != nil {
		return err
	}
	if reader.cw != nil {
		if err != nil {
			return err
		}
		err = reader.cw.Close()
		if err == nil {
			reader.cw = nil
		} else {
			errs = append(errs, err)
		}
	}
	if reader.bw != nil {
		bwerr = reader.bw.Flush()
		if bwerr == nil {
			reader.bw = nil
		} else {
			errs = append(errs, bwerr)
		}
	}
	if reader.src != nil {
		err := reader.src.Close()
		if err == nil {
			reader.src = nil
		} else {
			errs = append(errs, err)
		}
	}
	if bwerr == nil {
		if reader.s != nil {
			err := reader.s.Close()
			if err == nil {
				reader.s = nil
			} else {
				errs = append(errs, err)
			}
		}
	}
	if len(errs) > 0 {
		return Errors(errs)
	} else {
		reader.closeNotify(reader)
		return nil
	}
}

func (reader *CompressingBlobReader) ensureMD5SumAvailble() error {
	if reader.md5SumAvailable {
		return nil
	}
	if reader.s == nil {
		return errors.New("already closed")
	}
	err := reader.drainAll()
	if err != nil {
		return err
	}
	r := *reader.dst
	_, err = io.Copy(reader.h, &r)
	if err != nil {
		return err
	}
	reader.md5SumAvailable = true
	return nil
}

func (reader *CompressingBlobReader) md5sum() ([]byte, error) {
	err := reader.ensureMD5SumAvailble()
	if err != nil {
		return nil, err
	}
	retval := make([]byte, 0, reader.h.Size())
	return reader.h.Sum(retval), nil
}

func (blob *CompressingBlob) newReader() (*CompressingBlobReader, error) {
	err := (error)(nil)
	src := (io.ReadCloser)(nil)
	s := (SizedRandomAccessStore)(nil)
	defer func() {
		if err != nil {
			if src != nil {
				src.Close()
			}
			if s != nil {
				s.Close()
			}
		}
	}()
	src, err = blob.inner.Reader()
	if err != nil {
		return nil, err
	}
	s_, err := blob.tempFactory.RandomAccessStore()
	if err != nil {
		return nil, err
	}
	s = s_.(SizedRandomAccessStore)
	w := &StoreReadWriter{s, 0, -1}
	dst := &StoreReadWriter{s, 0, -1}
	// assuming average compression ratio to be 1/3
	writeBufferSize := maxInt(4096, blob.bufferSize/3)
	bw := bufio.NewWriterSize(w, writeBufferSize)
	cw, err := gzip.NewWriterLevel(bw, blob.level)
	if err != nil {
		return nil, err
	}
	return &CompressingBlobReader{
		buf: make([]byte, blob.bufferSize),
		o:   0,
		src: src,
		dst: dst,
		s:   s,
		w:   w,
		bw:  bw,
		cw:  cw,
		eof: false,
		h:   md5.New(),
		md5SumAvailable: false,
		closeNotify: func(reader *CompressingBlobReader) {
			md5sum, err := reader.md5sum()
			if err == nil {
				blob.md5sum = md5sum
			}
			size, err := reader.size()
			if err == nil {
				blob.size = size
			}
			blob.reader = nil
		},
	}, nil
}

func (blob *CompressingBlob) ensureReaderAvailable() error {
	if blob.reader != nil {
		return nil
	}
	reader, err := blob.newReader()
	if err != nil {
		return err
	}
	blob.reader = reader
	blob.md5sum = nil
	blob.size = -1
	return nil
}

func (blob *CompressingBlob) Reader() (io.ReadCloser, error) {
	err := blob.ensureReaderAvailable()
	if err != nil {
		return nil, err
	}
	return blob.reader, nil
}

func (blob *CompressingBlob) Size() (int64, error) {
	if blob.size < 0 {
		err := blob.ensureReaderAvailable()
		if err != nil {
			return -1, err
		}
		size, err := blob.reader.size()
		if err != nil {
			return -1, err
		}
		blob.size = size
	}
	return blob.size, nil
}

func (blob *CompressingBlob) MD5Sum() ([]byte, error) {
	if blob.md5sum == nil {
		err := blob.ensureReaderAvailable()
		if err != nil {
			return nil, err
		}
		md5sum, err := blob.reader.md5sum()
		if err != nil {
			return nil, err
		}
		blob.md5sum = md5sum
	}
	return blob.md5sum, nil
}

func (blob *CompressingBlob) Dispose() error {
	if blob.reader != nil {
		return blob.reader.Close()
	}
	return nil
}

func NewCompressingBlob(blob td_client.Blob, bufferSize int, level int, tempFactory RandomAccessStoreFactory) *CompressingBlob {
	return &CompressingBlob{
		inner:       blob,
		level:       level,
		bufferSize:  bufferSize,
		reader:      nil,
		tempFactory: tempFactory,
		md5sum:      nil,
		size:        -1,
	}
}
<commit_msg>Add missing md5SumAvailable = true<commit_after>package fluentd_forwarder

import (
	"bufio"
	"compress/gzip"
	"crypto/md5"
	"errors"
	td_client "github.com/treasure-data/td-client-go"
	"hash"
	"io"
	"os"
)

type CompressingBlob struct {
	inner       td_client.Blob
	level       int
	bufferSize  int
	reader      *CompressingBlobReader
	tempFactory RandomAccessStoreFactory
	md5sum      []byte
	size        int64
}

type CompressingBlobReader struct {
	buf         []byte // ring buffer
	o           int
	src         io.ReadCloser
	dst         *StoreReadWriter
	s           SizedRandomAccessStore
	w           *StoreReadWriter
	bw          *bufio.Writer
	cw          *gzip.Writer
	h           hash.Hash
	eof         bool
	md5SumAvailable   bool
	closeNotify func(*CompressingBlobReader)
}

func (reader *CompressingBlobReader) drainAll() error {
	rn := 0
	err := (error)(nil)
	for reader.cw != nil && err == nil {
		o := reader.o
		if !reader.eof {
			rn, err = reader.src.Read(reader.buf[reader.o:cap(reader.buf)])
			o += rn
			if err == io.EOF {
				reader.eof = true
			}
		} else {
			if o == 0 {
				break
			}
		}
		if o > 0 {
			wn, werr := reader.cw.Write(reader.buf[0:o])
			if werr != nil {
				err = werr
			}
			copy(reader.buf[0:], reader.buf[wn:])
			reader.o = o - wn
		}
		if err != nil {
			reader.cw.Close()
			reader.cw = nil
			werr := reader.bw.Flush()
			if werr != nil {
				return werr
			}
			reader.bw = nil
		}
	}
	if err == io.EOF {
		err = nil
	}
	return err
}

func (reader *CompressingBlobReader) Read(p []byte) (int, error) {
	n := int(0)
	err := (error)(nil)
	rn := int(0)
	rpos, err := reader.dst.Seek(0, os.SEEK_CUR)
	if err != nil {
		return 0, err // should never happen
	}
	wpos, err := reader.w.Seek(0, os.SEEK_CUR)
	if err != nil {
		return 0, err // should never happen
	}
	for {
		n = int(wpos - rpos) // XXX: may underflow, but it should be ok
		if err != nil || len(p) <= n || reader.cw == nil {
			break
		}
		o := reader.o
		if !reader.eof {
			rn, err = reader.src.Read(reader.buf[reader.o:cap(reader.buf)])
			o += rn
			if err == io.EOF {
				reader.eof = true
			}
		}
		if o > 0 {
			wn, werr := reader.cw.Write(reader.buf[0:o])
			copy(reader.buf[0:], reader.buf[wn:o])
			reader.o = o - wn
			if werr != nil {
				return 0, werr
			}
		} else {
			if reader.eof && reader.cw != nil {
				reader.cw.Close()
				reader.cw = nil
				werr := reader.bw.Flush()
				if werr != nil {
					return 0, werr
				}
				reader.bw = nil
			}
		}
		var werr error
		wpos, werr = reader.w.Seek(0, os.SEEK_CUR)
		if werr != nil {
			return 0, werr // should never happen
		}
	}
	if len(p) > 0 && n == 0 && reader.eof {
		reader.md5SumAvailable = true
		return 0, io.EOF
	}
	if n > len(p) {
		n = len(p)
	}
	n, err = reader.dst.Read(p[0:n])
	reader.h.Write(p[0:n])
	if err == io.EOF {
		if !reader.eof {
			panic("something went wrong!")
		}
		reader.md5SumAvailable = true
	}
	return n, err
}

func (reader *CompressingBlobReader) size() (int64, error) {
	if reader.s == nil {
		return -1, errors.New("already closed")
	}
	if reader.cw != nil {
		err := reader.drainAll()
		if err != nil {
			return -1, err
		}
	}
	size, err := reader.s.Size()
	if err != nil {
		return -1, err // should never happen
	}
	return size, nil
}

func (reader *CompressingBlobReader) Close() error {
	bwerr := (error)(nil)
	errs := make([]error, 0, 4)
	err := reader.ensureMD5SumAvailble()
	if err != nil {
		return err
	}
	if reader.cw != nil {
		if err != nil {
			return err
		}
		err = reader.cw.Close()
		if err == nil {
			reader.cw = nil
		} else {
			errs = append(errs, err)
		}
	}
	if reader.bw != nil {
		bwerr = reader.bw.Flush()
		if bwerr == nil {
			reader.bw = nil
		} else {
			errs = append(errs, bwerr)
		}
	}
	if reader.src != nil {
		err := reader.src.Close()
		if err == nil {
			reader.src = nil
		} else {
			errs = append(errs, err)
		}
	}
	if bwerr == nil {
		if reader.s != nil {
			err := reader.s.Close()
			if err == nil {
				reader.s = nil
			} else {
				errs = append(errs, err)
			}
		}
	}
	if len(errs) > 0 {
		return Errors(errs)
	} else {
		reader.closeNotify(reader)
		return nil
	}
}

func (reader *CompressingBlobReader) ensureMD5SumAvailble() error {
	if reader.md5SumAvailable {
		return nil
	}
	if reader.s == nil {
		return errors.New("already closed")
	}
	err := reader.drainAll()
	if err != nil {
		return err
	}
	r := *reader.dst
	_, err = io.Copy(reader.h, &r)
	if err != nil {
		return err
	}
	reader.md5SumAvailable = true
	return nil
}

func (reader *CompressingBlobReader) md5sum() ([]byte, error) {
	err := reader.ensureMD5SumAvailble()
	if err != nil {
		return nil, err
	}
	retval := make([]byte, 0, reader.h.Size())
	return reader.h.Sum(retval), nil
}

func (blob *CompressingBlob) newReader() (*CompressingBlobReader, error) {
	err := (error)(nil)
	src := (io.ReadCloser)(nil)
	s := (SizedRandomAccessStore)(nil)
	defer func() {
		if err != nil {
			if src != nil {
				src.Close()
			}
			if s != nil {
				s.Close()
			}
		}
	}()
	src, err = blob.inner.Reader()
	if err != nil {
		return nil, err
	}
	s_, err := blob.tempFactory.RandomAccessStore()
	if err != nil {
		return nil, err
	}
	s = s_.(SizedRandomAccessStore)
	w := &StoreReadWriter{s, 0, -1}
	dst := &StoreReadWriter{s, 0, -1}
	// assuming average compression ratio to be 1/3
	writeBufferSize := maxInt(4096, blob.bufferSize/3)
	bw := bufio.NewWriterSize(w, writeBufferSize)
	cw, err := gzip.NewWriterLevel(bw, blob.level)
	if err != nil {
		return nil, err
	}
	return &CompressingBlobReader{
		buf: make([]byte, blob.bufferSize),
		o:   0,
		src: src,
		dst: dst,
		s:   s,
		w:   w,
		bw:  bw,
		cw:  cw,
		eof: false,
		h:   md5.New(),
		md5SumAvailable: false,
		closeNotify: func(reader *CompressingBlobReader) {
			md5sum, err := reader.md5sum()
			if err == nil {
				blob.md5sum = md5sum
			}
			size, err := reader.size()
			if err == nil {
				blob.size = size
			}
			blob.reader = nil
		},
	}, nil
}

func (blob *CompressingBlob) ensureReaderAvailable() error {
	if blob.reader != nil {
		return nil
	}
	reader, err := blob.newReader()
	if err != nil {
		return err
	}
	blob.reader = reader
	blob.md5sum = nil
	blob.size = -1
	return nil
}

func (blob *CompressingBlob) Reader() (io.ReadCloser, error) {
	err := blob.ensureReaderAvailable()
	if err != nil {
		return nil, err
	}
	return blob.reader, nil
}

func (blob *CompressingBlob) Size() (int64, error) {
	if blob.size < 0 {
		err := blob.ensureReaderAvailable()
		if err != nil {
			return -1, err
		}
		size, err := blob.reader.size()
		if err != nil {
			return -1, err
		}
		blob.size = size
	}
	return blob.size, nil
}

func (blob *CompressingBlob) MD5Sum() ([]byte, error) {
	if blob.md5sum == nil {
		err := blob.ensureReaderAvailable()
		if err != nil {
			return nil, err
		}
		md5sum, err := blob.reader.md5sum()
		if err != nil {
			return nil, err
		}
		blob.md5sum = md5sum
	}
	return blob.md5sum, nil
}

func (blob *CompressingBlob) Dispose() error {
	if blob.reader != nil {
		return blob.reader.Close()
	}
	return nil
}

func NewCompressingBlob(blob td_client.Blob, bufferSize int, level int, tempFactory RandomAccessStoreFactory) *CompressingBlob {
	return &CompressingBlob{
		inner:       blob,
		level:       level,
		bufferSize:  bufferSize,
		reader:      nil,
		tempFactory: tempFactory,
		md5sum:      nil,
		size:        -1,
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

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

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

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

package version

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
)

// CurrentGaugeVersion represents the current version of Gauge
var CurrentGaugeVersion = &Version{0, 9, 6}

// BuildMetadata represents build information of current release (e.g, nightly build information)
var BuildMetadata = ""
var CommitHash = ""

type Version struct {
	Major int
	Minor int
	Patch int
}

type VersionSupport struct {
	Minimum string
	Maximum string
}

func ParseVersion(versionText string) (*Version, error) {
	splits := strings.Split(versionText, ".")
	if len(splits) != 3 {
		return nil, fmt.Errorf("Incorrect Version format. Version should be in the form 1.5.7")
	}
	Major, err := strconv.Atoi(splits[0])
	if err != nil {
		return nil, VersionError("major", splits[0], err)
	}
	Minor, err := strconv.Atoi(splits[1])
	if err != nil {
		return nil, VersionError("minor", splits[1], err)
	}
	Patch, err := strconv.Atoi(splits[2])
	if err != nil {
		return nil, VersionError("patch", splits[2], err)
	}

	return &Version{Major, Minor, Patch}, nil
}

func VersionError(level, text string, err error) error {
	return fmt.Errorf("Error parsing %s Version %s to integer. %s", level, text, err.Error())
}

func (Version *Version) IsBetween(lower *Version, greater *Version) bool {
	return Version.IsGreaterThanEqualTo(lower) && Version.IsLesserThanEqualTo(greater)
}

func (Version *Version) IsLesserThan(version1 *Version) bool {
	return CompareVersions(Version, version1, LesserThanFunc)
}

func (Version *Version) IsGreaterThan(version1 *Version) bool {
	return CompareVersions(Version, version1, GreaterThanFunc)
}

func (Version *Version) IsLesserThanEqualTo(version1 *Version) bool {
	return Version.IsLesserThan(version1) || Version.IsEqualTo(version1)
}

func (Version *Version) IsGreaterThanEqualTo(version1 *Version) bool {
	return Version.IsGreaterThan(version1) || Version.IsEqualTo(version1)
}

func (Version *Version) IsEqualTo(version1 *Version) bool {
	return IsEqual(Version.Major, version1.Major) && IsEqual(Version.Minor, version1.Minor) && IsEqual(Version.Patch, version1.Patch)
}

func CompareVersions(first *Version, second *Version, compareFunc func(int, int) bool) bool {
	if compareFunc(first.Major, second.Major) {
		return true
	} else if IsEqual(first.Major, second.Major) {
		if compareFunc(first.Minor, second.Minor) {
			return true
		} else if IsEqual(first.Minor, second.Minor) {
			if compareFunc(first.Patch, second.Patch) {
				return true
			}
			return false
		}
	}
	return false
}

func LesserThanFunc(first, second int) bool {
	return first < second
}

func GreaterThanFunc(first, second int) bool {
	return first > second
}

func IsEqual(first, second int) bool {
	return first == second
}

func (Version *Version) String() string {
	return fmt.Sprintf("%d.%d.%d", Version.Major, Version.Minor, Version.Patch)
}

// FullVersion returns the CurrentGaugeVersion including build metadata.
func FullVersion() string {
	var metadata string
	if BuildMetadata != "" {
		metadata = fmt.Sprintf(".%s", BuildMetadata)
	}
	return fmt.Sprintf("%s%s", CurrentGaugeVersion.String(), metadata)
}

func GetCommitHash() string {
	var commitHash string
	if CommitHash != "" {
		commitHash = fmt.Sprintf("%s", CommitHash)
	}
	return fmt.Sprintf("%s", commitHash)
}

type byDecreasingVersion []*Version

func (a byDecreasingVersion) Len() int      { return len(a) }
func (a byDecreasingVersion) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byDecreasingVersion) Less(i, j int) bool {
	return a[i].IsGreaterThan(a[j])
}

func GetLatestVersion(versions []*Version) *Version {
	sort.Sort(byDecreasingVersion(versions))
	return versions[0]
}

func CheckCompatibility(currentVersion *Version, versionSupport *VersionSupport) error {
	minSupportVersion, err := ParseVersion(versionSupport.Minimum)
	if err != nil {
		return fmt.Errorf("Invalid minimum support version %s. : %s. ", versionSupport.Minimum, err.Error())
	}
	if versionSupport.Maximum != "" {
		maxSupportVersion, err := ParseVersion(versionSupport.Maximum)
		if err != nil {
			return fmt.Errorf("Invalid maximum support version %s. : %s. ", versionSupport.Maximum, err.Error())
		}
		if currentVersion.IsBetween(minSupportVersion, maxSupportVersion) {
			return nil
		}
		return fmt.Errorf("Version %s is not between %s and %s", currentVersion, minSupportVersion, maxSupportVersion)
	}

	if minSupportVersion.IsLesserThanEqualTo(currentVersion) {
		return nil
	}
	return fmt.Errorf("Incompatible version. Minimum support version %s is higher than current version %s", minSupportVersion, currentVersion)
}
<commit_msg>Bumped up version to 0.9.7 for further development<commit_after>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

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

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

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

package version

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
)

// CurrentGaugeVersion represents the current version of Gauge
var CurrentGaugeVersion = &Version{0, 9, 7}

// BuildMetadata represents build information of current release (e.g, nightly build information)
var BuildMetadata = ""
var CommitHash = ""

type Version struct {
	Major int
	Minor int
	Patch int
}

type VersionSupport struct {
	Minimum string
	Maximum string
}

func ParseVersion(versionText string) (*Version, error) {
	splits := strings.Split(versionText, ".")
	if len(splits) != 3 {
		return nil, fmt.Errorf("Incorrect Version format. Version should be in the form 1.5.7")
	}
	Major, err := strconv.Atoi(splits[0])
	if err != nil {
		return nil, VersionError("major", splits[0], err)
	}
	Minor, err := strconv.Atoi(splits[1])
	if err != nil {
		return nil, VersionError("minor", splits[1], err)
	}
	Patch, err := strconv.Atoi(splits[2])
	if err != nil {
		return nil, VersionError("patch", splits[2], err)
	}

	return &Version{Major, Minor, Patch}, nil
}

func VersionError(level, text string, err error) error {
	return fmt.Errorf("Error parsing %s Version %s to integer. %s", level, text, err.Error())
}

func (Version *Version) IsBetween(lower *Version, greater *Version) bool {
	return Version.IsGreaterThanEqualTo(lower) && Version.IsLesserThanEqualTo(greater)
}

func (Version *Version) IsLesserThan(version1 *Version) bool {
	return CompareVersions(Version, version1, LesserThanFunc)
}

func (Version *Version) IsGreaterThan(version1 *Version) bool {
	return CompareVersions(Version, version1, GreaterThanFunc)
}

func (Version *Version) IsLesserThanEqualTo(version1 *Version) bool {
	return Version.IsLesserThan(version1) || Version.IsEqualTo(version1)
}

func (Version *Version) IsGreaterThanEqualTo(version1 *Version) bool {
	return Version.IsGreaterThan(version1) || Version.IsEqualTo(version1)
}

func (Version *Version) IsEqualTo(version1 *Version) bool {
	return IsEqual(Version.Major, version1.Major) && IsEqual(Version.Minor, version1.Minor) && IsEqual(Version.Patch, version1.Patch)
}

func CompareVersions(first *Version, second *Version, compareFunc func(int, int) bool) bool {
	if compareFunc(first.Major, second.Major) {
		return true
	} else if IsEqual(first.Major, second.Major) {
		if compareFunc(first.Minor, second.Minor) {
			return true
		} else if IsEqual(first.Minor, second.Minor) {
			if compareFunc(first.Patch, second.Patch) {
				return true
			}
			return false
		}
	}
	return false
}

func LesserThanFunc(first, second int) bool {
	return first < second
}

func GreaterThanFunc(first, second int) bool {
	return first > second
}

func IsEqual(first, second int) bool {
	return first == second
}

func (Version *Version) String() string {
	return fmt.Sprintf("%d.%d.%d", Version.Major, Version.Minor, Version.Patch)
}

// FullVersion returns the CurrentGaugeVersion including build metadata.
func FullVersion() string {
	var metadata string
	if BuildMetadata != "" {
		metadata = fmt.Sprintf(".%s", BuildMetadata)
	}
	return fmt.Sprintf("%s%s", CurrentGaugeVersion.String(), metadata)
}

func GetCommitHash() string {
	var commitHash string
	if CommitHash != "" {
		commitHash = fmt.Sprintf("%s", CommitHash)
	}
	return fmt.Sprintf("%s", commitHash)
}

type byDecreasingVersion []*Version

func (a byDecreasingVersion) Len() int      { return len(a) }
func (a byDecreasingVersion) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byDecreasingVersion) Less(i, j int) bool {
	return a[i].IsGreaterThan(a[j])
}

func GetLatestVersion(versions []*Version) *Version {
	sort.Sort(byDecreasingVersion(versions))
	return versions[0]
}

func CheckCompatibility(currentVersion *Version, versionSupport *VersionSupport) error {
	minSupportVersion, err := ParseVersion(versionSupport.Minimum)
	if err != nil {
		return fmt.Errorf("Invalid minimum support version %s. : %s. ", versionSupport.Minimum, err.Error())
	}
	if versionSupport.Maximum != "" {
		maxSupportVersion, err := ParseVersion(versionSupport.Maximum)
		if err != nil {
			return fmt.Errorf("Invalid maximum support version %s. : %s. ", versionSupport.Maximum, err.Error())
		}
		if currentVersion.IsBetween(minSupportVersion, maxSupportVersion) {
			return nil
		}
		return fmt.Errorf("Version %s is not between %s and %s", currentVersion, minSupportVersion, maxSupportVersion)
	}

	if minSupportVersion.IsLesserThanEqualTo(currentVersion) {
		return nil
	}
	return fmt.Errorf("Incompatible version. Minimum support version %s is higher than current version %s", minSupportVersion, currentVersion)
}
<|endoftext|>


<commit_before>package pinyin

import (
	"strings"
)

type Args struct {
	Style     string
	Heteronym bool
}

func SinglePinyin(r rune, a Args) []string {
	value, ok := PinyinDict[int(r)]
	pys := []string{}
	if ok {
		if len(value) < 1 || a.Heteronym {
			pys = strings.Split(value, ",")
		} else {
			pys = strings.Split(value, ",")[:1]
		}
	}
	return pys
}

func Pinyin(s string, a Args) [][]string {
	hans := []rune(s)
	pys := [][]string{}
	for _, r := range hans {
		pys = append(pys, SinglePinyin(r, a))
	}
	return pys
}
<commit_msg>定义拼音风格有关的 const<commit_after>package pinyin

import (
	"strings"
)

const (
	NORMAL       = 0 // 普通风格，不带声调。如： pin yin
	TONE         = 1 // 声调风格1，拼音声调在韵母第一个字母上（默认风格）。如： pīn yīn
	TONE2        = 2 // 声调风格2，即拼音声调在各个拼音之后，用数字 [0-4] 进行表示。如： pi1n yi1n
	INITIALS     = 3 // 声母风格，只返回各个拼音的声母部分。如： 中国 的拼音 zh g
	FIRST_LETTER = 4 // 首字母风格，只返回拼音的首字母部分。如： p y
	FINALS       = 5 // 韵母风格1，只返回各个拼音的韵母部分，不带声调。如： ong uo
	FINALS_TONE  = 6 // 韵母风格2，带声调，声调在韵母第一个字母上。如： ōng uó
	FINALS_TONE2 = 7 // 韵母风格2，带声调，声调在各个拼音之后，用数字 [0-4] 进行表示。如： o1ng uo2
)

type Args struct {
	Style     int
	Heteronym bool
}

func SinglePinyin(r rune, a Args) []string {
	value, ok := PinyinDict[int(r)]
	pys := []string{}
	if ok {
		if len(value) < 1 || a.Heteronym {
			pys = strings.Split(value, ",")
		} else {
			pys = strings.Split(value, ",")[:1]
		}
	}
	return pys
}

func Pinyin(s string, a Args) [][]string {
	hans := []rune(s)
	pys := [][]string{}
	for _, r := range hans {
		pys = append(pys, SinglePinyin(r, a))
	}
	return pys
}
<|endoftext|>


<commit_before>package vnet

import (
	"github.com/megamsys/opennebula-go/api"
	"gopkg.in/check.v1"
	"testing"
)

func Test(t *testing.T) {
	check.TestingT(t)
}

type S struct {
	cm map[string]string
}

var _ = check.Suite(&S{})

func (s *S) SetUpSuite(c *check.C) {
	cm := make(map[string]string)
	cm[api.ENDPOINT] = "http://188.240.231.85:2666/RPC2"
	cm[api.USERID] = "oneadmin"
	cm[api.PASSWORD] = "GhatpewfAut6"
	s.cm = cm
}

func (s *S) TestGetVnetInfos(c *check.C) {
	cl, _ := api.NewClient(s.cm)
	vm := VNETemplate{T: cl}
	_, err := vm.VnetInfos([]int{0})
	// for _, addr := range res[0].AddrPool.Addrs {
	// 	for _, leases := range addr.Leases {
	//     for i, lease := range leases.Leases {
	//       fmt.Printf("\n\n %v  %#v     ",i,lease)
	//      }
	// 		}
	// 	}
	c.Assert(err, check.NotNil)
}

/*
func (s *S) TestVnetCreate(c *check.C) {
	cl, _ := api.NewClient(s.cm)
  temp := Vnet{}
  ar := &Address{
      Type: "IP4",
      Size: "1",
      StartIP: "192.168.1.128",
    }
  temp.Addrs = append(temp.Addrs,ar)
  t := Vnet{
    Name: "vnet2",
    Type: "fixed",
    Description: "vnet for iPV4 ",
    Bridge: "one",
    Network_addr: "10.0.0.0",
    Network_mask: "255.255.255.0",
    Dns: "10.0.0.1",
    Gateway: "10.0.0.1",
    Vn_mad: "dummy",
    Addrs: temp.Addrs,
  }
	v := VNETemplate{T: cl, Template: t}

	c.Assert(v, check.NotNil)
	res, err := v.CreateVnet(-1)
	fmt.Println(res)
	err = nil
	c.Assert(err, check.NotNil)
}
*/
// func (s *S) TestGetVNets(c *check.C) {
// 	client, _ := api.NewClient(s.cm)
// 	vm := VNETemplate{T: client}
// 	_, err := vm.VnetInfos(2)
//   err = nil
// 	c.Assert(err, check.NotNil)
// }

// func (s *S) TestListVNets(c *check.C) {
// 	client, _ := api.NewClient(s.cm)
// 	vm := VNetPool{T: client}
//    err := vm.VnetPoolInfos(-1)
// 	 c.Assert(err, check.IsNil)
// 	 for _, i := range vm.Vnets {
// 		fmt.Println(i.Name, "  =    " , i.TotalIps)
// 	 }
//   err = fmt.Errorf("test")
// 	c.Assert(err, check.IsNil)
// }

// func (s *S) TestVnetAddIp(c *check.C) {
// 	cl, _ := api.NewClient(s.cm)
//   temp := Vnet{}
//   ar := &Address{
//       Type: "IP4",
//       Size: "1",
//       StartIP: "192.168.1.104",
//     }
//   var i int = 0
//   temp.Addrs = append(temp.Addrs,ar)
//   t := Vnet{
//     Id:  i,
//     Addrs: temp.Addrs,
//   }
//   v := VNETemplate{T: cl, Template: t}
//
//   c.Assert(v, check.NotNil)
//   res, err := v.VnetAddIps()
//   c.Assert(err, check.IsNil)
// }
// */
<commit_msg>removet test keys<commit_after>package vnet

import (
	"github.com/megamsys/opennebula-go/api"
	"gopkg.in/check.v1"
	"testing"
)

func Test(t *testing.T) {
	check.TestingT(t)
}

type S struct {
	cm map[string]string
}

var _ = check.Suite(&S{})

func (s *S) SetUpSuite(c *check.C) {
	cm := make(map[string]string)
	cm[api.ENDPOINT] = "http://192.168.0.100:2666/RPC2"
	cm[api.USERID] = "oneadmin"
	cm[api.PASSWORD] = "asdf"
	s.cm = cm
}

func (s *S) TestGetVnetInfos(c *check.C) {
	cl, _ := api.NewClient(s.cm)
	vm := VNETemplate{T: cl}
	_, err := vm.VnetInfos([]int{0})
	// for _, addr := range res[0].AddrPool.Addrs {
	// 	for _, leases := range addr.Leases {
	//     for i, lease := range leases.Leases {
	//       fmt.Printf("\n\n %v  %#v     ",i,lease)
	//      }
	// 		}
	// 	}
	c.Assert(err, check.NotNil)
}

/*
func (s *S) TestVnetCreate(c *check.C) {
	cl, _ := api.NewClient(s.cm)
  temp := Vnet{}
  ar := &Address{
      Type: "IP4",
      Size: "1",
      StartIP: "192.168.1.128",
    }
  temp.Addrs = append(temp.Addrs,ar)
  t := Vnet{
    Name: "vnet2",
    Type: "fixed",
    Description: "vnet for iPV4 ",
    Bridge: "one",
    Network_addr: "10.0.0.0",
    Network_mask: "255.255.255.0",
    Dns: "10.0.0.1",
    Gateway: "10.0.0.1",
    Vn_mad: "dummy",
    Addrs: temp.Addrs,
  }
	v := VNETemplate{T: cl, Template: t}

	c.Assert(v, check.NotNil)
	res, err := v.CreateVnet(-1)
	fmt.Println(res)
	err = nil
	c.Assert(err, check.NotNil)
}
*/
// func (s *S) TestGetVNets(c *check.C) {
// 	client, _ := api.NewClient(s.cm)
// 	vm := VNETemplate{T: client}
// 	_, err := vm.VnetInfos(2)
//   err = nil
// 	c.Assert(err, check.NotNil)
// }

// func (s *S) TestListVNets(c *check.C) {
// 	client, _ := api.NewClient(s.cm)
// 	vm := VNetPool{T: client}
//    err := vm.VnetPoolInfos(-1)
// 	 c.Assert(err, check.IsNil)
// 	 for _, i := range vm.Vnets {
// 		fmt.Println(i.Name, "  =    " , i.TotalIps)
// 	 }
//   err = fmt.Errorf("test")
// 	c.Assert(err, check.IsNil)
// }

// func (s *S) TestVnetAddIp(c *check.C) {
// 	cl, _ := api.NewClient(s.cm)
//   temp := Vnet{}
//   ar := &Address{
//       Type: "IP4",
//       Size: "1",
//       StartIP: "192.168.1.104",
//     }
//   var i int = 0
//   temp.Addrs = append(temp.Addrs,ar)
//   t := Vnet{
//     Id:  i,
//     Addrs: temp.Addrs,
//   }
//   v := VNETemplate{T: cl, Template: t}
//
//   c.Assert(v, check.NotNil)
//   res, err := v.VnetAddIps()
//   c.Assert(err, check.IsNil)
// }
// */
<|endoftext|>


<commit_before>package broker

import (
	"context"
	"sync"
	"time"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/celrenheit/sandflake"
	"github.com/gogo/protobuf/proto"

	"github.com/celrenheit/sandglass-grpc/go/sgproto"
	"golang.org/x/sync/errgroup"
)

var (
	RedeliveryTimeout = 10 * time.Second
)

type ConsumerGroup struct {
	broker    *Broker
	topic     string
	partition string
	name      string
	mu        sync.RWMutex
	receivers []*receiver
}

func NewConsumerGroup(b *Broker, topic, partition, name string) *ConsumerGroup {
	return &ConsumerGroup{
		broker:    b,
		name:      name,
		topic:     topic,
		partition: partition,
	}
}

type receiver struct {
	name   string
	msgCh  chan *sgproto.Message
	doneCh chan struct{}
}

func (c *ConsumerGroup) register(consumerName string) *receiver {
	r := c.getReceiver(consumerName)
	if r != nil {
		return r
	}

	c.mu.Lock()
	defer c.mu.Unlock()

	r = &receiver{
		name:   consumerName,
		msgCh:  make(chan *sgproto.Message),
		doneCh: make(chan struct{}),
	}
	c.receivers = append(c.receivers, r)

	if len(c.receivers) == 1 {
		go c.consumeLoop()
	}

	return r
}

func (c *ConsumerGroup) consumeLoop() {
	defer func() { // close receivers for whatever reason
		c.mu.Lock()
		for _, r := range c.receivers {
			close(r.msgCh)
			close(r.doneCh)
		}
		c.receivers = c.receivers[:0]
		c.mu.Unlock()
	}()

	lastCommited, err := c.broker.LastOffset(context.TODO(), c.topic, c.partition, c.name, "", sgproto.MarkKind_Commited)
	if err != nil {
		c.broker.Debug("got error when fetching last committed offset: %v ", err)
		return
	}

	from, err := c.broker.LastOffset(context.TODO(), c.topic, c.partition, c.name, "", sgproto.MarkKind_Consumed)
	if err != nil {
		c.broker.Debug("got error when fetching last committed offset: %v ", err)
		return
	}

	msgCh := make(chan *sgproto.Message)
	var group errgroup.Group

	if !lastCommited.Equal(from) {
		group.Go(func() error {
			var (
				lastMessage *sgproto.Message
				committed   = false
			)
			req := &sgproto.FetchRangeRequest{
				Topic:     c.topic,
				Partition: c.partition,
				From:      lastCommited,
				To:        from,
			}

			commit := func(offset sandflake.ID) {
				_, err := c.broker.Commit(context.TODO(), c.topic, c.partition, c.name, "", lastMessage.Offset)
				if err != nil {
					c.broker.Debug("unable to commit")
				}
			}

			i := 0
			err := c.broker.FetchRange(context.TODO(), req, func(m *sgproto.Message) error {
				if m.Offset.Equal(lastCommited) { // skip first item, since it is already committed
					lastMessage = m
					return nil
				}
				i++

				msg, err := c.broker.GetMarkStateMessage(context.TODO(), c.topic, c.partition, c.name, "", m.Offset)
				if err != nil {
					s, ok := status.FromError(err)
					if !ok || s.Code() != codes.NotFound {
						return err
					}
				}

				var state sgproto.MarkState
				if msg != nil {
					err := proto.Unmarshal(msg.Value, &state)
					if err != nil {
						return err
					}
				}

				// advance commit offset
				// if we only got acked messages before
				if !committed && lastMessage != nil {
					if state.Kind != sgproto.MarkKind_Acknowledged {
						// we might commit in a goroutine, we can redo this the next time we consume
						if !lastMessage.Offset.Equal(lastCommited) {
							commit(lastMessage.Offset)
						}
						committed = true
					} else if i%10000 == 0 {
						go commit(lastMessage.Offset)
					}
				}
				lastMessage = m

				if shouldRedeliver(m.Index, state) {
					msgCh <- m // deliver

					if state.Kind != sgproto.MarkKind_Unknown {
						state.DeliveryCount++
						msg.Value, err = proto.Marshal(&state)
						if err != nil {
							return err
						}

						if _, err := c.broker.Produce(context.TODO(), &sgproto.ProduceMessageRequest{
							Topic:    ConsumerOffsetTopicName,
							Messages: []*sgproto.Message{msg},
						}); err != nil {
							return err
						}
					}
				}

				return nil
			})
			if err != nil {
				return err
			}

			if !committed && lastMessage != nil {
				commit(lastMessage.Offset)
			}

			return nil
		})
	}
	group.Go(func() error {
		now := sandflake.NewID(time.Now().UTC(), sandflake.MaxID.WorkerID(), sandflake.MaxID.Sequence(), sandflake.MaxID.RandomBytes())
		req := &sgproto.FetchRangeRequest{
			Topic:     c.topic,
			Partition: c.partition,
			From:      from,
			To:        now,
		}

		return c.broker.FetchRange(context.TODO(), req, func(m *sgproto.Message) error {
			// skip the first if it is the same as the starting point
			if from == m.Offset {
				return nil
			}

			msgCh <- m

			return nil
		})
	})

	go func() {
		err := group.Wait()
		if err != nil {
			c.broker.Info("error in consumeLoop: %v", err)
		}
		close(msgCh)
	}()

	var i int
	var m *sgproto.Message
loop:
	for m = range msgCh {
		// select receiver
	selectreceiver:
		i++
		c.mu.RLock()
		r := c.receivers[i%len(c.receivers)]
		c.mu.RUnlock()

		select {
		case <-r.doneCh:
			if c.removeConsumer(r.name) {
				c.mu.RLock()
				l := len(c.receivers)
				c.mu.RUnlock()

				if l == 0 {
					break loop
				}

				goto selectreceiver // select another receiver
			}
		case r.msgCh <- m:
		}
	}

	if m != nil && !m.Offset.Equal(from) {
		_, err := c.broker.MarkConsumed(context.TODO(), c.topic, c.partition, c.name, "REMOVE THIS", m.Offset)
		if err != nil {
			c.broker.Debug("unable to mark as consumed: %v", err)
		}
	}
}

func shouldRedeliver(index sandflake.ID, state sgproto.MarkState) bool {
	switch state.Kind {
	case sgproto.MarkKind_NotAcknowledged:
		return true
	case sgproto.MarkKind_Consumed, sgproto.MarkKind_Unknown: // inflight
		return index.Time().Add(RedeliveryTimeout).Before(time.Now().UTC())
	case sgproto.MarkKind_Acknowledged, sgproto.MarkKind_Commited:
		return false
	default:
		panic("unknown markkind: " + state.Kind.String())
	}

	return false
}

func (c *ConsumerGroup) removeConsumer(name string) bool {
	c.mu.Lock()
	defer c.mu.Unlock()

	for i, r := range c.receivers {
		if r.name == name {
			c.receivers = append(c.receivers[:i], c.receivers[i+1:]...)
			return true
		}
	}

	return false
}

func (c *ConsumerGroup) getReceiver(consumerName string) *receiver {
	c.mu.RLock()
	defer c.mu.RUnlock()
	for _, r := range c.receivers {
		if r.name == consumerName {
			return r
		}
	}

	return nil
}

func (c *ConsumerGroup) Consume(consumerName string) (<-chan *sgproto.Message, chan<- struct{}, error) {
	r := c.register(consumerName)

	return r.msgCh, r.doneCh, nil
}
<commit_msg>Fix stopping redelivery after N trials<commit_after>package broker

import (
	"context"
	"sync"
	"time"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/celrenheit/sandflake"
	"github.com/gogo/protobuf/proto"

	"github.com/celrenheit/sandglass-grpc/go/sgproto"
	"golang.org/x/sync/errgroup"
)

var (
	// TODO: make these variables configurable
	RedeliveryTimeout  = 10 * time.Second
	MaxRedeliveryCount = 5
)

type ConsumerGroup struct {
	broker    *Broker
	topic     string
	partition string
	name      string
	mu        sync.RWMutex
	receivers []*receiver
}

func NewConsumerGroup(b *Broker, topic, partition, name string) *ConsumerGroup {
	return &ConsumerGroup{
		broker:    b,
		name:      name,
		topic:     topic,
		partition: partition,
	}
}

type receiver struct {
	name   string
	msgCh  chan *sgproto.Message
	doneCh chan struct{}
}

func (c *ConsumerGroup) register(consumerName string) *receiver {
	r := c.getReceiver(consumerName)
	if r != nil {
		return r
	}

	c.mu.Lock()
	defer c.mu.Unlock()

	r = &receiver{
		name:   consumerName,
		msgCh:  make(chan *sgproto.Message),
		doneCh: make(chan struct{}),
	}
	c.receivers = append(c.receivers, r)

	if len(c.receivers) == 1 {
		go c.consumeLoop()
	}

	return r
}

func (c *ConsumerGroup) consumeLoop() {
	defer func() { // close receivers for whatever reason
		c.mu.Lock()
		for _, r := range c.receivers {
			close(r.msgCh)
			close(r.doneCh)
		}
		c.receivers = c.receivers[:0]
		c.mu.Unlock()
	}()

	lastCommited, err := c.broker.LastOffset(context.TODO(), c.topic, c.partition, c.name, "", sgproto.MarkKind_Commited)
	if err != nil {
		c.broker.Debug("got error when fetching last committed offset: %v ", err)
		return
	}

	from, err := c.broker.LastOffset(context.TODO(), c.topic, c.partition, c.name, "", sgproto.MarkKind_Consumed)
	if err != nil {
		c.broker.Debug("got error when fetching last committed offset: %v ", err)
		return
	}

	msgCh := make(chan *sgproto.Message)
	var group errgroup.Group

	if !lastCommited.Equal(from) {
		group.Go(func() error {
			var (
				lastMessage *sgproto.Message
				committed   = false
			)
			req := &sgproto.FetchRangeRequest{
				Topic:     c.topic,
				Partition: c.partition,
				From:      lastCommited,
				To:        from,
			}

			commit := func(offset sandflake.ID) {
				_, err := c.broker.Commit(context.TODO(), c.topic, c.partition, c.name, "", lastMessage.Offset)
				if err != nil {
					c.broker.Debug("unable to commit")
				}
			}

			i := 0
			err := c.broker.FetchRange(context.TODO(), req, func(m *sgproto.Message) error {
				if m.Offset.Equal(lastCommited) { // skip first item, since it is already committed
					lastMessage = m
					return nil
				}
				i++

				msg, err := c.broker.GetMarkStateMessage(context.TODO(), c.topic, c.partition, c.name, "", m.Offset)
				if err != nil {
					s, ok := status.FromError(err)
					if !ok || s.Code() != codes.NotFound {
						return err
					}
				}

				var state sgproto.MarkState
				if msg != nil {
					err := proto.Unmarshal(msg.Value, &state)
					if err != nil {
						return err
					}
				}

				// advance commit offset
				// if we only got acked messages before
				if !committed && lastMessage != nil {
					if state.Kind != sgproto.MarkKind_Acknowledged {
						// we might commit in a goroutine, we can redo this the next time we consume
						if !lastMessage.Offset.Equal(lastCommited) {
							commit(lastMessage.Offset)
						}
						committed = true
					} else if i%10000 == 0 {
						go commit(lastMessage.Offset)
					}
				}
				lastMessage = m

				if shouldRedeliver(m.Index, state) {
					msgCh <- m // deliver

					// those calls should be batched
					if state.Kind == sgproto.MarkKind_Unknown {
						// TODO: Should this be nacked?
						_, err := c.broker.NotAcknowledge(context.Background(), c.topic, c.partition, c.name, "NOT SET", m.Offset)
						if err != nil {
							c.broker.Debug("error while acking message for the first redilvery", err)
							return err
						}
					} else {
						state.DeliveryCount++

						if int(state.DeliveryCount) >= MaxRedeliveryCount {
							// Mark the message as ACKed
							// TODO: produce this a dead letter queue
							state.Kind = sgproto.MarkKind_Acknowledged
						}

						msg.Value, err = proto.Marshal(&state)
						if err != nil {
							return err
						}

						// TODO: Should handle this in higher level method
						t := c.broker.GetTopic(ConsumerOffsetTopicName)
						p := t.ChoosePartitionForKey(msg.Key)
						msg.ClusteringKey = generateClusterKey(msg.Offset, state.Kind)

						if _, err := c.broker.Produce(context.TODO(), &sgproto.ProduceMessageRequest{
							Topic:     ConsumerOffsetTopicName,
							Partition: p.Id,
							Messages:  []*sgproto.Message{msg},
						}); err != nil {
							return err
						}
					}
				}

				return nil
			})
			if err != nil {
				return err
			}

			if !committed && lastMessage != nil {
				commit(lastMessage.Offset)
			}

			return nil
		})
	}
	group.Go(func() error {
		now := sandflake.NewID(time.Now().UTC(), sandflake.MaxID.WorkerID(), sandflake.MaxID.Sequence(), sandflake.MaxID.RandomBytes())
		req := &sgproto.FetchRangeRequest{
			Topic:     c.topic,
			Partition: c.partition,
			From:      from,
			To:        now,
		}

		return c.broker.FetchRange(context.TODO(), req, func(m *sgproto.Message) error {
			// skip the first if it is the same as the starting point
			if from == m.Offset {
				return nil
			}

			msgCh <- m

			return nil
		})
	})

	go func() {
		err := group.Wait()
		if err != nil {
			c.broker.Info("error in consumeLoop: %v", err)
		}
		close(msgCh)
	}()

	var i int
	var m *sgproto.Message
loop:
	for m = range msgCh {
		// select receiver
	selectreceiver:
		i++
		c.mu.RLock()
		r := c.receivers[i%len(c.receivers)]
		c.mu.RUnlock()

		select {
		case <-r.doneCh:
			if c.removeConsumer(r.name) {
				c.mu.RLock()
				l := len(c.receivers)
				c.mu.RUnlock()

				if l == 0 {
					break loop
				}

				goto selectreceiver // select another receiver
			}
		case r.msgCh <- m:
		}
	}

	if m != nil && !m.Offset.Equal(from) {
		_, err := c.broker.MarkConsumed(context.TODO(), c.topic, c.partition, c.name, "REMOVE THIS", m.Offset)
		if err != nil {
			c.broker.Debug("unable to mark as consumed: %v", err)
		}
	}
}

func shouldRedeliver(index sandflake.ID, state sgproto.MarkState) bool {
	switch state.Kind {
	case sgproto.MarkKind_NotAcknowledged:
		return true
	case sgproto.MarkKind_Consumed, sgproto.MarkKind_Unknown: // inflight
		return index.Time().Add(RedeliveryTimeout).Before(time.Now().UTC())
	case sgproto.MarkKind_Acknowledged, sgproto.MarkKind_Commited:
		return false
	default:
		panic("unknown markkind: " + state.Kind.String())
	}

	return false
}

func (c *ConsumerGroup) removeConsumer(name string) bool {
	c.mu.Lock()
	defer c.mu.Unlock()

	for i, r := range c.receivers {
		if r.name == name {
			c.receivers = append(c.receivers[:i], c.receivers[i+1:]...)
			return true
		}
	}

	return false
}

func (c *ConsumerGroup) getReceiver(consumerName string) *receiver {
	c.mu.RLock()
	defer c.mu.RUnlock()
	for _, r := range c.receivers {
		if r.name == consumerName {
			return r
		}
	}

	return nil
}

func (c *ConsumerGroup) Consume(consumerName string) (<-chan *sgproto.Message, chan<- struct{}, error) {
	r := c.register(consumerName)

	return r.msgCh, r.doneCh, nil
}
<|endoftext|>


<commit_before>package fun

import (
	"fmt"
	"reflect"
)

type parallel2Result struct {
	i   int
	val interface{}
	err error
}

func Parallel2(funs ...interface{}) ([]interface{}, error) {
	resChan := make(chan parallel2Result)
	results := make([]interface{}, len(funs))
	// Dispatch functions
	for i, fun := range funs {
		go func(i int, fun interface{}) {
			res := reflect.ValueOf(fun).Call(nil)
			val := res[0].Interface().(interface{})
			err := res[1].Interface().(error)
			resChan <- parallel2Result{i: i, val: val, err: err}
		}(i, fun)
	}
	// Collect results
	for i := 0; i < len(funs); i++ {
		res := <-resChan
		if res.err != nil {
			return nil, res.err
		}
		results[res.i] = res.val
	}
	return results, nil
}

func Parallel(args ...interface{}) error {
	if reflect.TypeOf(args[0]).NumOut() == 2 {
		funs := args[:len(args)-1]
		return parallelWithDone(funs, Last(args))
	} else {
		return parallelWithoutDone(args)
	}
}

func parallelWithoutDone(funs []interface{}) error {
	resChan := make(chan reflect.Value)

	for _, fun := range funs {
		fun := reflect.ValueOf(fun)
		go func() {
			resChan <- fun.Call(nil)[0]
		}()
	}

	for i := 0; i < len(funs); i++ {
		res := <-resChan

		// There was an error
		if !res.IsNil() {
			return res.Interface().(error)
		}
	}

	return nil
}

func parallelWithDone(funs []interface{}, done interface{}) error {
	doneVal := reflect.ValueOf(done)
	doneTyp := doneVal.Type()
	errorIndex := len(funs)
	results := make([]reflect.Value, len(funs)+1)
	resChan := make(chan parallelResult)
	doneReturnsError := false

	// Check types
	if doneTyp.NumIn() != len(funs)+1 {
		panic(fmt.Sprintf(finalFuncNumArgsMsg, len(funs)+1))
	}
	if !doneTyp.In(errorIndex).Implements(errorInterface) {
		panic(fmt.Sprint(finalFuncErrArgMsg, doneTyp.In(errorIndex)))
	}
	if doneTyp.NumOut() > 1 {
		panic(fmt.Sprint(finalFuncReturnCountMsg, doneTyp.NumOut()))
	}
	if doneTyp.NumOut() == 1 {
		if !doneTyp.Out(0).Implements(errorInterface) {
			panic(fmt.Sprint(finalFuncReturnWrongTypeMsg, doneTyp.Out(0)))
		}
		doneReturnsError = true
	}
	for funI, fun := range funs {
		funTyp := reflect.TypeOf(fun)
		if funTyp.NumIn() > 0 {
			panic(fmt.Sprintf(argFuncNoArgsMsg, funI+1, reflect.ValueOf(funTyp).Interface()))
		}
		if funTyp.NumOut() != 2 {
			panic(fmt.Sprintf(argFuncNumReturnMsg, funI, funTyp.NumOut()))
		}
		if funTyp.Out(0) != doneTyp.In(funI) {
			panic(fmt.Sprintf(argFuncDoneFuncTypeMismatch, funI, funTyp.Out(0), doneTyp.In(funI)))
		}
		if !funTyp.Out(1).Implements(errorInterface) {
			panic(fmt.Sprintf(argFuncErrReturnMsg, funI, funTyp.Out(1)))
		}
	}

	// Dispatch executions
	for i, fun := range funs {
		i := i
		fun := reflect.ValueOf(fun)
		go func() {
			returns := fun.Call(nil)
			resChan <- parallelResult{index: i, val: returns[0], err: returns[1]}
		}()
	}

	// Collect results
	for i := 0; i < len(funs); i++ {
		res := <-resChan

		// There was an error
		if !res.err.IsNil() {
			for funI, fun := range funs {
				results[funI] = reflect.Zero(reflect.ValueOf(fun).Type().Out(0))
			}
			results[errorIndex] = res.err
			doneRes := doneVal.Call(results)
			if !doneReturnsError {
				return res.err.Interface().(error)
			}
			return doneRes[0].Interface().(error)
		}

		results[res.index] = res.val
	}

	results[errorIndex] = reflect.ValueOf(&nilError).Elem()
	doneRes := doneVal.Call(results)
	if !doneReturnsError {
		return nil
	}
	if doneRes[0].IsNil() {
		return nil
	}
	return doneRes[0].Interface().(error)
}

type parallelResult struct {
	index int
	val   reflect.Value
	err   reflect.Value
}

var nilError = error(nil)
var errorInterface = reflect.TypeOf(func(error) {}).In(0)

const finalFuncErrArgMsg = "Parallel final function's last argument type should be error but is "
const finalFuncNumArgsMsg = "Parallel final function should take %d arguments"
const argFuncNoArgsMsg = "Parallel functions should not take any arguments\n(offending function is number %d with signature %q)"
const argFuncNumReturnMsg = "Parallel function number %d should return two values (returns %d)"
const argFuncDoneFuncTypeMismatch = "Parallel function number %d returns a %q but final function expects a %q"
const argFuncErrReturnMsg = "Parallel function number %d should return an error as second return value (returns %q)"
const finalFuncReturnCountMsg = "Parallel final function should return nothing or one error (returns %d values)"
const finalFuncReturnWrongTypeMsg = "Parallel final function return value must be an error (is %q)"
<commit_msg>force type assertion<commit_after>package fun

import (
	"fmt"
	"reflect"
)

type parallel2Result struct {
	i   int
	val interface{}
	err error
}

func Parallel2(funs ...interface{}) ([]interface{}, error) {
	resChan := make(chan parallel2Result)
	results := make([]interface{}, len(funs))
	// Dispatch functions
	for i, fun := range funs {
		go func(i int, fun interface{}) {
			res := reflect.ValueOf(fun).Call(nil)
			val, _ := res[0].Interface().(interface{})
			err, _ := res[1].Interface().(error)
			resChan <- parallel2Result{i: i, val: val, err: err}
		}(i, fun)
	}
	// Collect results
	for i := 0; i < len(funs); i++ {
		res := <-resChan
		if res.err != nil {
			return nil, res.err
		}
		results[res.i] = res.val
	}
	return results, nil
}

func Parallel(args ...interface{}) error {
	if reflect.TypeOf(args[0]).NumOut() == 2 {
		funs := args[:len(args)-1]
		return parallelWithDone(funs, Last(args))
	} else {
		return parallelWithoutDone(args)
	}
}

func parallelWithoutDone(funs []interface{}) error {
	resChan := make(chan reflect.Value)

	for _, fun := range funs {
		fun := reflect.ValueOf(fun)
		go func() {
			resChan <- fun.Call(nil)[0]
		}()
	}

	for i := 0; i < len(funs); i++ {
		res := <-resChan

		// There was an error
		if !res.IsNil() {
			return res.Interface().(error)
		}
	}

	return nil
}

func parallelWithDone(funs []interface{}, done interface{}) error {
	doneVal := reflect.ValueOf(done)
	doneTyp := doneVal.Type()
	errorIndex := len(funs)
	results := make([]reflect.Value, len(funs)+1)
	resChan := make(chan parallelResult)
	doneReturnsError := false

	// Check types
	if doneTyp.NumIn() != len(funs)+1 {
		panic(fmt.Sprintf(finalFuncNumArgsMsg, len(funs)+1))
	}
	if !doneTyp.In(errorIndex).Implements(errorInterface) {
		panic(fmt.Sprint(finalFuncErrArgMsg, doneTyp.In(errorIndex)))
	}
	if doneTyp.NumOut() > 1 {
		panic(fmt.Sprint(finalFuncReturnCountMsg, doneTyp.NumOut()))
	}
	if doneTyp.NumOut() == 1 {
		if !doneTyp.Out(0).Implements(errorInterface) {
			panic(fmt.Sprint(finalFuncReturnWrongTypeMsg, doneTyp.Out(0)))
		}
		doneReturnsError = true
	}
	for funI, fun := range funs {
		funTyp := reflect.TypeOf(fun)
		if funTyp.NumIn() > 0 {
			panic(fmt.Sprintf(argFuncNoArgsMsg, funI+1, reflect.ValueOf(funTyp).Interface()))
		}
		if funTyp.NumOut() != 2 {
			panic(fmt.Sprintf(argFuncNumReturnMsg, funI, funTyp.NumOut()))
		}
		if funTyp.Out(0) != doneTyp.In(funI) {
			panic(fmt.Sprintf(argFuncDoneFuncTypeMismatch, funI, funTyp.Out(0), doneTyp.In(funI)))
		}
		if !funTyp.Out(1).Implements(errorInterface) {
			panic(fmt.Sprintf(argFuncErrReturnMsg, funI, funTyp.Out(1)))
		}
	}

	// Dispatch executions
	for i, fun := range funs {
		i := i
		fun := reflect.ValueOf(fun)
		go func() {
			returns := fun.Call(nil)
			resChan <- parallelResult{index: i, val: returns[0], err: returns[1]}
		}()
	}

	// Collect results
	for i := 0; i < len(funs); i++ {
		res := <-resChan

		// There was an error
		if !res.err.IsNil() {
			for funI, fun := range funs {
				results[funI] = reflect.Zero(reflect.ValueOf(fun).Type().Out(0))
			}
			results[errorIndex] = res.err
			doneRes := doneVal.Call(results)
			if !doneReturnsError {
				return res.err.Interface().(error)
			}
			return doneRes[0].Interface().(error)
		}

		results[res.index] = res.val
	}

	results[errorIndex] = reflect.ValueOf(&nilError).Elem()
	doneRes := doneVal.Call(results)
	if !doneReturnsError {
		return nil
	}
	if doneRes[0].IsNil() {
		return nil
	}
	return doneRes[0].Interface().(error)
}

type parallelResult struct {
	index int
	val   reflect.Value
	err   reflect.Value
}

var nilError = error(nil)
var errorInterface = reflect.TypeOf(func(error) {}).In(0)

const finalFuncErrArgMsg = "Parallel final function's last argument type should be error but is "
const finalFuncNumArgsMsg = "Parallel final function should take %d arguments"
const argFuncNoArgsMsg = "Parallel functions should not take any arguments\n(offending function is number %d with signature %q)"
const argFuncNumReturnMsg = "Parallel function number %d should return two values (returns %d)"
const argFuncDoneFuncTypeMismatch = "Parallel function number %d returns a %q but final function expects a %q"
const argFuncErrReturnMsg = "Parallel function number %d should return an error as second return value (returns %q)"
const finalFuncReturnCountMsg = "Parallel final function should return nothing or one error (returns %d values)"
const finalFuncReturnWrongTypeMsg = "Parallel final function return value must be an error (is %q)"
<|endoftext|>


<commit_before>package lib

import (
	"github.com/Symantec/Dominator/lib/filesystem"
	"github.com/Symantec/Dominator/lib/filter"
	"github.com/Symantec/Dominator/lib/hash"
	"github.com/Symantec/Dominator/lib/image"
	subproto "github.com/Symantec/Dominator/proto/sub"
	"testing"
)

func TestSameFile(t *testing.T) {
	request := makeUpdateRequest(testDataFile0(), testDataFile0())
	if len(request.PathsToDelete) != 0 {
		t.Errorf("number of paths to delete: %d != 0",
			len(request.PathsToDelete))
	}
}

func TestFileToDelete(t *testing.T) {
	request := makeUpdateRequest(testDataFile1(), testDataFile0())
	if len(request.PathsToDelete) != 1 {
		t.Errorf("number of paths to delete: %d != 1",
			len(request.PathsToDelete))
	}
}

func TestSameOnlyDirectory(t *testing.T) {
	request := makeUpdateRequest(testDataDirectory0(), testDataDirectory0())
	if len(request.PathsToDelete) != 0 {
		t.Errorf("number of paths to delete: %d != 0",
			len(request.PathsToDelete))
	}
}

func TestOnlyDirectoryToDelete(t *testing.T) {
	request := makeUpdateRequest(testDataDirectory2(), testDataDirectory0())
	if len(request.PathsToDelete) != 1 {
		t.Errorf("number of paths to delete: %d != 1",
			len(request.PathsToDelete))
	}
}

func TestExtraDirectoryToDelete(t *testing.T) {
	request := makeUpdateRequest(testDataDirectory0(), testDataDirectory01())
	if len(request.PathsToDelete) != 1 {
		t.Errorf("number of paths to delete: %d != 1",
			len(request.PathsToDelete))
	}
}

func makeUpdateRequest(imageFS *filesystem.FileSystem,
	subFS *filesystem.FileSystem) subproto.UpdateRequest {
	objectCache := make([]hash.Hash, 0, len(imageFS.InodeTable))
	for hashVal := range imageFS.HashToInodesTable() {
		objectCache = append(objectCache, hashVal)
	}
	imageFS.BuildEntryMap()
	if err := subFS.RebuildInodePointers(); err != nil {
		panic(err)
	}
	subFS.BuildEntryMap()
	if err := imageFS.RebuildInodePointers(); err != nil {
		panic(err)
	}
	subObj := Sub{FileSystem: subFS, ObjectCache: objectCache}
	var request subproto.UpdateRequest
	emptyFilter, _ := filter.New(nil)
	BuildUpdateRequest(subObj,
		&image.Image{FileSystem: imageFS, Filter: emptyFilter},
		&request,
		false, nil)
	return request
}

func testDataDirectory0() *filesystem.FileSystem {
	return &filesystem.FileSystem{
		InodeTable: filesystem.InodeTable{
			1: &filesystem.DirectoryInode{},
		},
		DirectoryInode: filesystem.DirectoryInode{
			EntryList: []*filesystem.DirectoryEntry{
				&filesystem.DirectoryEntry{
					Name:        "dir0",
					InodeNumber: 1,
				},
			},
		},
	}
}

func testDataDirectory01() *filesystem.FileSystem {
	return &filesystem.FileSystem{
		InodeTable: filesystem.InodeTable{
			1: &filesystem.DirectoryInode{},
			2: &filesystem.DirectoryInode{},
		},
		DirectoryInode: filesystem.DirectoryInode{
			EntryList: []*filesystem.DirectoryEntry{
				&filesystem.DirectoryEntry{
					Name:        "dir0",
					InodeNumber: 1,
				},
				&filesystem.DirectoryEntry{
					Name:        "dir1",
					InodeNumber: 2,
				},
			},
		},
	}
}

func testDataDirectory2() *filesystem.FileSystem {
	return &filesystem.FileSystem{
		InodeTable: filesystem.InodeTable{
			1: &filesystem.DirectoryInode{},
		},
		DirectoryInode: filesystem.DirectoryInode{
			EntryList: []*filesystem.DirectoryEntry{
				&filesystem.DirectoryEntry{
					Name:        "dir2",
					InodeNumber: 1,
				},
			},
		},
	}
}

func testDataFile0() *filesystem.FileSystem {
	return &filesystem.FileSystem{
		InodeTable: filesystem.InodeTable{
			1: &filesystem.RegularInode{Size: 100, Hash: hash0},
		},
		DirectoryInode: filesystem.DirectoryInode{
			EntryList: []*filesystem.DirectoryEntry{
				&filesystem.DirectoryEntry{
					Name:        "file0",
					InodeNumber: 1,
				},
			},
		},
	}
}

func testDataFile1() *filesystem.FileSystem {
	return &filesystem.FileSystem{
		InodeTable: filesystem.InodeTable{
			1: &filesystem.RegularInode{Size: 101, Hash: hash1},
		},
		DirectoryInode: filesystem.DirectoryInode{
			EntryList: []*filesystem.DirectoryEntry{
				&filesystem.DirectoryEntry{
					Name:        "file1",
					InodeNumber: 1,
				},
			},
		},
	}
}

var (
	hash0 hash.Hash = hash.Hash{0xde, 0xad}
	hash1 hash.Hash = hash.Hash{0xbe, 0xef}
)
<commit_msg>Add TestFileToChange() to dom/lib package tests.<commit_after>package lib

import (
	"encoding/json"
	"github.com/Symantec/Dominator/lib/filesystem"
	"github.com/Symantec/Dominator/lib/filter"
	"github.com/Symantec/Dominator/lib/hash"
	"github.com/Symantec/Dominator/lib/image"
	subproto "github.com/Symantec/Dominator/proto/sub"
	"reflect"
	"testing"
)

func TestSameFile(t *testing.T) {
	request := makeUpdateRequest(testDataFile0(0), testDataFile0(0))
	if len(request.PathsToDelete) != 0 {
		t.Errorf("number of paths to delete: %d != 0",
			len(request.PathsToDelete))
	}
}

func TestFileToDelete(t *testing.T) {
	request := makeUpdateRequest(testDataFile1(0), testDataFile0(0))
	if len(request.PathsToDelete) != 1 {
		t.Errorf("number of paths to delete: %d != 1",
			len(request.PathsToDelete))
	}
}

func TestFileToChange(t *testing.T) {
	request := makeUpdateRequest(testDataFile0(0), testDataFile0(1))
	if reflect.DeepEqual(request, subproto.UpdateRequest{}) {
		txt, _ := json.MarshalIndent(request, "", "    ")
		t.Errorf("Inode not being changed:\n%s", txt)
	}
	if len(request.InodesToChange) != 1 {
		txt, _ := json.MarshalIndent(request, "", "    ")
		t.Errorf("Inode not being changed:\n%s", txt)
	}
}

func TestSameOnlyDirectory(t *testing.T) {
	request := makeUpdateRequest(testDataDirectory0(), testDataDirectory0())
	if len(request.PathsToDelete) != 0 {
		t.Errorf("number of paths to delete: %d != 0",
			len(request.PathsToDelete))
	}
}

func TestOnlyDirectoryToDelete(t *testing.T) {
	request := makeUpdateRequest(testDataDirectory2(), testDataDirectory0())
	if len(request.PathsToDelete) != 1 {
		t.Errorf("number of paths to delete: %d != 1",
			len(request.PathsToDelete))
	}
}

func TestExtraDirectoryToDelete(t *testing.T) {
	request := makeUpdateRequest(testDataDirectory0(), testDataDirectory01())
	if len(request.PathsToDelete) != 1 {
		t.Errorf("number of paths to delete: %d != 1",
			len(request.PathsToDelete))
	}
}

func makeUpdateRequest(imageFS *filesystem.FileSystem,
	subFS *filesystem.FileSystem) subproto.UpdateRequest {
	fetchedObjects := make(map[hash.Hash]struct{}, len(imageFS.InodeTable))
	for hashVal := range imageFS.HashToInodesTable() {
		fetchedObjects[hashVal] = struct{}{}
	}
	for hashVal := range subFS.HashToInodesTable() {
		delete(fetchedObjects, hashVal)
	}
	objectCache := make([]hash.Hash, 0, len(fetchedObjects))
	for hashVal := range fetchedObjects {
		objectCache = append(objectCache, hashVal)
	}
	imageFS.BuildEntryMap()
	if err := subFS.RebuildInodePointers(); err != nil {
		panic(err)
	}
	subFS.BuildEntryMap()
	if err := imageFS.RebuildInodePointers(); err != nil {
		panic(err)
	}
	subObj := Sub{FileSystem: subFS, ObjectCache: objectCache}
	var request subproto.UpdateRequest
	emptyFilter, _ := filter.New(nil)
	BuildUpdateRequest(subObj,
		&image.Image{FileSystem: imageFS, Filter: emptyFilter},
		&request,
		false, nil)
	return request
}

func testDataDirectory0() *filesystem.FileSystem {
	return &filesystem.FileSystem{
		InodeTable: filesystem.InodeTable{
			1: &filesystem.DirectoryInode{},
		},
		DirectoryInode: filesystem.DirectoryInode{
			EntryList: []*filesystem.DirectoryEntry{
				&filesystem.DirectoryEntry{
					Name:        "dir0",
					InodeNumber: 1,
				},
			},
		},
	}
}

func testDataDirectory01() *filesystem.FileSystem {
	return &filesystem.FileSystem{
		InodeTable: filesystem.InodeTable{
			1: &filesystem.DirectoryInode{},
			2: &filesystem.DirectoryInode{},
		},
		DirectoryInode: filesystem.DirectoryInode{
			EntryList: []*filesystem.DirectoryEntry{
				&filesystem.DirectoryEntry{
					Name:        "dir0",
					InodeNumber: 1,
				},
				&filesystem.DirectoryEntry{
					Name:        "dir1",
					InodeNumber: 2,
				},
			},
		},
	}
}

func testDataDirectory2() *filesystem.FileSystem {
	return &filesystem.FileSystem{
		InodeTable: filesystem.InodeTable{
			1: &filesystem.DirectoryInode{},
		},
		DirectoryInode: filesystem.DirectoryInode{
			EntryList: []*filesystem.DirectoryEntry{
				&filesystem.DirectoryEntry{
					Name:        "dir2",
					InodeNumber: 1,
				},
			},
		},
	}
}

func testDataFile0(uid uint32) *filesystem.FileSystem {
	return &filesystem.FileSystem{
		InodeTable: filesystem.InodeTable{
			1: &filesystem.RegularInode{Size: 100, Hash: hash0, Uid: uid},
		},
		DirectoryInode: filesystem.DirectoryInode{
			EntryList: []*filesystem.DirectoryEntry{
				&filesystem.DirectoryEntry{
					Name:        "file0",
					InodeNumber: 1,
				},
			},
		},
	}
}

func testDataFile1(uid uint32) *filesystem.FileSystem {
	return &filesystem.FileSystem{
		InodeTable: filesystem.InodeTable{
			1: &filesystem.RegularInode{Size: 101, Hash: hash1, Uid: uid},
		},
		DirectoryInode: filesystem.DirectoryInode{
			EntryList: []*filesystem.DirectoryEntry{
				&filesystem.DirectoryEntry{
					Name:        "file1",
					InodeNumber: 1,
				},
			},
		},
	}
}

var (
	hash0 hash.Hash = hash.Hash{0xde, 0xad}
	hash1 hash.Hash = hash.Hash{0xbe, 0xef}
)
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "io/ioutil"
    "math/rand"
    "os"
    "strings"
    "time"
    goirc "github.com/thoj/go-ircevent"
    urbandict "github.com/davidscholberg/go-urbandict"
    gcfg "gopkg.in/gcfg.v1"
)

type config struct {
    User struct {
        Nick string
        User string
    }
    Server struct {
        Host string
        Port string
    }
    Channel struct {
        Channelname string
    }
    Module struct {
        Insult_swearfile string
    }
}

type privmsg struct {
    msg string
    msgArgs []string
    dest string
    e *goirc.Event
    s chan say
}

type say struct {
    c *goirc.Connection
    dest string
    msg string
}

var swears []string

func main() {
    // get config
    confPath := fmt.Sprintf("%s/.config/irkbot/irkbot.ini", os.Getenv("HOME"))
    cfg := config{}
    err := gcfg.ReadFileInto(&cfg, confPath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    // initialize swear array
    swearBytes, err := ioutil.ReadFile(cfg.Module.Insult_swearfile)
    if err == nil {
        swears = strings.Split(string(swearBytes), "\n")
    } else {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    conn := goirc.IRC(cfg.User.Nick, cfg.User.User)
    err = conn.Connect(fmt.Sprintf(
        "%s:%s",
        cfg.Server.Host,
        cfg.Server.Port))
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    conn.VerboseCallbackHandler = true
    conn.Debug = true

    conn.AddCallback("001", func(e *goirc.Event) {
        conn.Join(cfg.Channel.Channelname)
    })

    conn.AddCallback("366", func(e *goirc.Event) {
        conn.Privmsg(e.Arguments[1], "yo yo yo\n")
    })

    privmsgCallbacks := []func(*privmsg, *goirc.Connection) bool{
        privmsgEchoName,
        privmsgQuit,
        privmsgInsult,
        privmsgUrban}

    sayChan := make(chan say)
    go sayLoop(sayChan)

    conn.AddCallback("PRIVMSG", func(e *goirc.Event) {
        p := privmsg{}
        p.msg = e.Message()
        p.msgArgs = strings.Split(p.msg, " ")
        p.dest = e.Arguments[0]
        if !strings.HasPrefix(p.dest, "#") {
            p.dest = e.Nick
        }
        p.e = e
        p.s = sayChan

        for _, callback := range privmsgCallbacks {
            if callback(&p, conn) {
                break
            }
        }
    })

    conn.Loop()
}

func privmsgEchoName(p *privmsg, c *goirc.Connection) bool {
    if p.msg != "irkbot!" {
        return false
    }
    p.s <- say{c, p.dest, fmt.Sprintf("%s!", p.e.Nick)}
    return true
}

func privmsgQuit(p *privmsg, c *goirc.Connection) bool {
    if p.msg != "..quit" {
        return false
    }
    c.Quit()
    return true
}

func privmsgInsult(p *privmsg, c *goirc.Connection) bool {
    if ! strings.HasPrefix(p.msg, "..insult") {
        return false
    }

    if len(swears) == 0 {
        p.s <- say{c, p.dest, "error: no swears"}
        return true
    }

    insultee := p.e.Nick
    if len(p.msgArgs) > 1 {
        insultee = strings.Join(p.msgArgs[1:], " ")
    }

    response := fmt.Sprintf(
        "%s: you %s %s",
        insultee,
        swears[rand.Intn(len(swears))],
        swears[rand.Intn(len(swears))])

    p.s <- say{c, p.dest, response}
    return true
}

func privmsgUrban(p *privmsg, c *goirc.Connection) bool {
    if ! strings.HasPrefix(p.msg, "..urban") {
        return false
    }

    var def *urbandict.Definition
    var err error
    if len(p.msgArgs) == 1 {
        def, err = urbandict.Random()
        if err != nil {
            p.s <- say{c, p.dest, fmt.Sprintf("%s: %s", p.e.Nick, err.Error())}
            return true
        }
    } else {
        def, err = urbandict.Define(strings.Join(p.msgArgs[1:], " "))
        if err != nil {
            p.s <- say{c, p.dest, fmt.Sprintf("%s: %s", p.e.Nick, err.Error())}
            return true
        }
    }

    // TODO: implement max message length handling

    p.s <- say{
        c,
        p.dest,
        fmt.Sprintf(
            "%s: Top definition for \"%s\"",
            p.e.Nick,
            def.Word)}
    for _, line := range strings.Split(def.Definition, "\r\n") {
        p.s <- say{c, p.dest, fmt.Sprintf("%s: %s", p.e.Nick, line)}
    }
    p.s <- say{c, p.dest, fmt.Sprintf("%s: Example:", p.e.Nick)}
    for _, line := range strings.Split(def.Example, "\r\n") {
        p.s <- say{c, p.dest, fmt.Sprintf("%s: %s", p.e.Nick, line)}
    }
    p.s <- say{
        c,
        p.dest,
        fmt.Sprintf("%s: permalink: %s", p.e.Nick, def.Permalink)}
    return true
}

func sayLoop(sayChan chan say) {
    sayTimeouts := make(map[string]time.Time)

    for s := range sayChan {
        sleepDuration := time.Duration(0)

        if prevTime, ok := sayTimeouts[s.dest]; ok {
            sleepDuration = time.Second - time.Now().Sub(prevTime)
            if sleepDuration < 0 {
                sleepDuration = time.Duration(0)
            }
        }

        time.Sleep(sleepDuration)
        sayTimeouts[s.dest] = time.Now()

        s.c.Privmsg(s.dest, s.msg)
    }
}
<commit_msg>improved struct field naming, added conn pointer to privmsg struct<commit_after>package main

import (
    "fmt"
    "io/ioutil"
    "math/rand"
    "os"
    "strings"
    "time"
    goirc "github.com/thoj/go-ircevent"
    urbandict "github.com/davidscholberg/go-urbandict"
    gcfg "gopkg.in/gcfg.v1"
)

type config struct {
    User struct {
        Nick string
        User string
    }
    Server struct {
        Host string
        Port string
    }
    Channel struct {
        Channelname string
    }
    Module struct {
        Insult_swearfile string
    }
}

type privmsg struct {
    msg string
    msgArgs []string
    dest string
    event *goirc.Event
    conn *goirc.Connection
    sayChan chan say
}

type say struct {
    conn *goirc.Connection
    dest string
    msg string
}

var swears []string

func main() {
    // get config
    confPath := fmt.Sprintf("%s/.config/irkbot/irkbot.ini", os.Getenv("HOME"))
    cfg := config{}
    err := gcfg.ReadFileInto(&cfg, confPath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    // initialize swear array
    swearBytes, err := ioutil.ReadFile(cfg.Module.Insult_swearfile)
    if err == nil {
        swears = strings.Split(string(swearBytes), "\n")
    } else {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    conn := goirc.IRC(cfg.User.Nick, cfg.User.User)
    err = conn.Connect(fmt.Sprintf(
        "%s:%s",
        cfg.Server.Host,
        cfg.Server.Port))
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    conn.VerboseCallbackHandler = true
    conn.Debug = true

    conn.AddCallback("001", func(e *goirc.Event) {
        conn.Join(cfg.Channel.Channelname)
    })

    conn.AddCallback("366", func(e *goirc.Event) {
        conn.Privmsg(e.Arguments[1], "yo yo yo\n")
    })

    privmsgCallbacks := []func(*privmsg) bool{
        privmsgEchoName,
        privmsgQuit,
        privmsgInsult,
        privmsgUrban}

    // TODO: start multiple sayLoops, one per conn
    // TODO: pass conn to sayLoop instead of privmsg callbacks?
    sayChan := make(chan say)
    go sayLoop(sayChan)

    conn.AddCallback("PRIVMSG", func(e *goirc.Event) {
        p := privmsg{}
        p.msg = e.Message()
        p.msgArgs = strings.Split(p.msg, " ")
        p.dest = e.Arguments[0]
        if !strings.HasPrefix(p.dest, "#") {
            p.dest = e.Nick
        }
        p.event = e
        p.conn = conn
        p.sayChan = sayChan

        for _, callback := range privmsgCallbacks {
            if callback(&p) {
                break
            }
        }
    })

    conn.Loop()
}

func privmsgEchoName(p *privmsg) bool {
    if p.msg != "irkbot!" {
        return false
    }
    p.sayChan <- say{p.conn, p.dest, fmt.Sprintf("%s!", p.event.Nick)}
    return true
}

func privmsgQuit(p *privmsg) bool {
    if p.msg != "..quit" {
        return false
    }
    p.conn.Quit()
    return true
}

func privmsgInsult(p *privmsg) bool {
    if ! strings.HasPrefix(p.msg, "..insult") {
        return false
    }

    if len(swears) == 0 {
        p.sayChan <- say{p.conn, p.dest, "error: no swears"}
        return true
    }

    insultee := p.event.Nick
    if len(p.msgArgs) > 1 {
        insultee = strings.Join(p.msgArgs[1:], " ")
    }

    response := fmt.Sprintf(
        "%s: you %s %s",
        insultee,
        swears[rand.Intn(len(swears))],
        swears[rand.Intn(len(swears))])

    p.sayChan <- say{p.conn, p.dest, response}
    return true
}

func privmsgUrban(p *privmsg) bool {
    if ! strings.HasPrefix(p.msg, "..urban") {
        return false
    }

    var def *urbandict.Definition
    var err error
    nick := p.event.Nick
    if len(p.msgArgs) == 1 {
        def, err = urbandict.Random()
        if err != nil {
            p.sayChan <- say{
                p.conn,
                p.dest,
                fmt.Sprintf("%s: %s", nick, err.Error())}
            return true
        }
    } else {
        def, err = urbandict.Define(strings.Join(p.msgArgs[1:], " "))
        if err != nil {
            p.sayChan <- say{
                p.conn,
                p.dest,
                fmt.Sprintf("%s: %s", nick, err.Error())}
            return true
        }
    }

    // TODO: implement max message length handling

    p.sayChan <- say{
        p.conn,
        p.dest,
        fmt.Sprintf("%s: Top definition for \"%s\"", nick, def.Word)}
    for _, line := range strings.Split(def.Definition, "\r\n") {
        p.sayChan <- say{p.conn, p.dest, fmt.Sprintf("%s: %s", nick, line)}
    }
    p.sayChan <- say{p.conn, p.dest, fmt.Sprintf("%s: Example:", nick)}
    for _, line := range strings.Split(def.Example, "\r\n") {
        p.sayChan <- say{p.conn, p.dest, fmt.Sprintf("%s: %s", nick, line)}
    }
    p.sayChan <- say{
        p.conn,
        p.dest,
        fmt.Sprintf("%s: permalink: %s", nick, def.Permalink)}
    return true
}

func sayLoop(sayChan chan say) {
    sayTimeouts := make(map[string]time.Time)

    for s := range sayChan {
        sleepDuration := time.Duration(0)

        if prevTime, ok := sayTimeouts[s.dest]; ok {
            sleepDuration = time.Second - time.Now().Sub(prevTime)
            if sleepDuration < 0 {
                sleepDuration = time.Duration(0)
            }
        }

        time.Sleep(sleepDuration)
        sayTimeouts[s.dest] = time.Now()

        s.conn.Privmsg(s.dest, s.msg)
    }
}
<|endoftext|>