<commit_before>package pkg

import "fmt"

type ActionType interface {
	Do(Sender, Channel, User) error
	Priority() int
}

type Action interface {
	Do() error
	Set(ActionType)

	NotifyModerator() User
	SetNotifyModerator(User)
}

var _ Action = &TwitchAction{}

type TwitchAction struct {
	Sender Sender

	Channel Channel

	User User

	action ActionType

	notifyModerator User
}

type Timeout struct {
	Duration int
	Reason   string
}

func (a Timeout) Do(sender Sender, channel Channel, user User) error {
	sender.Timeout(channel, user, a.Duration, a.Reason)

	return nil
}

func (a Timeout) Priority() int {
	return 100 + a.Duration
}

type Ban struct {
	Reason string
}

func (a Ban) Do(sender Sender, channel Channel, user User) error {
	sender.Ban(channel, user, a.Reason)

	return nil
}

func (a Ban) Priority() int {
	return 0
}

func (a TwitchAction) Do() error {
	if a.action != nil {
		if a.NotifyModerator() != nil {
			a.Sender.Whisper(a.NotifyModerator(), fmt.Sprintf("%s triggered bad banphrase in %s", a.User.GetName(), a.Channel.GetChannel()))
		}
		return a.action.Do(a.Sender, a.Channel, a.User)
	}

	return nil
}

func (a *TwitchAction) Set(action ActionType) {
	if a.action == nil {
		a.action = action
	} else {
		if a.action.Priority() > action.Priority() {
			a.action = action
		}
	}
}

func (a TwitchAction) NotifyModerator() User {
	return a.notifyModerator
}

func (a *TwitchAction) SetNotifyModerator(user User) {
	a.notifyModerator = user
}
<commit_msg>fix action priority<commit_after>package pkg

import "fmt"

type ActionType interface {
	Do(Sender, Channel, User) error
	Priority() int
}

type Action interface {
	Do() error
	Set(ActionType)

	NotifyModerator() User
	SetNotifyModerator(User)
}

var _ Action = &TwitchAction{}

type TwitchAction struct {
	Sender Sender

	Channel Channel

	User User

	action ActionType

	notifyModerator User
}

type Timeout struct {
	Duration int
	Reason   string
}

func (a Timeout) Do(sender Sender, channel Channel, user User) error {
	sender.Timeout(channel, user, a.Duration, a.Reason)

	return nil
}

func (a Timeout) Priority() int {
	return 100 + a.Duration
}

type Ban struct {
	Reason string
}

func (a Ban) Do(sender Sender, channel Channel, user User) error {
	sender.Ban(channel, user, a.Reason)

	return nil
}

func (a Ban) Priority() int {
	return 0
}

func (a TwitchAction) Do() error {
	if a.action != nil {
		if a.NotifyModerator() != nil {
			a.Sender.Whisper(a.NotifyModerator(), fmt.Sprintf("%s triggered bad banphrase in %s", a.User.GetName(), a.Channel.GetChannel()))
		}
		return a.action.Do(a.Sender, a.Channel, a.User)
	}

	return nil
}

func (a *TwitchAction) Set(action ActionType) {
	if a.action == nil {
		a.action = action
	} else {
		if action.Priority() > a.action.Priority() {
			a.action = action
		}
	}
}

func (a TwitchAction) NotifyModerator() User {
	return a.notifyModerator
}

func (a *TwitchAction) SetNotifyModerator(user User) {
	a.notifyModerator = user
}
<|endoftext|>


<commit_before>package nsqcrawl

import (
	"io"
	"time"

	"github.com/golang/glog"
	"golang.org/x/net/context"

	"github.com/crackcomm/crawl"
	"github.com/crackcomm/nsqueue/consumer"
	"github.com/crackcomm/nsqueue/producer"
)

// NewQueue - Creates nsq consumer and producer.
func NewQueue(topic, channel string, maxInFlight int) *Queue {
	q := &Queue{
		Consumer: consumer.New(),
		Producer: producer.New(),
		channel:  make(chan *nsqJob, maxInFlight+1),
		topic:    topic,
	}
	q.Consumer.Register(topic, channel, maxInFlight, q.nsqHandler)
	return q
}

// NewProducer - Creates queue producer.
func NewProducer(topic string) *Queue {
	return &Queue{
		Producer: producer.New(),
		topic:    topic,
	}
}

// Queue - NSQ Queue.
type Queue struct {
	*consumer.Consumer
	*producer.Producer

	topic   string
	channel chan *nsqJob
}

// Schedule - Schedules job in nsq.
// It will not call job.Done ever.
func (queue *Queue) Schedule(ctx context.Context, req *crawl.Request) (err error) {
	r := &request{Request: req}
	if deadline, ok := ctx.Deadline(); ok {
		r.Deadline = deadline
	}
	return queue.Producer.PublishJSON(queue.topic, r)
}

// Get - Gets job from channel.
func (queue *Queue) Get() (crawl.Job, error) {
	job, ok := <-queue.channel
	if !ok {
		return nil, io.EOF
	}
	return job, nil
}

// Close - Closes consumer and producer.
func (queue *Queue) Close() (err error) {
	if queue.Producer != nil {
		queue.Producer.Stop()
	}
	if queue.Consumer != nil {
		queue.Consumer.Stop()
	}
	if queue.channel != nil {
		close(queue.channel)
	}
	return
}

func (queue *Queue) nsqHandler(msg *consumer.Message) {
	req := new(request)
	err := msg.ReadJSON(req)
	if err != nil {
		glog.V(3).Infof("nsq json (%s) error: %v", msg.Body, err)
		msg.GiveUp()
		return
	}

	// Check if deadline exceeded
	if !req.Deadline.IsZero() && time.Now().After(req.Deadline) {
		glog.V(3).Infof("request deadline exceeded (%s)", msg.Body)
		msg.GiveUp()
		return
	}

	// Request context
	ctx := context.Background()

	// Set request deadline
	if !req.Deadline.IsZero() {
		ctx, _ = context.WithDeadline(ctx, req.Deadline)
	}

	// Set nsq message in context
	ctx = consumer.WithMessage(ctx, msg)

	// Schedule job in memory
	queue.channel <- &nsqJob{msg: msg, req: req.Request, ctx: ctx}
}

type request struct {
	Request  *crawl.Request `json:"request,omitempty"`
	Deadline time.Time      `json:"deadline,omitempty"`
}

type nsqJob struct {
	msg *consumer.Message
	req *crawl.Request
	ctx context.Context
}

func (job *nsqJob) Context() context.Context { return job.ctx }
func (job *nsqJob) Request() *crawl.Request  { return job.req }
func (job *nsqJob) Done()                    { job.msg.Success() }
<commit_msg>nsq: use grpc context metadata<commit_after>package nsqcrawl

import (
	"io"
	"time"

	"google.golang.org/grpc/metadata"

	"github.com/golang/glog"
	"golang.org/x/net/context"

	"github.com/crackcomm/crawl"
	"github.com/crackcomm/nsqueue/consumer"
	"github.com/crackcomm/nsqueue/producer"
)

// NewQueue - Creates nsq consumer and producer.
func NewQueue(topic, channel string, maxInFlight int) *Queue {
	q := &Queue{
		Consumer: consumer.New(),
		Producer: producer.New(),
		channel:  make(chan *nsqJob, maxInFlight+1),
		topic:    topic,
	}
	q.Consumer.Register(topic, channel, maxInFlight, q.nsqHandler)
	return q
}

// NewProducer - Creates queue producer.
func NewProducer(topic string) *Queue {
	return &Queue{
		Producer: producer.New(),
		topic:    topic,
	}
}

// Queue - NSQ Queue.
type Queue struct {
	*consumer.Consumer
	*producer.Producer

	topic   string
	channel chan *nsqJob
}

// Schedule - Schedules job in nsq.
// It will not call job.Done ever.
func (queue *Queue) Schedule(ctx context.Context, req *crawl.Request) (err error) {
	md, _ := metadata.FromContext(ctx)
	r := &request{Request: req, Metadata: md}
	if deadline, ok := ctx.Deadline(); ok {
		r.Deadline = deadline
	}
	return queue.Producer.PublishJSON(queue.topic, r)
}

// Get - Gets job from channel.
func (queue *Queue) Get() (crawl.Job, error) {
	job, ok := <-queue.channel
	if !ok {
		return nil, io.EOF
	}
	return job, nil
}

// Close - Closes consumer and producer.
func (queue *Queue) Close() (err error) {
	if queue.Producer != nil {
		queue.Producer.Stop()
	}
	if queue.Consumer != nil {
		queue.Consumer.Stop()
	}
	if queue.channel != nil {
		close(queue.channel)
	}
	return
}

func (queue *Queue) nsqHandler(msg *consumer.Message) {
	req := new(request)
	err := msg.ReadJSON(req)
	if err != nil {
		glog.V(3).Infof("nsq json (%s) error: %v", msg.Body, err)
		msg.GiveUp()
		return
	}

	// Check if deadline exceeded
	if !req.Deadline.IsZero() && time.Now().After(req.Deadline) {
		glog.V(3).Infof("request deadline exceeded (%s)", msg.Body)
		msg.GiveUp()
		return
	}

	// Request context
	ctx := context.Background()

	// Set request deadline
	if !req.Deadline.IsZero() {
		ctx, _ = context.WithDeadline(ctx, req.Deadline)
	}

	// Set nsq message in context
	ctx = consumer.WithMessage(ctx, msg)

	// Set metadata in context
	if len(req.Metadata) > 0 {
		ctx = metadata.NewContext(ctx, req.Metadata)
	}

	// Schedule job in memory
	queue.channel <- &nsqJob{msg: msg, req: req.Request, ctx: ctx}
}

type request struct {
	Request  *crawl.Request `json:"request,omitempty"`
	Deadline time.Time      `json:"deadline,omitempty"`
	Metadata metadata.MD    `json:"metadata,omitempty"`
}

type nsqJob struct {
	msg *consumer.Message
	req *crawl.Request
	ctx context.Context
}

func (job *nsqJob) Context() context.Context { return job.ctx }
func (job *nsqJob) Request() *crawl.Request  { return job.req }
func (job *nsqJob) Done()                    { job.msg.Success() }
<|endoftext|>


<commit_before>package zeroconf

// convenience routines

import (
	"fmt"
	"log"
	"net"
	"os"
	"strings"
	"time"

	dns "github.com/miekg/godns"
)

type Host struct {
	Name   string
	Domain string
	Addrs  []net.IP
}

type Service struct {
	*Host
	*Type
	Port uint16
}

type proto int

func (p proto) String() string {
	if p == tcp {
		return "_tcp"
	}
	return "_udp"
}

const (
	tcp proto = iota
	udp
)

type Type struct {
	name string
	proto
}

var (
	Ssh = &Type{"_ssh", tcp}
)

func (s *Service) fqdn() string {
	return fmt.Sprintf("%s.%s", s.Name, s.Domain)
}

func (s *Service) service() string {
	return fmt.Sprintf("%s.%s.%s", s.Type.name, s.Type.proto.String(), s.Domain)
}

func (s *Service) serviceFqdn() string {
	return s.Name + "." + s.service()
}

func Publish(z Zone, s *Service) {
	for _, addr := range s.Addrs {
		a := dns.NewRR(dns.TypeA).(*dns.RR_A)
		a.Hdr.Name = s.fqdn()
		a.Hdr.Class = dns.ClassINET
		a.Hdr.Ttl = 3600
		a.A = addr
		PublishRR(z, a)
	}

	ptr := dns.NewRR(dns.TypePTR).(*dns.RR_PTR)
	ptr.Hdr.Name = s.service()
	ptr.Hdr.Class = dns.ClassINET
	ptr.Hdr.Ttl = 3600
	ptr.Ptr = s.serviceFqdn()
	PublishRR(z, ptr)

	srv := dns.NewRR(dns.TypeSRV).(*dns.RR_SRV)
	srv.Hdr.Name = s.serviceFqdn()
	srv.Hdr.Class = dns.ClassINET
	srv.Hdr.Ttl = 3600
	srv.Port = s.Port
	srv.Target = s.fqdn()
	PublishRR(z, srv)

	txt := dns.NewRR(dns.TypeTXT).(*dns.RR_TXT)
	txt.Hdr.Name = s.serviceFqdn()
	txt.Hdr.Class = dns.ClassINET
	txt.Hdr.Ttl = 3600
	PublishRR(z, txt)
}

func PublishRR(z Zone, rr dns.RR) {
	z.Add(&Entry{
		Publish: true,
		RR:      rr,
	})
}

type Entry struct {
	Expires int64 // the timestamp when this record will expire in nanoseconds
	Publish bool  // whether this entry should be broadcast in response to an mDNS question
	RR      dns.RR
	Source  *net.UDPAddr
}

func (e *Entry) fqdn() string {
	return e.RR.Header().Name
}

func (e *Entry) Domain() string {
	return "local." // TODO
}

func (e *Entry) Name() string {
	return strings.Split(e.fqdn(), ".")[0]
}

func (e *Entry) Type() string {
	return e.fqdn()[len(e.Name()+".") : len(e.fqdn())-len(e.Domain())]
}

type Query struct {
	Question dns.Question
	Result   chan *Entry
}

type entries []*Entry

func (e entries) contains(entry *Entry) bool {
	for _, ee := range e {
		if equals(ee.RR, entry.RR) {
			return true
		}
	}
	return false
}

type zone struct {
	Domain        string
	entries       map[string]entries
	add           chan *Entry // add entries to zone
	query         chan *Query // query exsting entries in zone
	subscribe     chan *Query // subscribe to new entries added to zone
	subscriptions []*Query
}

type Zone interface {
	Query(dns.Question) chan *Entry
	Subscribe(uint16) chan *Entry
	Add(*Entry)
}

func NewLocalZone() Zone {
	z := &zone{
		Domain:    "local.",
		entries:   make(map[string]entries),
		add:       make(chan *Entry, 16),
		query:     make(chan *Query, 16),
		subscribe: make(chan *Query, 16),
	}
	go z.mainloop()
	if err := z.listen(IPv4MCASTADDR); err != nil {
		log.Fatal("Failed to listen: ", err)
	}
	if err := z.listen(IPv6MCASTADDR); err != nil {
		log.Fatal("Failed to listen: ", err)
	}
	return z
}

func (z *zone) mainloop() {
	for {
		select {
		case entry := <-z.add:
			z.add0(entry)
		case q := <-z.query:
			z.query0(q)
		case q := <-z.subscribe:
			z.subscriptions = append(z.subscriptions, q)
		}
	}
}

func (z *zone) Add(e *Entry) {
	z.add <- e
}

func (z *zone) Subscribe(t uint16) chan *Entry {
	res := make(chan *Entry, 16)
	z.subscribe <- &Query{
		dns.Question{
			"",
			t,
			dns.ClassINET,
		},
		res,
	}
	return res
}

func (z *zone) Query(q dns.Question) chan *Entry {
	res := make(chan *Entry, 16)
	z.query <- &Query{q, res}
	return res
}

func (z *zone) add0(entry *Entry) {
	if !z.entries[entry.fqdn()].contains(entry) {
		z.entries[entry.fqdn()] = append(z.entries[entry.fqdn()], entry)
		z.publish(entry)
	}
}

func (z *zone) publish(entry *Entry) {
	for _, c := range z.subscriptions {
		if c.matches(entry) {
			c.Result <- entry
		}
	}
}

func (z *zone) query0(query *Query) {
	for _, entry := range z.entries[query.Question.Name] {
		if query.matches(entry) {
			query.Result <- entry
		}
	}
	close(query.Result)
}

func (q *Query) matches(entry *Entry) bool {
	return q.Question.Qtype == dns.TypeANY || q.Question.Qtype == entry.RR.Header().Rrtype
}

func equals(this, that dns.RR) bool {
	if _, ok := this.(*dns.RR_ANY); ok {
		return true // *RR_ANY matches anything
	}
	if _, ok := that.(*dns.RR_ANY); ok {
		return true // *RR_ANY matches all
	}
	return false
}

const (
	seconds = 1e9
)

var (
	IPv4MCASTADDR = &net.UDPAddr{
		IP:   net.ParseIP("224.0.0.251"),
		Port: 5353,
	}

	IPv6MCASTADDR = &net.UDPAddr{
		IP:   net.ParseIP("ff02::fb"),
		Port: 5353,
	}
)

type connector struct {
	*net.UDPAddr
	*net.UDPConn
	Zone
}

func (z *zone) listen(addr *net.UDPAddr) os.Error {
	conn, err := openSocket(addr)
	if err != nil {
		return err
	}
	if err := conn.JoinGroup(nil, addr.IP); err != nil {
		return err
	}
	c := &connector{
		UDPAddr: addr,
		UDPConn: conn,
		Zone:    z,
	}
	go c.mainloop()
	return nil
}

func openSocket(addr *net.UDPAddr) (*net.UDPConn, os.Error) {
	switch addr.IP.To4() {
	case nil:
		return net.ListenUDP("udp6", &net.UDPAddr{
			IP:   net.IPv6zero,
			Port: addr.Port,
		})
	default:
		return net.ListenUDP("udp4", &net.UDPAddr{
			IP:   net.IPv4zero,
			Port: addr.Port,
		})
	}
	panic("unreachable")
}

func (c *connector) mainloop() {
	type incoming struct {
		*dns.Msg
		*net.UDPAddr
	}
	in := make(chan incoming, 32)
	out := make(chan *dns.Msg, 32)
	go func() {
		for {
			msg, addr, err := c.readMessage()
			if err != nil {
				log.Fatalf("Cound not read from %s: %s", c.UDPConn, err)
			}
			in <- incoming{msg, addr}
		}
	}()

	for {
		select {
		case msg := <-in:
			if msg.IsQuestion() {
				r := new(dns.Msg)
				r.MsgHdr.Response = true
				for _, result := range c.query(msg.Question) {
					if result.Publish {
						r.Answer = append(r.Answer, result.RR)
					}
				}
				if len(r.Answer) > 0 {
					fmt.Println(r)
					out <- r
				}
			} else {
				for _, rr := range msg.Answer {
					c.Add(&Entry{
						Expires: time.Nanoseconds() + int64(rr.Header().Ttl*seconds),
						Publish: false,
						RR:      rr,
						Source:  msg.UDPAddr,
					})
				}
			}
		case msg := <-out:
			if err := c.writeMessage(msg); err != nil {
				log.Fatalf("Cannot send: %s", err)
			}
		}
	}
}

func (c *connector) query(qs []dns.Question) []*Entry {
	result := make([]*Entry, 0)
	for _, q := range qs {
		for r := range c.Query(q) {
			result = append(result, r)
		}
	}
	return result
}

func (c *connector) writeMessage(msg *dns.Msg) (err os.Error) {
	if buf, ok := msg.Pack(); ok {
		_, err = c.WriteToUDP(buf, c.UDPAddr)
	}
	return
}

func (c *connector) readMessage() (*dns.Msg, *net.UDPAddr, os.Error) {
	buf := make([]byte, 1500)
	read, addr, err := c.ReadFromUDP(buf)
	if err != nil {
		return nil, nil, err
	}
	if msg := new(dns.Msg); msg.Unpack(buf[:read]) {
		return msg, addr, nil
	}
	return nil, addr, os.NewError("Unable to unpack buffer")
}
<commit_msg>Initial support for findAdditional<commit_after>package zeroconf

// convenience routines

import (
	"fmt"
	"log"
	"net"
	"os"
	"strings"
	"time"

	dns "github.com/miekg/godns"
)

type Host struct {
	Name   string
	Domain string
	Addrs  []net.IP
}

type Service struct {
	*Host
	*Type
	Port uint16
}

type proto int

func (p proto) String() string {
	if p == tcp {
		return "_tcp"
	}
	return "_udp"
}

const (
	tcp proto = iota
	udp
)

type Type struct {
	name string
	proto
}

var (
	Ssh = &Type{"_ssh", tcp}
)

func (s *Service) fqdn() string {
	return fmt.Sprintf("%s.%s", s.Name, s.Domain)
}

func (s *Service) service() string {
	return fmt.Sprintf("%s.%s.%s", s.Type.name, s.Type.proto.String(), s.Domain)
}

func (s *Service) serviceFqdn() string {
	return s.Name + "." + s.service()
}

func Publish(z Zone, s *Service) {
	for _, addr := range s.Addrs {
		a := dns.NewRR(dns.TypeA).(*dns.RR_A)
		a.Hdr.Name = s.fqdn()
		a.Hdr.Class = dns.ClassINET
		a.Hdr.Ttl = 3600
		a.A = addr
		PublishRR(z, a)
	}

	ptr := dns.NewRR(dns.TypePTR).(*dns.RR_PTR)
	ptr.Hdr.Name = s.service()
	ptr.Hdr.Class = dns.ClassINET
	ptr.Hdr.Ttl = 3600
	ptr.Ptr = s.serviceFqdn()
	PublishRR(z, ptr)

	srv := dns.NewRR(dns.TypeSRV).(*dns.RR_SRV)
	srv.Hdr.Name = s.serviceFqdn()
	srv.Hdr.Class = dns.ClassINET
	srv.Hdr.Ttl = 3600
	srv.Port = s.Port
	srv.Target = s.fqdn()
	PublishRR(z, srv)

	txt := dns.NewRR(dns.TypeTXT).(*dns.RR_TXT)
	txt.Hdr.Name = s.serviceFqdn()
	txt.Hdr.Class = dns.ClassINET
	txt.Hdr.Ttl = 3600
	PublishRR(z, txt)
}

func PublishRR(z Zone, rr dns.RR) {
	z.Add(&Entry{
		Publish: true,
		RR:      rr,
	})
}

type Entry struct {
	Expires int64 // the timestamp when this record will expire in nanoseconds
	Publish bool  // whether this entry should be broadcast in response to an mDNS question
	RR      dns.RR
	Source  *net.UDPAddr
}

func (e *Entry) fqdn() string {
	return e.RR.Header().Name
}

func (e *Entry) Domain() string {
	return "local." // TODO
}

func (e *Entry) Name() string {
	return strings.Split(e.fqdn(), ".")[0]
}

func (e *Entry) Type() string {
	return e.fqdn()[len(e.Name()+".") : len(e.fqdn())-len(e.Domain())]
}

type Query struct {
	Question dns.Question
	Result   chan *Entry
}

type entries []*Entry

func (e entries) contains(entry *Entry) bool {
	for _, ee := range e {
		if equals(ee.RR, entry.RR) {
			return true
		}
	}
	return false
}

type zone struct {
	Domain        string
	entries       map[string]entries
	add           chan *Entry // add entries to zone
	query         chan *Query // query exsting entries in zone
	subscribe     chan *Query // subscribe to new entries added to zone
	subscriptions []*Query
}

type Zone interface {
	Query(dns.Question) chan *Entry
	Subscribe(uint16) chan *Entry
	Add(*Entry)
}

func NewLocalZone() Zone {
	z := &zone{
		Domain:    "local.",
		entries:   make(map[string]entries),
		add:       make(chan *Entry, 16),
		query:     make(chan *Query, 16),
		subscribe: make(chan *Query, 16),
	}
	go z.mainloop()
	if err := z.listen(IPv4MCASTADDR); err != nil {
		log.Fatal("Failed to listen: ", err)
	}
	if err := z.listen(IPv6MCASTADDR); err != nil {
		log.Fatal("Failed to listen: ", err)
	}
	return z
}

func (z *zone) mainloop() {
	for {
		select {
		case entry := <-z.add:
			z.add0(entry)
		case q := <-z.query:
			z.query0(q)
		case q := <-z.subscribe:
			z.subscriptions = append(z.subscriptions, q)
		}
	}
}

func (z *zone) Add(e *Entry) {
	z.add <- e
}

func (z *zone) Subscribe(t uint16) chan *Entry {
	res := make(chan *Entry, 16)
	z.subscribe <- &Query{
		dns.Question{
			"",
			t,
			dns.ClassINET,
		},
		res,
	}
	return res
}

func (z *zone) Query(q dns.Question) chan *Entry {
	res := make(chan *Entry, 16)
	z.query <- &Query{q, res}
	return res
}

func (z *zone) add0(entry *Entry) {
	if !z.entries[entry.fqdn()].contains(entry) {
		z.entries[entry.fqdn()] = append(z.entries[entry.fqdn()], entry)
		z.publish(entry)
	}
}

func (z *zone) publish(entry *Entry) {
	for _, c := range z.subscriptions {
		if c.matches(entry) {
			c.Result <- entry
		}
	}
}

func (z *zone) query0(query *Query) {
	for _, entry := range z.entries[query.Question.Name] {
		if query.matches(entry) {
			query.Result <- entry
		}
	}
	close(query.Result)
}

func (q *Query) matches(entry *Entry) bool {
	return q.Question.Qtype == dns.TypeANY || q.Question.Qtype == entry.RR.Header().Rrtype
}

func equals(this, that dns.RR) bool {
	if _, ok := this.(*dns.RR_ANY); ok {
		return true // *RR_ANY matches anything
	}
	if _, ok := that.(*dns.RR_ANY); ok {
		return true // *RR_ANY matches all
	}
	return false
}

const (
	seconds = 1e9
)

var (
	IPv4MCASTADDR = &net.UDPAddr{
		IP:   net.ParseIP("224.0.0.251"),
		Port: 5353,
	}

	IPv6MCASTADDR = &net.UDPAddr{
		IP:   net.ParseIP("ff02::fb"),
		Port: 5353,
	}
)

type connector struct {
	*net.UDPAddr
	*net.UDPConn
	Zone
}

func (z *zone) listen(addr *net.UDPAddr) os.Error {
	conn, err := openSocket(addr)
	if err != nil {
		return err
	}
	if err := conn.JoinGroup(nil, addr.IP); err != nil {
		return err
	}
	c := &connector{
		UDPAddr: addr,
		UDPConn: conn,
		Zone:    z,
	}
	go c.mainloop()
	return nil
}

func openSocket(addr *net.UDPAddr) (*net.UDPConn, os.Error) {
	switch addr.IP.To4() {
	case nil:
		return net.ListenUDP("udp6", &net.UDPAddr{
			IP:   net.IPv6zero,
			Port: addr.Port,
		})
	default:
		return net.ListenUDP("udp4", &net.UDPAddr{
			IP:   net.IPv4zero,
			Port: addr.Port,
		})
	}
	panic("unreachable")
}

func (c *connector) mainloop() {
	type incoming struct {
		*dns.Msg
		*net.UDPAddr
	}
	in := make(chan incoming, 32)
	go func() {
		for {
			msg, addr, err := c.readMessage()
			if err != nil {
				log.Fatalf("Cound not read from %s: %s", c.UDPConn, err)
			}
			in <- incoming{msg, addr}
		}
	}()

	for {
		select {
		case msg := <-in:
			if msg.IsQuestion() {
				r := new(dns.Msg)
				r.MsgHdr.Response = true
				for _, result := range c.query(msg.Question) {
					if result.Publish {
						r.Answer = append(r.Answer, result.RR)
					}
				}
				if len(r.Answer) > 0 {
					r.Extra = c.findAdditional(r.Answer)
					fmt.Println(r)
					if err := c.writeMessage(r); err != nil {
                           	     		log.Fatalf("Cannot send: %s", err)
                        		}

				}
			} else {
				for _, rr := range msg.Answer {
					c.Add(&Entry{
						Expires: time.Nanoseconds() + int64(rr.Header().Ttl*seconds),
						Publish: false,
						RR:      rr,
						Source:  msg.UDPAddr,
					})
				}
			}
		}
	}
}

func (c *connector) findAdditional(rr []dns.RR) []dns.RR {
	return []dns.RR{}
}

func (c *connector) query(qs []dns.Question) []*Entry {
	result := make([]*Entry, 0)
	for _, q := range qs {
		for r := range c.Query(q) {
			result = append(result, r)
		}
	}
	return result
}

func (c *connector) writeMessage(msg *dns.Msg) (err os.Error) {
	if buf, ok := msg.Pack(); ok {
		_, err = c.WriteToUDP(buf, c.UDPAddr)
	}
	return
}

func (c *connector) readMessage() (*dns.Msg, *net.UDPAddr, os.Error) {
	buf := make([]byte, 1500)
	read, addr, err := c.ReadFromUDP(buf)
	if err != nil {
		return nil, nil, err
	}
	if msg := new(dns.Msg); msg.Unpack(buf[:read]) {
		return msg, addr, nil
	}
	return nil, addr, os.NewError("Unable to unpack buffer")
}
<|endoftext|>


<commit_before>package zwave

import (
	"fmt"

	"github.com/looplab/fsm"
)

type FrameLayer struct {
	transportLayer *TransportLayer
	frameParser    *FrameParser

	parserInput  chan<- byte
	parserOutput <-chan *FrameParseEvent

	pendingWrites chan *Frame
	frameOutput   chan *Frame

	state *fsm.FSM
}

func NewFrameLayer(transportLayer *TransportLayer) *FrameLayer {
	parserInput := make(chan byte)
	parserOutput := make(chan *FrameParseEvent)

	frameLayer := &FrameLayer{
		transportLayer: transportLayer,
		frameParser:    NewFrameParser(parserInput, parserOutput),

		parserInput:  parserInput,
		parserOutput: parserOutput,

		pendingWrites: make(chan *Frame),
		frameOutput:   make(chan *Frame),
	}

	frameLayer.state = fsm.NewFSM(
		"idle",
		fsm.Events{
			{Name: "TX_DATA", Src: []string{"idle"}, Dst: "awaiting_ack"},
			{Name: "RX_ACK", Src: []string{"awaiting_ack"}, Dst: "idle"},
			{Name: "RX_NAK", Src: []string{"awaiting_ack"}, Dst: "idle"},
			{Name: "RX_CAN", Src: []string{"idle", "awaiting_ack"}, Dst: "idle"},
			{Name: "RX_SOF", Src: []string{"idle"}, Dst: "parse_frame"},
			{Name: "RX_COMPLETE", Src: []string{"parse_frame"}, Dst: "idle"},
		},
		fsm.Callbacks{
			"before_event": func(e *fsm.Event) {
				fmt.Printf("%s: %s -> %s\n", e.Event, e.Src, e.Dst)
			},
		},
	)

	go frameLayer.loop()

	return frameLayer
}

func (layer *FrameLayer) loop() {
	transportBytesIn := layer.transportLayer.Read()

start:
	for {
		select {

		// Read
		case firstByte := <-transportBytesIn:
			layer.parserInput <- firstByte

			for {
				select {
				case event := <-layer.parserOutput:

					if event.status == FrameParseOk {
						layer.sendAck()
						fmt.Println("hey")
						layer.frameOutput <- event.frame
						fmt.Println("yo")
					} else if event.status == FrameParseNotOk {
						layer.sendNak()
						layer.frameOutput <- event.frame
					} else {
						fmt.Println("frame parse timeout or something")
					}

					goto start

				case nextByte := <-transportBytesIn:
					layer.parserInput <- nextByte
				}
			}

		// Write
		case writeFrame := <-layer.pendingWrites:
			layer.writeToTransport(writeFrame.Marshal())

		}
	}
}

func (f *FrameLayer) Write(frame *Frame) {
	go func() {
		f.pendingWrites <- frame
	}()
}

func (f *FrameLayer) GetOutput() <-chan *Frame {
	return f.frameOutput
}

func (f *FrameLayer) writeToTransport(buf []byte) (int, error) {
	return f.transportLayer.Write(buf)
}

func (f *FrameLayer) sendAck() error {
	_, err := f.transportLayer.Write([]byte{FrameSOFAck})
	return err
}

func (f *FrameLayer) sendNak() error {
	_, err := f.transportLayer.Write([]byte{FrameSOFNak})
	return err
}
<commit_msg>remove console output<commit_after>package zwave

import (
	"fmt"

	"github.com/looplab/fsm"
)

type FrameLayer struct {
	transportLayer *TransportLayer
	frameParser    *FrameParser

	parserInput  chan<- byte
	parserOutput <-chan *FrameParseEvent

	pendingWrites chan *Frame
	frameOutput   chan *Frame

	state *fsm.FSM
}

func NewFrameLayer(transportLayer *TransportLayer) *FrameLayer {
	parserInput := make(chan byte)
	parserOutput := make(chan *FrameParseEvent)

	frameLayer := &FrameLayer{
		transportLayer: transportLayer,
		frameParser:    NewFrameParser(parserInput, parserOutput),

		parserInput:  parserInput,
		parserOutput: parserOutput,

		pendingWrites: make(chan *Frame),
		frameOutput:   make(chan *Frame),
	}

	frameLayer.state = fsm.NewFSM(
		"idle",
		fsm.Events{
			{Name: "TX_DATA", Src: []string{"idle"}, Dst: "awaiting_ack"},
			{Name: "RX_ACK", Src: []string{"awaiting_ack"}, Dst: "idle"},
			{Name: "RX_NAK", Src: []string{"awaiting_ack"}, Dst: "idle"},
			{Name: "RX_CAN", Src: []string{"idle", "awaiting_ack"}, Dst: "idle"},
			{Name: "RX_SOF", Src: []string{"idle"}, Dst: "parse_frame"},
			{Name: "RX_COMPLETE", Src: []string{"parse_frame"}, Dst: "idle"},
		},
		fsm.Callbacks{
			"before_event": func(e *fsm.Event) {
				fmt.Printf("%s: %s -> %s\n", e.Event, e.Src, e.Dst)
			},
		},
	)

	go frameLayer.loop()

	return frameLayer
}

func (layer *FrameLayer) loop() {
	transportBytesIn := layer.transportLayer.Read()

start:
	for {
		select {

		// Read
		case firstByte := <-transportBytesIn:
			layer.parserInput <- firstByte

			for {
				select {
				case event := <-layer.parserOutput:

					if event.status == FrameParseOk {
						layer.sendAck()
						layer.frameOutput <- event.frame
					} else if event.status == FrameParseNotOk {
						layer.sendNak()
						layer.frameOutput <- event.frame
					} else {
						fmt.Println("frame parse timeout or something")
					}

					goto start

				case nextByte := <-transportBytesIn:
					layer.parserInput <- nextByte
				}
			}

		// Write
		case writeFrame := <-layer.pendingWrites:
			layer.writeToTransport(writeFrame.Marshal())

		}
	}
}

func (f *FrameLayer) Write(frame *Frame) {
	go func() {
		f.pendingWrites <- frame
	}()
}

func (f *FrameLayer) GetOutput() <-chan *Frame {
	return f.frameOutput
}

func (f *FrameLayer) writeToTransport(buf []byte) (int, error) {
	return f.transportLayer.Write(buf)
}

func (f *FrameLayer) sendAck() error {
	_, err := f.transportLayer.Write([]byte{FrameSOFAck})
	return err
}

func (f *FrameLayer) sendNak() error {
	_, err := f.transportLayer.Write([]byte{FrameSOFNak})
	return err
}
<|endoftext|>


<commit_before>package backend

import (
	"fmt"

	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/terraform/configs"
	"github.com/hashicorp/terraform/terraform"
	"github.com/hashicorp/terraform/tfdiags"
	"github.com/zclconf/go-cty/cty"
)

// UnparsedVariableValue represents a variable value provided by the caller
// whose parsing must be deferred until configuration is available.
//
// This exists to allow processing of variable-setting arguments (e.g. in the
// command package) to be separated from parsing (in the backend package).
type UnparsedVariableValue interface {
	// ParseVariableValue information in the provided variable configuration
	// to parse (if necessary) and return the variable value encapsulated in
	// the receiver.
	//
	// If error diagnostics are returned, the resulting value may be invalid
	// or incomplete.
	ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics)
}

// ParseVariableValues processes a map of unparsed variable values by
// correlating each one with the given variable declarations which should
// be from a root module.
//
// The map of unparsed variable values should include variables from all
// possible root module declarations sources such that it is as complete as
// it can possibly be for the current operation. If any declared variables
// are not included in the map, ParseVariableValues will either substitute
// a configured default value or produce an error.
//
// If this function returns without any errors in the diagnostics, the
// resulting input values map is guaranteed to be valid and ready to pass
// to terraform.NewContext. If the diagnostics contains errors, the returned
// InputValues may be incomplete but will include the subset of variables
// that were successfully processed, allowing for careful analysis of the
// partial result.
func ParseVariableValues(vv map[string]UnparsedVariableValue, decls map[string]*configs.Variable) (terraform.InputValues, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics
	ret := make(terraform.InputValues, len(vv))

	// Currently we're generating only warnings for undeclared variables
	// defined in files (see below) but we only want to generate a few warnings
	// at a time because existing deployments may have lots of these and
	// the result can therefore be overwhelming.
	seenUndeclaredInFile := 0

	for name, rv := range vv {
		var mode configs.VariableParsingMode
		config, declared := decls[name]
		if declared {
			mode = config.ParsingMode
		} else {
			mode = configs.VariableParseLiteral
		}

		val, valDiags := rv.ParseVariableValue(mode)
		diags = diags.Append(valDiags)
		if valDiags.HasErrors() {
			continue
		}

		if !declared {
			switch val.SourceType {
			case terraform.ValueFromConfig, terraform.ValueFromAutoFile, terraform.ValueFromNamedFile:
				// These source types have source ranges, so we can produce
				// a nice error message with good context.
				//
				// This one is a warning for now because there is an existing
				// pattern of providing a file containing the superset of
				// variables across all configurations in an organization. This
				// is deprecated in v0.12.0 because it's more important to give
				// feedback to users who make typos. Those using this approach
				// should migrate to using environment variables instead before
				// this becomes an error in a future major release.
				if seenUndeclaredInFile < 3 {
					diags = diags.Append(&hcl.Diagnostic{
						Severity: hcl.DiagWarning,
						Summary:  "Value for undeclared variable",
						Detail:   fmt.Sprintf("The root module does not declare a variable named %q. To use this value, add a \"variable\" block to the configuration.\n\nUsing a variables file to set an undeclared variable is deprecated and will become an error in a future release. If you wish to provide certain \"global\" settings to all configurations in your organization, use TF_VAR_... environment variables to set these instead.", name),
						Subject:  val.SourceRange.ToHCL().Ptr(),
					})
				}
				seenUndeclaredInFile++

			case terraform.ValueFromEnvVar:
				// We allow and ignore undeclared names for environment
				// variables, because users will often set these globally
				// when they are used across many (but not necessarily all)
				// configurations.
			case terraform.ValueFromCLIArg:
				diags = diags.Append(tfdiags.Sourceless(
					tfdiags.Error,
					"Value for undeclared variable",
					fmt.Sprintf("A variable named %q was assigned on the command line, but the root module does not declare a variable of that name. To use this value, add a \"variable\" block to the configuration.", name),
				))
			default:
				// For all other source types we are more vague, but other situations
				// don't generally crop up at this layer in practice.
				diags = diags.Append(tfdiags.Sourceless(
					tfdiags.Error,
					"Value for undeclared variable",
					fmt.Sprintf("A variable named %q was assigned a value, but the root module does not declare a variable of that name. To use this value, add a \"variable\" block to the configuration.", name),
				))
			}
			continue
		}

		ret[name] = val
	}

	if seenUndeclaredInFile >= 3 {
		extras := seenUndeclaredInFile - 2
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagWarning,
			Summary:  "Values for undeclared variables",
			Detail:   fmt.Sprintf("In addition to the other similar warnings shown, %d other variable(s) defined without being declared.", extras),
		})
	}

	// By this point we should've gathered all of the required root module
	// variables from one of the many possible sources. We'll now populate
	// any we haven't gathered as their defaults and fail if any of the
	// missing ones are required.
	for name, vc := range decls {
		if _, defined := ret[name]; defined {
			continue
		}

		if vc.Required() {
			diags = diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "No value for required variable",
				Detail:   fmt.Sprintf("The root module input variable %q is not set, and has no default value. Use a -var or -var-file command line argument to provide a value for this variable.", name),
				Subject:  vc.DeclRange.Ptr(),
			})

			// We'll include a placeholder value anyway, just so that our
			// result is complete for any calling code that wants to cautiously
			// analyze it for diagnostic purposes. Since our diagnostics now
			// includes an error, normal processing will ignore this result.
			ret[name] = &terraform.InputValue{
				Value:       cty.DynamicVal,
				SourceType:  terraform.ValueFromConfig,
				SourceRange: tfdiags.SourceRangeFromHCL(vc.DeclRange),
			}
		} else {
			ret[name] = &terraform.InputValue{
				Value:       vc.Default,
				SourceType:  terraform.ValueFromConfig,
				SourceRange: tfdiags.SourceRangeFromHCL(vc.DeclRange),
			}
		}
	}

	return ret, diags
}
<commit_msg>Avoid leaking sensitive values in undeclared warnings<commit_after>package backend

import (
	"fmt"

	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/terraform/configs"
	"github.com/hashicorp/terraform/terraform"
	"github.com/hashicorp/terraform/tfdiags"
	"github.com/zclconf/go-cty/cty"
)

// UnparsedVariableValue represents a variable value provided by the caller
// whose parsing must be deferred until configuration is available.
//
// This exists to allow processing of variable-setting arguments (e.g. in the
// command package) to be separated from parsing (in the backend package).
type UnparsedVariableValue interface {
	// ParseVariableValue information in the provided variable configuration
	// to parse (if necessary) and return the variable value encapsulated in
	// the receiver.
	//
	// If error diagnostics are returned, the resulting value may be invalid
	// or incomplete.
	ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics)
}

// ParseVariableValues processes a map of unparsed variable values by
// correlating each one with the given variable declarations which should
// be from a root module.
//
// The map of unparsed variable values should include variables from all
// possible root module declarations sources such that it is as complete as
// it can possibly be for the current operation. If any declared variables
// are not included in the map, ParseVariableValues will either substitute
// a configured default value or produce an error.
//
// If this function returns without any errors in the diagnostics, the
// resulting input values map is guaranteed to be valid and ready to pass
// to terraform.NewContext. If the diagnostics contains errors, the returned
// InputValues may be incomplete but will include the subset of variables
// that were successfully processed, allowing for careful analysis of the
// partial result.
func ParseVariableValues(vv map[string]UnparsedVariableValue, decls map[string]*configs.Variable) (terraform.InputValues, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics
	ret := make(terraform.InputValues, len(vv))

	// Currently we're generating only warnings for undeclared variables
	// defined in files (see below) but we only want to generate a few warnings
	// at a time because existing deployments may have lots of these and
	// the result can therefore be overwhelming.
	seenUndeclaredInFile := 0

	for name, rv := range vv {
		var mode configs.VariableParsingMode
		config, declared := decls[name]
		if declared {
			mode = config.ParsingMode
		} else {
			mode = configs.VariableParseLiteral
		}

		val, valDiags := rv.ParseVariableValue(mode)
		diags = diags.Append(valDiags)
		if valDiags.HasErrors() {
			continue
		}

		if !declared {
			switch val.SourceType {
			case terraform.ValueFromConfig, terraform.ValueFromAutoFile, terraform.ValueFromNamedFile:
				// These source types have source ranges, so we can produce
				// a nice error message with good context.
				//
				// This one is a warning for now because there is an existing
				// pattern of providing a file containing the superset of
				// variables across all configurations in an organization. This
				// is deprecated in v0.12.0 because it's more important to give
				// feedback to users who make typos. Those using this approach
				// should migrate to using environment variables instead before
				// this becomes an error in a future major release.
				if seenUndeclaredInFile < 3 {
					diags = diags.Append(tfdiags.Sourceless(
						tfdiags.Warning,
						"Value for undeclared variable",
						fmt.Sprintf("The root module does not declare a variable named %q but a value was found in file %q. To use this value, add a \"variable\" block to the configuration.\n\nUsing a variables file to set an undeclared variable is deprecated and will become an error in a future release. If you wish to provide certain \"global\" settings to all configurations in your organization, use TF_VAR_... environment variables to set these instead.", name, val.SourceRange.Filename),
					))
				}
				seenUndeclaredInFile++

			case terraform.ValueFromEnvVar:
				// We allow and ignore undeclared names for environment
				// variables, because users will often set these globally
				// when they are used across many (but not necessarily all)
				// configurations.
			case terraform.ValueFromCLIArg:
				diags = diags.Append(tfdiags.Sourceless(
					tfdiags.Error,
					"Value for undeclared variable",
					fmt.Sprintf("A variable named %q was assigned on the command line, but the root module does not declare a variable of that name. To use this value, add a \"variable\" block to the configuration.", name),
				))
			default:
				// For all other source types we are more vague, but other situations
				// don't generally crop up at this layer in practice.
				diags = diags.Append(tfdiags.Sourceless(
					tfdiags.Error,
					"Value for undeclared variable",
					fmt.Sprintf("A variable named %q was assigned a value, but the root module does not declare a variable of that name. To use this value, add a \"variable\" block to the configuration.", name),
				))
			}
			continue
		}

		ret[name] = val
	}

	if seenUndeclaredInFile >= 3 {
		extras := seenUndeclaredInFile - 2
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagWarning,
			Summary:  "Values for undeclared variables",
			Detail:   fmt.Sprintf("In addition to the other similar warnings shown, %d other variable(s) defined without being declared.", extras),
		})
	}

	// By this point we should've gathered all of the required root module
	// variables from one of the many possible sources. We'll now populate
	// any we haven't gathered as their defaults and fail if any of the
	// missing ones are required.
	for name, vc := range decls {
		if _, defined := ret[name]; defined {
			continue
		}

		if vc.Required() {
			diags = diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "No value for required variable",
				Detail:   fmt.Sprintf("The root module input variable %q is not set, and has no default value. Use a -var or -var-file command line argument to provide a value for this variable.", name),
				Subject:  vc.DeclRange.Ptr(),
			})

			// We'll include a placeholder value anyway, just so that our
			// result is complete for any calling code that wants to cautiously
			// analyze it for diagnostic purposes. Since our diagnostics now
			// includes an error, normal processing will ignore this result.
			ret[name] = &terraform.InputValue{
				Value:       cty.DynamicVal,
				SourceType:  terraform.ValueFromConfig,
				SourceRange: tfdiags.SourceRangeFromHCL(vc.DeclRange),
			}
		} else {
			ret[name] = &terraform.InputValue{
				Value:       vc.Default,
				SourceType:  terraform.ValueFromConfig,
				SourceRange: tfdiags.SourceRangeFromHCL(vc.DeclRange),
			}
		}
	}

	return ret, diags
}
<|endoftext|>


<commit_before>package main

import "fmt"
import "io"
import "net/http"
import "os"
import "path/filepath"
import "regexp"
import "runtime"
import "sort"
import "strings"
import "github.com/google/go-github/github"
import "github.com/codegangsta/cli"
import "github.com/kardianos/osext"

var shell string
var dvmDir string
var debug bool
var silent bool

func main() {
  app := cli.NewApp()
  app.Name = "Docker Version Manager"
  app.Usage = "Manage multiple versions of the Docker client"
  app.EnableBashCompletion = true
  app.Flags = []cli.Flag{
    cli.StringFlag { Name: "dvm-dir", EnvVar: "DVM_DIR", Usage: "Specify an alternate DVM home directory, defaults to the current directory." },
    cli.StringFlag { Name: "shell", EnvVar: "SHELL", Usage: "Specify the shell format in which environment variables should be output, e.g. powershell, cmd or sh/bash. Defaults to sh/bash."},
    cli.BoolFlag { Name: "debug", Usage: "Print additional debug information." },
    cli.BoolFlag{ Name: "silent", EnvVar: "DVM_SILENT", Usage: "Suppress output. Errors will still be displayed."},
  }
  app.Commands = []cli.Command{
    {
      Name:    "install",
      Aliases: []string{"i"},
      Usage:   "dvm install <version>. Uses $DOCKER_VERSION if available",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        install(c.Args().First())
      },
    },
    {
      Name:    "use",
      Usage:   "dvm use <version>. Uses $DOCKER_VERSION if available",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        use(c.Args().First())
      },
    },
    {
      Name: "list-remote",
      Aliases: []string{"ls-remote"},
      Usage: "dvm list-remote [<pattern>]",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        listRemote(c.Args().First())
      },
    },
    {
      Name: "deactivate",
      Usage: "dvm deactivate. Undo effects of `dvm` on current shell",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        deactivate()
      },
    },
  }

  app.Run(os.Args)
}

func setGlobalVars(c *cli.Context) {
  debug = c.GlobalBool("debug")
  shell = c.GlobalString("shell")
  silent = c.GlobalBool("silent")
  dvmDir = c.GlobalString("dvm-dir")
  if dvmDir == "" {
    var err error
    dvmDir, err = osext.ExecutableFolder()
    if err != nil {
      fmt.Fprintln(os.Stderr, "Unable to determine DVM home directory")
      os.Exit(1)
    }
  }
}

func install(version string) {
  if version == "" {
    version = os.Getenv("DOCKER_VERSION")
  }

  if version == "" {
    fmt.Fprintln(os.Stderr, "The install command requires that a version is specified or the DOCKER_VERSION environment variable is set")
    os.Exit(127)
  }

  if !versionExists(version) {
    fmt.Fprintf(os.Stderr, "Version %s not found - try `dvm ls-remote` to browse available versions.\n", version)
    os.Exit(3)
  }

  versionDir := getVersionDir(version)

  // TODO: Support experimental

  if _, err := os.Stat(versionDir); err == nil {
    if !silent { fmt.Fprintf(os.Stderr, "%s is already installed\n", version) }
    use(version)
    return
  }

  if !silent { fmt.Printf("Installing %s\n", version) }

  url := fmt.Sprintf("https://get.docker.com/builds/%s/%s/%s", getDockerOS(), getDockerArch(), getDockerBinaryName(version))
  tmpPath := filepath.Join(getDvmDir(), ".tmp/docker", version, getBinaryName())
  downloadFile(url, tmpPath)
  binaryPath := filepath.Join(getDvmDir(), "bin/docker", version, getBinaryName())
  ensureParentDirectoryExists(binaryPath)
  err := os.Rename(tmpPath, binaryPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to copy %s to %s.\n%s\n", tmpPath, binaryPath, err)
  }

  if debug {
    fmt.Printf("Installed Docker %s to %s", version, binaryPath)
  }
}

func use(version string) {
  if version == "" {
    version = os.Getenv("DOCKER_VERSION")
  }

  if version == "" {
    fmt.Fprintln(os.Stderr, "The use command requires that a version is specified or the DOCKER_VERSION environment variable is set")
    os.Exit(127)
  }

  if !versionExists(version) {
    fmt.Fprintf(os.Stderr, "Version %s not found - try `dvm ls-remote` to browse available versions.\n", version)
    os.Exit(3)
  }

  // TODO: Handle system version

  ensureVersionIsInstalled(version)
  removePreviousDvmVersionFromPath()
  prependDvmVersionToPath(version)
  writeShellScript()

  if !silent {
    fmt.Printf("Now using Docker %s\n", version)
  }
}

func getDvmDir() string {
  return dvmDir
}

func getDockerBinaryName(version string) string {
    if runtime.GOOS == "windows" {
      return fmt.Sprintf("docker-%s.exe", version)
    }
    return fmt.Sprintf("docker-%s", version)
}

func getBinaryName() string {
    if runtime.GOOS == "windows" {
      return "docker.exe"
    }
    return "docker"
}

func deactivate() {
  removePreviousDvmVersionFromPath()
  writeShellScript()
}

func prependDvmVersionToPath(version string) {
  var pathSep string
  if runtime.GOOS == "windows" { pathSep = ";" } else { pathSep = ":" }
  versionDir := getVersionDir(version)
  path := fmt.Sprintf("%s%s%s", versionDir, pathSep, os.Getenv("PATH"))
  os.Setenv("PATH", path)
}

func writeShellScript() {
  if runtime.GOOS == "windows" && shell == "" {
    fmt.Fprintf(os.Stderr, "The --shell flag or SHELL environment variable must be set when running on Windows. Available values are sh, powershell and cmd.")
    os.Exit(127)
  }

  path := os.Getenv("PATH")

  var contents string
  var fileExtension string
  if shell == "powershell" {
    contents = fmt.Sprintf(`$env:PATH="%s"`, path)
    fileExtension = "ps1"
  } else if shell == "cmd" {
    contents = fmt.Sprintf("PATH=%s", path)
    fileExtension = "cmd"
  } else { // default to bash
    contents = fmt.Sprintf("export PATH=%s", path)
    fileExtension = "sh"
  }

  // Write to a shell script for the calling wrapper to execute
  scriptPath := filepath.Join(dvmDir, ".tmp", ("dvm-output." + fileExtension))

  if debug {
    fmt.Printf("Writing wrapper shell script to %s\n%s\n", scriptPath, contents)
  }

  ensureParentDirectoryExists(scriptPath)
  scriptFile, err := os.Create(scriptPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create %s\n%s\n", scriptPath, err)
    os.Exit(1)
  }

  _, err = io.WriteString(scriptFile, contents)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to write to %s\n%s\n", scriptPath, err)
    os.Exit(1)
  }

  scriptFile.Close()
}

func removePreviousDvmVersionFromPath() {
  versionDir := getVersionDir("")

  var pathRegex string
  if runtime.GOOS == "windows" {
    escapedVersionDir := strings.Replace(versionDir, `\`, `\\`, -1)
    pathRegex = escapedVersionDir + `\\\d+\.\d+\.\d+;`
  } else {
    pathRegex = versionDir + `/\d+\.\d+\.\d+:`
  }

  regex, _ := regexp.Compile(pathRegex)
  path := regex.ReplaceAllString(os.Getenv("PATH"), "")
  os.Setenv("PATH", path)
}

func ensureVersionIsInstalled(version string) {
    versionDir := getVersionDir(version)
    if _, err := os.Stat(versionDir); err == nil {
      return
    }

    if !silent {
      fmt.Printf("%s is not installed. Installing now...\n", version)
      install(version)
    }
}

func downloadFile(url string, destPath string) {
  ensureParentDirectoryExists(destPath)

  destFile, err := os.Create(destPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create to %s.\n%s\n", destPath, err)
    os.Exit(1)
  }
  defer destFile.Close()
  os.Chmod(destPath, 0755)

  if debug { fmt.Printf("Downloading %s\n", url) }

  response, err := http.Get(url)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to download %s.\n%s\n", url, err)
    os.Exit(1)
  }
  if response.StatusCode != 200 {
    fmt.Fprintf(os.Stderr, "Unable to download %s.\nStatus Code: %d\n", url, response.StatusCode)
    os.Exit(1)
  }
  defer response.Body.Close()

  _, err = io.Copy(destFile, response.Body)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to write to %s.\n%s\n", destPath, err)
    os.Exit(1)
  }
}

func versionExists(version string) bool {
  availableVersions := getAvailableVersions(version)

  for _,availableVersion := range availableVersions {
      if version == availableVersion {
          return true
      }
  }
  return false
}

func ensureParentDirectoryExists(filePath string) {
  dir := filepath.Dir(filePath)

  err := os.MkdirAll(dir, 0777)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create directory %s\n", dir)
    os.Exit(1)
  }
}

func listRemote(pattern string) {
    versions := getAvailableVersions(pattern)
    for _, version := range versions {
      fmt.Println(version)
    }
}

func getAvailableVersions(pattern string) []string {
  gh := github.NewClient(nil)
	tags, response, err := gh.Repositories.ListTags("docker", "docker", nil)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to retrieve list of Docker tags from GitHub.\n%s\n", err)
    os.Exit(1)
  }
  if response.StatusCode != 200 {
    fmt.Fprintf(os.Stderr, "Unable to retrieve list of Docker tags from GitHub.\nStatus Code:%d\n", response.StatusCode)
    os.Exit(1)
  }

  versionRegex := regexp.MustCompile(`^v([1-9]+\.\d+\.\d+)$`)
  patternRegex, err := regexp.Compile(pattern)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Invalid pattern.\n%s\n", err)
    os.Exit(3)
  }

  var results []string
  for _, tag := range tags {
    version := *tag.Name
    match := versionRegex.FindStringSubmatch(version)
    if len(match) > 1 && patternRegex.MatchString(version) {
      results = append(results, match[1])
    }
  }

  sort.Strings(results)
  return results
}

func getVersionDir(version string) string {
  return filepath.Join(dvmDir, "bin", "docker", version)
}

func getDockerOS() string {
  switch runtime.GOOS {
    case "windows":
      return "Windows"
    case "darwin":
      return "Darwin"
    case "linux":
      return "Linux"
    }

  fmt.Fprintf(os.Stderr, "Unsupported OS: %s\n", runtime.GOOS)
  os.Exit(1)
  return ""
}

func getDockerArch() string {
  switch runtime.GOARCH {
    case "amd64":
      return "x86_64"
    case "386":
      return "i386"
    }

  fmt.Fprintf(os.Stderr, "Unsupported ARCH: %s\n", runtime.GOARCH)
  os.Exit(1)
  return ""
}
/*
func help() {
  echo
  echo "Docker Version Manager"
  echo
  echo "Usage:"
  echo "  dvm help                              Show this message"
  echo "  dvm --version                         Print out the latest released version of dvm"
  echo "  dvm install <version>                 Download and install a <version>. Uses \$DOCKER_VERSION if available"
  echo "  dvm uninstall <version>               Uninstall a version"
  echo "  dvm use <version>                     Modify PATH to use <version>. Uses \$DOCKER_VERSION if available"
  echo "  dvm current                           Display currently activated version"
  echo "  dvm ls                                List installed versions"
  echo "  dvm ls <version>                      List versions matching a given description"
  echo "  dvm ls-remote                         List remote versions available for install"
  echo "  dvm deactivate                        Undo effects of \`dvm\` on current shell"
  echo "  dvm alias [<pattern>]                 Show all aliases beginning with <pattern>"
  echo "  dvm alias <name> <version>            Set an alias named <name> pointing to <version>"
  echo "  dvm unalias <name>                    Deletes the alias named <name>"
  echo "  dvm unload                            Unload \`dvm\` from shell"
  echo "  dvm which [<version>]                 Display path to installed docker version."
  echo
  echo "Example:"
  echo "  dvm install 1.8.1                     Install a specific version number"
  echo "  dvm use 1.6                           Use the latest available 1.6.x release"
  echo "  dvm alias default 1.8.1               Set default Docker version on a shell"
  echo
  echo "Note:"
  echo "  to remove, delete, or uninstall dvm - remove ~/.dvm"
  echo
}*/
<commit_msg>dvm use system<commit_after>package main

import "errors"
import "fmt"
import "io"
import "net/http"
import "os"
import "os/exec"
import "path/filepath"
import "regexp"
import "runtime"
import "sort"
import "strings"
import "github.com/google/go-github/github"
import "github.com/codegangsta/cli"
import "github.com/kardianos/osext"

var shell string
var dvmDir string
var debug bool
var silent bool

func main() {
  app := cli.NewApp()
  app.Name = "Docker Version Manager"
  app.Usage = "Manage multiple versions of the Docker client"
  app.EnableBashCompletion = true
  app.Flags = []cli.Flag{
    cli.StringFlag { Name: "dvm-dir", EnvVar: "DVM_DIR", Usage: "Specify an alternate DVM home directory, defaults to the current directory." },
    cli.StringFlag { Name: "shell", EnvVar: "SHELL", Usage: "Specify the shell format in which environment variables should be output, e.g. powershell, cmd or sh/bash. Defaults to sh/bash."},
    cli.BoolFlag { Name: "debug", Usage: "Print additional debug information." },
    cli.BoolFlag{ Name: "silent", EnvVar: "DVM_SILENT", Usage: "Suppress output. Errors will still be displayed."},
  }
  app.Commands = []cli.Command{
    {
      Name:    "install",
      Aliases: []string{"i"},
      Usage:   "dvm install <version>. Uses $DOCKER_VERSION if available",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        install(c.Args().First())
      },
    },
    {
      Name:    "use",
      Usage:   "dvm use <version>. dvm use system reverts to the system installation of Docker. Uses $DOCKER_VERSION if available",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        use(c.Args().First())
      },
    },
    {
      Name: "list-remote",
      Aliases: []string{"ls-remote"},
      Usage: "dvm list-remote [<pattern>]",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        listRemote(c.Args().First())
      },
    },
    {
      Name: "deactivate",
      Usage: "dvm deactivate. Undo effects of `dvm` on current shell",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        deactivate()
      },
    },
  }

  app.Run(os.Args)
}

func setGlobalVars(c *cli.Context) {
  debug = c.GlobalBool("debug")
  shell = c.GlobalString("shell")
  silent = c.GlobalBool("silent")
  dvmDir = c.GlobalString("dvm-dir")
  if dvmDir == "" {
    var err error
    dvmDir, err = osext.ExecutableFolder()
    if err != nil {
      fmt.Fprintln(os.Stderr, "Unable to determine DVM home directory")
      os.Exit(1)
    }
  }
}

func install(version string) {
  if version == "" {
    version = os.Getenv("DOCKER_VERSION")
  }

  if version == "" {
    fmt.Fprintln(os.Stderr, "The install command requires that a version is specified or the DOCKER_VERSION environment variable is set")
    os.Exit(127)
  }

  if !versionExists(version) {
    fmt.Fprintf(os.Stderr, "Version %s not found - try `dvm ls-remote` to browse available versions.\n", version)
    os.Exit(3)
  }

  versionDir := getVersionDir(version)

  // TODO: Support experimental

  if _, err := os.Stat(versionDir); err == nil {
    if !silent { fmt.Fprintf(os.Stderr, "%s is already installed\n", version) }
    use(version)
    return
  }

  if !silent { fmt.Printf("Installing %s\n", version) }

  url := fmt.Sprintf("https://get.docker.com/builds/%s/%s/%s", getDockerOS(), getDockerArch(), getDockerBinaryName(version))
  tmpPath := filepath.Join(getDvmDir(), ".tmp/docker", version, getBinaryName())
  downloadFile(url, tmpPath)
  binaryPath := filepath.Join(getDvmDir(), "bin/docker", version, getBinaryName())
  ensureParentDirectoryExists(binaryPath)
  err := os.Rename(tmpPath, binaryPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to copy %s to %s.\n%s\n", tmpPath, binaryPath, err)
  }

  if debug {
    fmt.Printf("Installed Docker %s to %s", version, binaryPath)
  }
}

func use(version string) {
  if version == "" {
    version = os.Getenv("DOCKER_VERSION")
  }

  if version == "" {
    fmt.Fprintln(os.Stderr, "The use command requires that a version is specified or the DOCKER_VERSION environment variable is set")
    os.Exit(127)
  }

  // dvm use system undoes changes to the PATH and uses installed version of DOcker
  if version == "system" {
    systemDockerVersion, err := useSystemDocker()
    if err != nil {
      fmt.Fprintln(os.Stderr, "System version of Docker not found.")
      os.Exit(3)
    }

    if !silent { fmt.Printf("Now using system version of Docker: %s\n", systemDockerVersion) }
    writeShellScript()
    return
  }

  if !versionExists(version) {
    fmt.Fprintf(os.Stderr, "Version %s not found - try `dvm ls-remote` to browse available versions.\n", version)
    os.Exit(3)
  }

  ensureVersionIsInstalled(version)
  removePreviousDvmVersionFromPath()
  prependDvmVersionToPath(version)
  writeShellScript()

  if !silent {
    fmt.Printf("Now using Docker %s\n", version)
  }
}

func getDvmDir() string {
  return dvmDir
}

func getDockerBinaryName(version string) string {
    if runtime.GOOS == "windows" {
      return fmt.Sprintf("docker-%s.exe", version)
    }
    return fmt.Sprintf("docker-%s", version)
}

func getBinaryName() string {
    if runtime.GOOS == "windows" {
      return "docker.exe"
    }
    return "docker"
}

func deactivate() {
  removePreviousDvmVersionFromPath()
  writeShellScript()
}

func prependDvmVersionToPath(version string) {
  var pathSep string
  if runtime.GOOS == "windows" { pathSep = ";" } else { pathSep = ":" }
  versionDir := getVersionDir(version)
  path := fmt.Sprintf("%s%s%s", versionDir, pathSep, os.Getenv("PATH"))
  os.Setenv("PATH", path)
}

func writeShellScript() {
  if runtime.GOOS == "windows" && shell == "" {
    fmt.Fprintf(os.Stderr, "The --shell flag or SHELL environment variable must be set when running on Windows. Available values are sh, powershell and cmd.")
    os.Exit(127)
  }

  path := os.Getenv("PATH")

  var contents string
  var fileExtension string
  if shell == "powershell" {
    contents = fmt.Sprintf(`$env:PATH="%s"`, path)
    fileExtension = "ps1"
  } else if shell == "cmd" {
    contents = fmt.Sprintf("PATH=%s", path)
    fileExtension = "cmd"
  } else { // default to bash
    contents = fmt.Sprintf("export PATH=%s", path)
    fileExtension = "sh"
  }

  // Write to a shell script for the calling wrapper to execute
  scriptPath := filepath.Join(dvmDir, ".tmp", ("dvm-output." + fileExtension))

  if debug {
    fmt.Printf("Writing wrapper shell script to %s\n%s\n", scriptPath, contents)
  }

  ensureParentDirectoryExists(scriptPath)
  scriptFile, err := os.Create(scriptPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create %s\n%s\n", scriptPath, err)
    os.Exit(1)
  }

  _, err = io.WriteString(scriptFile, contents)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to write to %s\n%s\n", scriptPath, err)
    os.Exit(1)
  }

  scriptFile.Close()
}

func removePreviousDvmVersionFromPath() {
  versionDir := getVersionDir("")

  var pathRegex string
  if runtime.GOOS == "windows" {
    escapedVersionDir := strings.Replace(versionDir, `\`, `\\`, -1)
    pathRegex = escapedVersionDir + `\\\d+\.\d+\.\d+;`
  } else {
    pathRegex = versionDir + `/\d+\.\d+\.\d+:`
  }

  regex, _ := regexp.Compile(pathRegex)
  path := regex.ReplaceAllString(os.Getenv("PATH"), "")
  os.Setenv("PATH", path)
}

func ensureVersionIsInstalled(version string) {
    versionDir := getVersionDir(version)
    if _, err := os.Stat(versionDir); err == nil {
      return
    }

    if !silent {
      fmt.Printf("%s is not installed. Installing now...\n", version)
      install(version)
    }
}

func downloadFile(url string, destPath string) {
  ensureParentDirectoryExists(destPath)

  destFile, err := os.Create(destPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create to %s.\n%s\n", destPath, err)
    os.Exit(1)
  }
  defer destFile.Close()
  os.Chmod(destPath, 0755)

  if debug { fmt.Printf("Downloading %s\n", url) }

  response, err := http.Get(url)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to download %s.\n%s\n", url, err)
    os.Exit(1)
  }
  if response.StatusCode != 200 {
    fmt.Fprintf(os.Stderr, "Unable to download %s.\nStatus Code: %d\n", url, response.StatusCode)
    os.Exit(1)
  }
  defer response.Body.Close()

  _, err = io.Copy(destFile, response.Body)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to write to %s.\n%s\n", destPath, err)
    os.Exit(1)
  }
}

func versionExists(version string) bool {
  availableVersions := getAvailableVersions(version)

  for _,availableVersion := range availableVersions {
      if version == availableVersion {
          return true
      }
  }
  return false
}

func ensureParentDirectoryExists(filePath string) {
  dir := filepath.Dir(filePath)

  err := os.MkdirAll(dir, 0777)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create directory %s\n", dir)
    os.Exit(1)
  }
}

func useSystemDocker() (string, error) {
  removePreviousDvmVersionFromPath()
  systemDockerPath, _ := exec.LookPath("docker")
  rawVersion, _ := exec.Command(systemDockerPath, "-v").Output()

  if debug { fmt.Printf("docker -v output: %s\n", rawVersion)}

  versionRegex := regexp.MustCompile(`^Docker version (.*),`)
  match := versionRegex.FindSubmatch(rawVersion)
  if len(match) < 2 {
    return "", errors.New("No system installation of Docker detected.")
  }

  return string(match[1][:]), nil
}

func listRemote(pattern string) {
    versions := getAvailableVersions(pattern)
    for _, version := range versions {
      fmt.Println(version)
    }
}

func getAvailableVersions(pattern string) []string {
  gh := github.NewClient(nil)
	tags, response, err := gh.Repositories.ListTags("docker", "docker", nil)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to retrieve list of Docker tags from GitHub.\n%s\n", err)
    os.Exit(1)
  }
  if response.StatusCode != 200 {
    fmt.Fprintf(os.Stderr, "Unable to retrieve list of Docker tags from GitHub.\nStatus Code:%d\n", response.StatusCode)
    os.Exit(1)
  }

  versionRegex := regexp.MustCompile(`^v([1-9]+\.\d+\.\d+)$`)
  patternRegex, err := regexp.Compile(pattern)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Invalid pattern.\n%s\n", err)
    os.Exit(3)
  }

  var results []string
  for _, tag := range tags {
    version := *tag.Name
    match := versionRegex.FindStringSubmatch(version)
    if len(match) > 1 && patternRegex.MatchString(version) {
      results = append(results, match[1])
    }
  }

  sort.Strings(results)
  return results
}

func getVersionDir(version string) string {
  return filepath.Join(dvmDir, "bin", "docker", version)
}

func getDockerOS() string {
  switch runtime.GOOS {
    case "windows":
      return "Windows"
    case "darwin":
      return "Darwin"
    case "linux":
      return "Linux"
    }

  fmt.Fprintf(os.Stderr, "Unsupported OS: %s\n", runtime.GOOS)
  os.Exit(1)
  return ""
}

func getDockerArch() string {
  switch runtime.GOARCH {
    case "amd64":
      return "x86_64"
    case "386":
      return "i386"
    }

  fmt.Fprintf(os.Stderr, "Unsupported ARCH: %s\n", runtime.GOARCH)
  os.Exit(1)
  return ""
}
/*
func help() {
  echo
  echo "Docker Version Manager"
  echo
  echo "Usage:"
  echo "  dvm help                              Show this message"
  echo "  dvm --version                         Print out the latest released version of dvm"
  echo "  dvm install <version>                 Download and install a <version>. Uses \$DOCKER_VERSION if available"
  echo "  dvm uninstall <version>               Uninstall a version"
  echo "  dvm use <version>                     Modify PATH to use <version>. Uses \$DOCKER_VERSION if available"
  echo "  dvm current                           Display currently activated version"
  echo "  dvm ls                                List installed versions"
  echo "  dvm ls <version>                      List versions matching a given description"
  echo "  dvm ls-remote                         List remote versions available for install"
  echo "  dvm deactivate                        Undo effects of \`dvm\` on current shell"
  echo "  dvm alias [<pattern>]                 Show all aliases beginning with <pattern>"
  echo "  dvm alias <name> <version>            Set an alias named <name> pointing to <version>"
  echo "  dvm unalias <name>                    Deletes the alias named <name>"
  echo "  dvm unload                            Unload \`dvm\` from shell"
  echo "  dvm which [<version>]                 Display path to installed docker version."
  echo
  echo "Example:"
  echo "  dvm install 1.8.1                     Install a specific version number"
  echo "  dvm use 1.6                           Use the latest available 1.6.x release"
  echo "  dvm alias default 1.8.1               Set default Docker version on a shell"
  echo
  echo "Note:"
  echo "  to remove, delete, or uninstall dvm - remove ~/.dvm"
  echo
}*/
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/currantlabs/ble"
	"github.com/currantlabs/ble/examples/lib/gatt"
)

var (
	name = flag.String("name", "Gopher", "name of remote peripheral")
	addr = flag.String("addr", "", "address of remote peripheral (MAC on Linux, UUID on OS X)")
	sub  = flag.Duration("sub", 0, "subscribe to notification and indication for a specified period")
)

// matcher returns true if the advertisement matches our search criteria.
type matcher func(a ble.Advertisement) bool

// explorer connects to a remote peripheral and explores its GATT server.
type explorerer struct {
	match matcher
	ch    chan ble.Advertisement
}

func (e *explorerer) Handle(a ble.Advertisement) {
	if e.match(a) {
		gatt.StopScanning()
		e.ch <- a
	}
}

func explorer(cln ble.Client) error {
	fmt.Printf("Exploring Peripheral [ %s ] ...\n", cln.Address())

	ss, err := cln.DiscoverServices(nil)
	if err != nil {
		return fmt.Errorf("can't discover services: %s\n", err)
	}
	for _, s := range ss {
		fmt.Printf("Service: %s %s\n", s.UUID.String(), ble.Name(s.UUID))

		cs, err := cln.DiscoverCharacteristics(nil, s)
		if err != nil {
			return fmt.Errorf("can't discover characteristics: %s\n", err)
		}
		for _, c := range cs {
			fmt.Printf("  Characteristic: %s, Property: 0x%02X, %s\n", c.UUID, c.Property, ble.Name(c.UUID))
			if (c.Property & ble.CharRead) != 0 {
				b, err := cln.ReadCharacteristic(c)
				if err != nil {
					fmt.Printf("Failed to read characteristic: %s\n", err)
					continue
				}
				fmt.Printf("    Value         %x | %q\n", b, b)
			}

			ds, err := cln.DiscoverDescriptors(nil, c)
			if err != nil {
				return fmt.Errorf("can't discover descriptors: %s\n", err)
			}
			for _, d := range ds {
				fmt.Printf("    Descriptor: %s, %s\n", d.UUID, ble.Name(d.UUID))
				b, err := cln.ReadDescriptor(d)
				if err != nil {
					fmt.Printf("Failed to read descriptor: %s\n", err)
					continue
				}
				fmt.Printf("    Value         %x | %q\n", b, b)
			}
			if *sub != 0 {
				if (c.Property & ble.CharNotify) != 0 {
					fmt.Printf("\n-- Subscribe to notification for %s --\n", *sub)
					h := func(req []byte) { fmt.Printf("Notified: %q [ % X ]\n", string(req), req) }
					if err := cln.Subscribe(c, false, h); err != nil {
						log.Fatalf("subscribe failed: %s", err)
					}
					time.Sleep(*sub)
					if err := cln.Unsubscribe(c, false); err != nil {
						log.Fatalf("unsubscribe failed: %s", err)
					}
					fmt.Printf("-- Unsubscribe to notification --\n")
				}
				if (c.Property & ble.CharIndicate) != 0 {
					fmt.Printf("\n-- Subscribe to indication of %s --\n", *sub)
					h := func(req []byte) { fmt.Printf("Indicated: %q [ % X ]\n", string(req), req) }
					if err := cln.Subscribe(c, true, h); err != nil {
						log.Fatalf("subscribe failed: %s", err)
					}
					time.Sleep(*sub)
					if err := cln.Unsubscribe(c, true); err != nil {
						log.Fatalf("unsubscribe failed: %s", err)
					}
					fmt.Printf("-- Unsubscribe to indication --\n")
				}
			}

		}
		fmt.Printf("\n")
	}
	return nil
}

func main() {
	flag.Parse()

	// Default to search device with name of Gopher (or specified by user).
	match := func(a ble.Advertisement) bool {
		return strings.ToUpper(a.LocalName()) == strings.ToUpper(*name)
	}

	// If addr is specified, search for addr instead.
	if len(*addr) != 0 {
		match = func(a ble.Advertisement) bool {
			return strings.ToUpper(a.Address().String()) == strings.ToUpper(*addr)
		}
	}

	exp := &explorerer{
		ch:    make(chan ble.Advertisement),
		match: match,
	}

	if err := gatt.SetAdvHandler(exp); err != nil {
		log.Fatalf("can't set adv handler: %s", err)
	}

	if err := gatt.Scan(false); err != nil {
		log.Fatalf("can't scan: %s", err)
	}

	// Wait for the exploration is done.
	a := <-exp.ch

	// Dial connects to the remote device.
	cln, err := gatt.Dial(a.Address())
	if err != nil {
		log.Fatalf("can't dial: %s", err)
	}

	// Start the exploration.
	explorer(cln)

	// Disconnect the connection. (On OS X, this might take a while.)
	fmt.Printf("Disconnecting [ %s ]... (this might take up to few seconds on OS X)\n", cln.Address())
	cln.CancelConnection()
}
<commit_msg>examples: add a helper function to decode characteristic properties<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/currantlabs/ble"
	"github.com/currantlabs/ble/examples/lib/gatt"
)

var (
	name = flag.String("name", "Gopher", "name of remote peripheral")
	addr = flag.String("addr", "", "address of remote peripheral (MAC on Linux, UUID on OS X)")
	sub  = flag.Duration("sub", 0, "subscribe to notification and indication for a specified period")
)

// matcher returns true if the advertisement matches our search criteria.
type matcher func(a ble.Advertisement) bool

// explorer connects to a remote peripheral and explores its GATT server.
type explorerer struct {
	match matcher
	ch    chan ble.Advertisement
}

func (e *explorerer) Handle(a ble.Advertisement) {
	if e.match(a) {
		gatt.StopScanning()
		e.ch <- a
	}
}

func explorer(cln ble.Client) error {
	fmt.Printf("Exploring Peripheral [ %s ] ...\n", cln.Address())

	ss, err := cln.DiscoverServices(nil)
	if err != nil {
		return fmt.Errorf("can't discover services: %s\n", err)
	}
	for _, s := range ss {
		fmt.Printf("Service: %s %s\n", s.UUID.String(), ble.Name(s.UUID))

		cs, err := cln.DiscoverCharacteristics(nil, s)
		if err != nil {
			return fmt.Errorf("can't discover characteristics: %s\n", err)
		}
		for _, c := range cs {
			fmt.Printf("  Characteristic: %s, Property: 0x%02X (%s), %s\n", c.UUID, c.Property, propString(c.Property), ble.Name(c.UUID))
			if (c.Property & ble.CharRead) != 0 {
				b, err := cln.ReadCharacteristic(c)
				if err != nil {
					fmt.Printf("Failed to read characteristic: %s\n", err)
					continue
				}
				fmt.Printf("    Value         %x | %q\n", b, b)
			}

			ds, err := cln.DiscoverDescriptors(nil, c)
			if err != nil {
				return fmt.Errorf("can't discover descriptors: %s\n", err)
			}
			for _, d := range ds {
				fmt.Printf("    Descriptor: %s, %s\n", d.UUID, ble.Name(d.UUID))
				b, err := cln.ReadDescriptor(d)
				if err != nil {
					fmt.Printf("Failed to read descriptor: %s\n", err)
					continue
				}
				fmt.Printf("    Value         %x | %q\n", b, b)
			}
			if *sub != 0 {
				if (c.Property & ble.CharNotify) != 0 {
					fmt.Printf("\n-- Subscribe to notification for %s --\n", *sub)
					h := func(req []byte) { fmt.Printf("Notified: %q [ % X ]\n", string(req), req) }
					if err := cln.Subscribe(c, false, h); err != nil {
						log.Fatalf("subscribe failed: %s", err)
					}
					time.Sleep(*sub)
					if err := cln.Unsubscribe(c, false); err != nil {
						log.Fatalf("unsubscribe failed: %s", err)
					}
					fmt.Printf("-- Unsubscribe to notification --\n")
				}
				if (c.Property & ble.CharIndicate) != 0 {
					fmt.Printf("\n-- Subscribe to indication of %s --\n", *sub)
					h := func(req []byte) { fmt.Printf("Indicated: %q [ % X ]\n", string(req), req) }
					if err := cln.Subscribe(c, true, h); err != nil {
						log.Fatalf("subscribe failed: %s", err)
					}
					time.Sleep(*sub)
					if err := cln.Unsubscribe(c, true); err != nil {
						log.Fatalf("unsubscribe failed: %s", err)
					}
					fmt.Printf("-- Unsubscribe to indication --\n")
				}
			}

		}
		fmt.Printf("\n")
	}
	return nil
}

func propString(p ble.Property) string {
	var s string
	for k, v := range map[ble.Property]string{
		ble.CharBroadcast:   "B",
		ble.CharRead:        "R",
		ble.CharWriteNR:     "w",
		ble.CharWrite:       "W",
		ble.CharNotify:      "N",
		ble.CharIndicate:    "I",
		ble.CharSignedWrite: "S",
		ble.CharExtended:    "E",
	} {
		if p&k != 0 {
			s += v
		}
	}
	return s
}

func main() {
	flag.Parse()

	// Default to search device with name of Gopher (or specified by user).
	match := func(a ble.Advertisement) bool {
		return strings.ToUpper(a.LocalName()) == strings.ToUpper(*name)
	}

	// If addr is specified, search for addr instead.
	if len(*addr) != 0 {
		match = func(a ble.Advertisement) bool {
			return strings.ToUpper(a.Address().String()) == strings.ToUpper(*addr)
		}
	}

	exp := &explorerer{
		ch:    make(chan ble.Advertisement),
		match: match,
	}

	if err := gatt.SetAdvHandler(exp); err != nil {
		log.Fatalf("can't set adv handler: %s", err)
	}

	if err := gatt.Scan(false); err != nil {
		log.Fatalf("can't scan: %s", err)
	}

	// Wait for the exploration is done.
	a := <-exp.ch

	// Dial connects to the remote device.
	cln, err := gatt.Dial(a.Address())
	if err != nil {
		log.Fatalf("can't dial: %s", err)
	}

	// Start the exploration.
	explorer(cln)

	// Disconnect the connection. (On OS X, this might take a while.)
	fmt.Printf("Disconnecting [ %s ]... (this might take up to few seconds on OS X)\n", cln.Address())
	cln.CancelConnection()
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"net"
	"strings"
)

func strcat(s1 string, s2 string) string {
	var buffer bytes.Buffer
	buffer.WriteString(s1)
	buffer.WriteString(s2)
	return buffer.String()
}

func GetUserByNick(nick string) *User {
	nick = strings.ToLower(nick)
	for _, k := range userlist {
		if strings.ToLower(k.nick) == nick {
			return k
		}
	}
	return nil
}

func GetIpFromConn(conn net.Conn) string {
	ip := conn.RemoteAddr().String()
	if !strings.HasPrefix(ip, "[") {
		//IPV4
		return strings.Split(ip, ":")[0]
	} else {
		//IPV6
		ip = strings.Split(ip, "]")[0]
		ip = StripLeading(ip, "[")
		return ip
	}
}

/*
func GetChannelByName(name string) *Channel {
	for _, k := range chanlist {
		if strings.ToLower(name) == strings.ToLower(k.name) {
			return k
		}
	}
	return nil
}
*/

func GetChannelByName(name string) *Channel {
	return chanlist[strings.ToLower(name)]
}

//SendToMany() sends to a list of users, checking for duplicates. This is very inefficient, so should only be used for things like QUIT and NICK
func SendToMany(msg string, list []*User) {
	list2 := []*User{}
	for _, j := range list {
		match := false
		for _, k := range list2 {
			if j == k {
				match = true
				break
			}
		}
		if !match {
			list2 = append(list2, j)
		}
	}
	for _, j := range list2 {
		j.SendLine(msg)
	}
}

//SendToMany2f sends to all users in list ([]*User), does not check for dupes, accepts in printf format.
func SendToMany2f(list []*User, msg string, args ...interface{}) {
	for _, k := range list {
		k.SendLinef(msg, args...)
	}
}

func ValidChanName(name string) bool {
	if ChanHasBadChars(name) {
		return false
	}
	for _, k := range valid_chan_prefix {
		if strings.HasPrefix(name, k) {
			return true
		}
	}
	return false
}

//IMPORTANT: args must ABSOLUTELY be a valid privmsg command, or this will not work
//validity does not depend on leading ":", I don't care that much
func FormatMessageArgs(args []string) string {
	msg := strings.Join(args[2:], " ")
	msg = StripLeading(msg, ":")
	return msg
}

//removes the first instance of toremove, only if it has it
//r can be more than one char, but I wouldn't recommend it
func StripLeading(msg string, r string) string {
	if strings.HasPrefix(msg, r) {
		msg = strings.Replace(msg, r, "", 1)
	}
	return msg
}

func NickHasBadChars(nick string) bool {
	for _, k := range global_bad_chars {
		if strings.Contains(nick, k) {
			return true
		}
	}
	for _, k := range valid_chan_prefix {
		if strings.Contains(nick, k) {
			return true
		}
	}
	return false
}

func ChanHasBadChars(nick string) bool {
	for _, k := range global_bad_chars {
		if strings.Contains(nick, k) {
			return true
		}
	}
	return false
}

func ChanUserNone(name string) int {
	if GetChannelByName(name) != nil {
		return 1
	} else if GetUserByNick(name) != nil {
		return 2
	} else {
		return 0
	}
}

func WildcardMatch(text string, pattern string) bool {
	cards := strings.Split(pattern, "*")
	for _, card := range cards {
		index := strings.Index(text, card)
		if index == -1 {
			return false
		}
		text = text[index+len(card):]
	}
	return true
}
<commit_msg>remove old code<commit_after>package main

import (
	"bytes"
	"net"
	"strings"
)

func strcat(s1 string, s2 string) string {
	var buffer bytes.Buffer
	buffer.WriteString(s1)
	buffer.WriteString(s2)
	return buffer.String()
}

func GetUserByNick(nick string) *User {
	nick = strings.ToLower(nick)
	for _, k := range userlist {
		if strings.ToLower(k.nick) == nick {
			return k
		}
	}
	return nil
}

func GetIpFromConn(conn net.Conn) string {
	ip := conn.RemoteAddr().String()
	if !strings.HasPrefix(ip, "[") {
		//IPV4
		return strings.Split(ip, ":")[0]
	} else {
		//IPV6
		ip = strings.Split(ip, "]")[0]
		ip = StripLeading(ip, "[")
		return ip
	}
}

func GetChannelByName(name string) *Channel {
	return chanlist[strings.ToLower(name)]
}

//SendToMany() sends to a list of users, checking for duplicates. This is very inefficient, so should only be used for things like QUIT and NICK
func SendToMany(msg string, list []*User) {
	list2 := []*User{}
	for _, j := range list {
		match := false
		for _, k := range list2 {
			if j == k {
				match = true
				break
			}
		}
		if !match {
			list2 = append(list2, j)
		}
	}
	for _, j := range list2 {
		j.SendLine(msg)
	}
}

//SendToMany2f sends to all users in list ([]*User), does not check for dupes, accepts in printf format.
func SendToMany2f(list []*User, msg string, args ...interface{}) {
	for _, k := range list {
		k.SendLinef(msg, args...)
	}
}

func ValidChanName(name string) bool {
	if ChanHasBadChars(name) {
		return false
	}
	for _, k := range valid_chan_prefix {
		if strings.HasPrefix(name, k) {
			return true
		}
	}
	return false
}

//IMPORTANT: args must ABSOLUTELY be a valid privmsg command, or this will not work
//validity does not depend on leading ":", I don't care that much
func FormatMessageArgs(args []string) string {
	msg := strings.Join(args[2:], " ")
	msg = StripLeading(msg, ":")
	return msg
}

//removes the first instance of toremove, only if it has it
//r can be more than one char, but I wouldn't recommend it
func StripLeading(msg string, r string) string {
	if strings.HasPrefix(msg, r) {
		msg = strings.Replace(msg, r, "", 1)
	}
	return msg
}

func NickHasBadChars(nick string) bool {
	for _, k := range global_bad_chars {
		if strings.Contains(nick, k) {
			return true
		}
	}
	for _, k := range valid_chan_prefix {
		if strings.Contains(nick, k) {
			return true
		}
	}
	return false
}

func ChanHasBadChars(nick string) bool {
	for _, k := range global_bad_chars {
		if strings.Contains(nick, k) {
			return true
		}
	}
	return false
}

func ChanUserNone(name string) int {
	if GetChannelByName(name) != nil {
		return 1
	} else if GetUserByNick(name) != nil {
		return 2
	} else {
		return 0
	}
}

func WildcardMatch(text string, pattern string) bool {
	cards := strings.Split(pattern, "*")
	for _, card := range cards {
		index := strings.Index(text, card)
		if index == -1 {
			return false
		}
		text = text[index+len(card):]
	}
	return true
}
<|endoftext|>


<commit_before>// Command pubsub is an example of a fanout exchange with dynamic reliable
// membership, reading from stdin, writing to stdout.
//
// This example shows how to implement reconnect logic independent from a
// publish/subscribe loop with bridges to application types.

package main

import (
	"bufio"
	"crypto/sha1"
	"flag"
	"fmt"
	"io"
	"log"
	"os"

	"code.google.com/p/go.net/context"
	"github.com/streadway/amqp"
)

var url = flag.String("url", "amqp:///", "AMQP url for both the publisher and subscriber")

// exchange binds the publishers to the subscribers
const exchange = "pubsub"

// message is the application type for a message.  This can contain identity,
// or a reference to the recevier chan for further demuxing.
type message []byte

// session composes an amqp.Connection with an amqp.Channel
type session struct {
	*amqp.Connection
	*amqp.Channel
}

// Close tears the connection down, taking the channel with it.
func (s session) Close() error {
	if s.Connection == nil {
		return nil
	}
	return s.Connection.Close()
}

// redial continually connects to the URL, exiting the program when no longer possible
func redial(ctx context.Context, url string) chan chan session {
	sessions := make(chan chan session)

	go func() {
		sess := make(chan session)
		defer close(sessions)

		for {
			select {
			case sessions <- sess:
			case <-ctx.Done():
				log.Println("shutting down session factory")
				return
			}

			conn, err := amqp.Dial(url)
			if err != nil {
				log.Fatalf("cannot (re)dial: %v: %q", err, url)
			}

			ch, err := conn.Channel()
			if err != nil {
				log.Fatalf("cannot create channel: %v", err)
			}

			if err := ch.ExchangeDeclare(exchange, "fanout", false, true, false, false, nil); err != nil {
				log.Fatalf("cannot declare fanout exchange: %v", err)
			}

			select {
			case sess <- session{conn, ch}:
			case <-ctx.Done():
				log.Println("shutting down new session")
				return
			}
		}
	}()

	return sessions
}

// publish publishes messages to a reconnecting session to a fanout exchange.
// It receives from the application specific source of messages.
func publish(sessions chan chan session, messages <-chan message) {
	var (
		running   bool
		reading   = messages
		pending   = make(chan message, 1)
		ack, nack = make(chan uint64), make(chan uint64)
	)

	for session := range sessions {
		pub := <-session

		// publisher confirms for this channel/connection
		if err := pub.Confirm(false); err != nil {
			log.Printf("publisher confirms not supported")
			close(ack) // confirms not supported, simulate by always acking
		} else {
			pub.NotifyConfirm(ack, nack)
		}

		log.Printf("publishing...")

		for {
			var body message
			select {
			case <-ack:
				reading = messages

			case id := <-nack:
				log.Printf("nack message %d, body: %q", id, string(body))
				reading = messages

			case body = <-pending:
				routingKey := "ignored for fanout exchanges, application dependent for other exchanges"
				err := pub.Publish(exchange, routingKey, false, false, amqp.Publishing{
					Body: body,
				})
				// Retry failed delivery on the next session
				if err != nil {
					pending <- body
					pub.Close()
					break
				}

			case body, running = <-reading:
				// all messages consumed
				if !running {
					return
				}
				// work on pending delivery until ack'd
				pending <- body
				reading = nil
			}
		}
	}
}

// identity returns the same host/process unique string for the lifetime of
// this process so that subscriber reconnections reuse the same queue name.
func identity() string {
	hostname, err := os.Hostname()
	h := sha1.New()
	fmt.Fprint(h, hostname)
	fmt.Fprint(h, err)
	fmt.Fprint(h, os.Getpid())
	return fmt.Sprintf("%x", h.Sum(nil))
}

// subscribe consumes deliveries from an exclusive queue from a fanout exchange and sends to the application specific messages chan.
func subscribe(sessions chan chan session, messages chan<- message) {
	queue := identity()

	for session := range sessions {
		sub := <-session

		if _, err := sub.QueueDeclare(queue, false, true, true, false, nil); err != nil {
			log.Printf("cannot consume from exclusive queue: %q, %v", queue, err)
			return
		}

		routingKey := "application specific routing key for fancy toplogies"
		if err := sub.QueueBind(queue, routingKey, exchange, false, nil); err != nil {
			log.Printf("cannot consume without a binding to exchange: %q, %v", exchange, err)
			return
		}

		deliveries, err := sub.Consume(queue, "", false, true, false, false, nil)
		if err != nil {
			log.Printf("cannot consume from: %q, %v", queue, err)
			return
		}

		log.Printf("subscribed...")

		for msg := range deliveries {
			messages <- message(msg.Body)
			sub.Ack(msg.DeliveryTag, false)
		}
	}
}

// read is this application's translation to the message format, scanning from
// stdin.
func read(r io.Reader) <-chan message {
	lines := make(chan message)
	go func() {
		defer close(lines)
		scan := bufio.NewScanner(r)
		for scan.Scan() {
			lines <- message(scan.Bytes())
		}
	}()
	return lines
}

// write is this application's subscriber of application messages, printing to
// stdout.
func write(w io.Writer) chan<- message {
	lines := make(chan message)
	go func() {
		for line := range lines {
			fmt.Fprintln(w, string(line))
		}
	}()
	return lines
}

func main() {
	flag.Parse()

	ctx, done := context.WithCancel(context.Background())

	go func() {
		publish(redial(ctx, *url), read(os.Stdin))
		done()
	}()

	go func() {
		subscribe(redial(ctx, *url), write(os.Stdout))
		done()
	}()

	<-ctx.Done()
}
<commit_msg>fix import path "code.google.com/p/go.net/context" => "golang.org/x/net/context" in examples<commit_after>// Command pubsub is an example of a fanout exchange with dynamic reliable
// membership, reading from stdin, writing to stdout.
//
// This example shows how to implement reconnect logic independent from a
// publish/subscribe loop with bridges to application types.

package main

import (
	"bufio"
	"crypto/sha1"
	"flag"
	"fmt"
	"io"
	"log"
	"os"

	"github.com/streadway/amqp"
	"golang.org/x/net/context"
)

var url = flag.String("url", "amqp:///", "AMQP url for both the publisher and subscriber")

// exchange binds the publishers to the subscribers
const exchange = "pubsub"

// message is the application type for a message.  This can contain identity,
// or a reference to the recevier chan for further demuxing.
type message []byte

// session composes an amqp.Connection with an amqp.Channel
type session struct {
	*amqp.Connection
	*amqp.Channel
}

// Close tears the connection down, taking the channel with it.
func (s session) Close() error {
	if s.Connection == nil {
		return nil
	}
	return s.Connection.Close()
}

// redial continually connects to the URL, exiting the program when no longer possible
func redial(ctx context.Context, url string) chan chan session {
	sessions := make(chan chan session)

	go func() {
		sess := make(chan session)
		defer close(sessions)

		for {
			select {
			case sessions <- sess:
			case <-ctx.Done():
				log.Println("shutting down session factory")
				return
			}

			conn, err := amqp.Dial(url)
			if err != nil {
				log.Fatalf("cannot (re)dial: %v: %q", err, url)
			}

			ch, err := conn.Channel()
			if err != nil {
				log.Fatalf("cannot create channel: %v", err)
			}

			if err := ch.ExchangeDeclare(exchange, "fanout", false, true, false, false, nil); err != nil {
				log.Fatalf("cannot declare fanout exchange: %v", err)
			}

			select {
			case sess <- session{conn, ch}:
			case <-ctx.Done():
				log.Println("shutting down new session")
				return
			}
		}
	}()

	return sessions
}

// publish publishes messages to a reconnecting session to a fanout exchange.
// It receives from the application specific source of messages.
func publish(sessions chan chan session, messages <-chan message) {
	var (
		running   bool
		reading   = messages
		pending   = make(chan message, 1)
		ack, nack = make(chan uint64), make(chan uint64)
	)

	for session := range sessions {
		pub := <-session

		// publisher confirms for this channel/connection
		if err := pub.Confirm(false); err != nil {
			log.Printf("publisher confirms not supported")
			close(ack) // confirms not supported, simulate by always acking
		} else {
			pub.NotifyConfirm(ack, nack)
		}

		log.Printf("publishing...")

		for {
			var body message
			select {
			case <-ack:
				reading = messages

			case id := <-nack:
				log.Printf("nack message %d, body: %q", id, string(body))
				reading = messages

			case body = <-pending:
				routingKey := "ignored for fanout exchanges, application dependent for other exchanges"
				err := pub.Publish(exchange, routingKey, false, false, amqp.Publishing{
					Body: body,
				})
				// Retry failed delivery on the next session
				if err != nil {
					pending <- body
					pub.Close()
					break
				}

			case body, running = <-reading:
				// all messages consumed
				if !running {
					return
				}
				// work on pending delivery until ack'd
				pending <- body
				reading = nil
			}
		}
	}
}

// identity returns the same host/process unique string for the lifetime of
// this process so that subscriber reconnections reuse the same queue name.
func identity() string {
	hostname, err := os.Hostname()
	h := sha1.New()
	fmt.Fprint(h, hostname)
	fmt.Fprint(h, err)
	fmt.Fprint(h, os.Getpid())
	return fmt.Sprintf("%x", h.Sum(nil))
}

// subscribe consumes deliveries from an exclusive queue from a fanout exchange and sends to the application specific messages chan.
func subscribe(sessions chan chan session, messages chan<- message) {
	queue := identity()

	for session := range sessions {
		sub := <-session

		if _, err := sub.QueueDeclare(queue, false, true, true, false, nil); err != nil {
			log.Printf("cannot consume from exclusive queue: %q, %v", queue, err)
			return
		}

		routingKey := "application specific routing key for fancy toplogies"
		if err := sub.QueueBind(queue, routingKey, exchange, false, nil); err != nil {
			log.Printf("cannot consume without a binding to exchange: %q, %v", exchange, err)
			return
		}

		deliveries, err := sub.Consume(queue, "", false, true, false, false, nil)
		if err != nil {
			log.Printf("cannot consume from: %q, %v", queue, err)
			return
		}

		log.Printf("subscribed...")

		for msg := range deliveries {
			messages <- message(msg.Body)
			sub.Ack(msg.DeliveryTag, false)
		}
	}
}

// read is this application's translation to the message format, scanning from
// stdin.
func read(r io.Reader) <-chan message {
	lines := make(chan message)
	go func() {
		defer close(lines)
		scan := bufio.NewScanner(r)
		for scan.Scan() {
			lines <- message(scan.Bytes())
		}
	}()
	return lines
}

// write is this application's subscriber of application messages, printing to
// stdout.
func write(w io.Writer) chan<- message {
	lines := make(chan message)
	go func() {
		for line := range lines {
			fmt.Fprintln(w, string(line))
		}
	}()
	return lines
}

func main() {
	flag.Parse()

	ctx, done := context.WithCancel(context.Background())

	go func() {
		publish(redial(ctx, *url), read(os.Stdin))
		done()
	}()

	go func() {
		subscribe(redial(ctx, *url), write(os.Stdout))
		done()
	}()

	<-ctx.Done()
}
<|endoftext|>