<commit_before>// Package poslog works with POSLog XML files.
// Currently writing types for NCR ACS POSLog
// But can be extended for other POS vendores and all
// NCR specific tags are (will be) set as options
package poslog

// POSLog the main type of a POSLog XMl file. The type contains
// the marshaling information to marshal and unmarshal to json and xml
// currently it is not complete and does not handle all fields in
// source XML
type POSLog struct {
	POSLog      string `xml:"POSLog" json:"POSLog"`
	XMLNSPOSLog string `xml:"xmlns poslog,attr"`
	XMLNS       string `xml:"xmlns,attr"`
	XMLNSACS    string `xml:"xmlns acs,attr"`
	XMLNSRaw    string `xml:"xmlns raw,attr"`
	XMLNSXSI    string `xml:"xmlns xsi,attr"`
	XMLNSMSXSL  string `xml:"xmlns msxsl,attr"`
	XMLNSAS     string `xml:"xmlns as,attr"`
	XMLNSACSSM  string `xml:"xmlns acssm,attr"`
	// Transaction does not have to exist in the transaction, thus a pointer
	// and we we have a POSLog type with nothing in it (unless there is
	// another top level array type)
	Transaction []*Transaction `xml:"Transaction" json:"Transaction"`
}

// Transaction is the main body of a POSLog XML. Each Transaction is not
// a literal transaction at the register but each complete actions
// at the register so a sign in to register would be a transactions
type Transaction struct {
	RetailStoreID     *int    `xml:"RetailStoreID" json:"RetailStoreID"`
	WorkstationID     *int    `xml:"WorkstationID" json:"WorkstationID"`
	SequenceNumber    *int    `xml:"SequenceNumber" json:"SequenceNumber"`
	BusinessDayDate   *string `xml:"BusinessDayDate" json:"BusinessDayDate"`
	EndDateTime       *string `xml:"EndDateTime" json:"EndDateTime"`
	OperatorID        *OperatorID
	CurrencyCode      *string `xml:"CurrencyCode" json:"CurrencyCode"`
	RetailTransaction *RetailTransaction
}

// OperatorID is the operator associeated with the transacions, some
// are done with system operators
type OperatorID struct {
	OperatorID   *int    `xml:",chardata" json:"OperatorID"`
	OperatorName *string `xml:"OperatorName,attr" json:"OperatorName"`
}

type RetailTransaction struct {
	Version            *string `xml:"Version,attr" json:"Version"`
	ReceiptDateTime    *string `xml:"ReceiptDateTime" json:"ReceiptDateTime"`
	TransactionCount   *int    `xml:"TransactionCount" json:"TransactionCount"`
	LineItem           []*LineItem
	Total              []*Total
	ItemCode           *int `xml:"ItemCount" json:"ItemCount"`
	PerformanceMetrics *PerformanceMetrics
}

// LineItem is a each action at the register, SequenceNumber will show actual sequence at register
type LineItem struct {
	EntryMethod    string  `xml:"EntryMethod,attr" json:"EntryMethod"`
	SequenceNumber int     `xml:"SequenceNumber" json:"SequenceNumber"`
	Sale           *Sale   `xml:"Sale,omitempty" json:"Sale,omitempty"`
	Tender         *Tender `xml:"Tender,omitempty" json:"Tender,omitempty"`
}

// Sale is a subtype of LineItem, the literal sale of an item
type Sale struct {
	ItemType               *string `xml:"ItemType,attr,omitempty" json:"ItemType,omitempty"`
	POSIdentity            *POSIdentity
	ItemID                 *string `xml:"ItemID" json:"ItemID"`
	MerchandiseHierarchy   *MerchandiseHierarchy
	Description            *string `Description:"ItemID"`
	RegularSalesUnitPrice  *string `xml:"RegularSalesUnitPrice" json:"RegularSalesUnitPrice"`
	ExtendedAmount         *string `xml:"ExtendedAmount" json:"ExtendedAmount"`
	DiscountAmount         *string `xml:"IteDiscountAmountmID" json:"IteDiscountAmountmID"`
	ExtendedDiscountAmount *string `ExtendedDiscountAmount:"ItemID"`
	Quantity               *string `xml:"Quantity" json:"Quantity"`
	OperatorSequence       *string `xml:"OperatorSequence" json:"OperatorSequence"`
	ReportCode             *string `xml:"ReportCode" json:"ReportCode"`
	SaleableMediaID        *string `xml:"SaleableMediaID" json:"SaleableMediaID"`
	Itemizers              *Itemizers
}

type POSIdentity struct {
	POSIDType *string `xml:"POSIDType,attr" json:"POSIDType"`
	POSItemID *string `xml:"POSItemID" json:"POSItemID"`
	Qualifier *int    `xml:"Qualifier" json:"Qualifier"`
}

type MerchandiseHierarchy struct {
	MerchandiseHierarchy     *int    `xml:",chardata" json:"MerchandiseHierarchy"`
	Level                    *string `xml:"Level,attr" json:"Level"`
	ACSDepartmentDescription *string `xml:"DepartmentDescription,attr" json:"DepartmentDescription"`
}

type Itemizers struct {
	Itemizers     int    `xml:",chardata" json:"Itemizers"`
	FoodStampable string `xml:"FoodStampable,attr,omitempty" json:"FoodStampable,omitempty"`
	Itemizer1     string `xml:"Itemizer1,attr,omitempty" json:"Itemizer1,omitempty"`
	Itemizer2     string `xml:"Itemizer2,attr,omitempty" json:"Itemizer2,omitempty"`
}

type Tender struct {
	TenderType        *string `xml:"TenderType,attr,omitempty" json:"TenderType,omitempty"`
	TypeCode          *string `xml:"TypeCode,attr,omitempty" json:"TypeCode,omitempty"`
	TenderDescription *string `xml:"TenderDescription,attr,omitempty" json:"TenderDescription,omitempty"`
	TenderID          *int    `xml:"TenderID,omitempty" json:"TenderID,omitempty"`
	Amount            *string `xml:"Amount,omitempty" json:"Amount,omitempty"`
}

type Total struct {
	Total     string `xml:",chardata" json:"Total"`
	TotalType string `xml:"TotalType,attr" json:"TotalType"`
}

type PerformanceMetrics struct {
	RingTime   int `xml:"RingTime" json:"RingTime"`
	IdleTime   int `xml:"IdleTime" json:"IdleTime"`
	TenderTime int `xml:"TenderTime" json:"TenderTime"`
}
<commit_msg>Remove some pointers, dayid and filename functions<commit_after>// Package poslog works with POSLog XML files.
// Currently writing types for NCR ACS POSLog
// But can be extended for other POS vendores and all
// NCR specific tags are (will be) set as options
package poslog

import (
	"strings"
)

// POSLog the main type of a POSLog XMl file. The type contains
// the marshaling information to marshal and unmarshal to json and xml
// currently it is not complete and does not handle all fields in
// source XML
type POSLog struct {
	Filename    string
	DayID       DayID
	POSLog      string `xml:"POSLog" json:"POSLog"`
	XMLNSPOSLog string `xml:"xmlns poslog,attr"`
	XMLNS       string `xml:"xmlns,attr"`
	XMLNSACS    string `xml:"xmlns acs,attr"`
	XMLNSRaw    string `xml:"xmlns raw,attr"`
	XMLNSXSI    string `xml:"xmlns xsi,attr"`
	XMLNSMSXSL  string `xml:"xmlns msxsl,attr"`
	XMLNSAS     string `xml:"xmlns as,attr"`
	XMLNSACSSM  string `xml:"xmlns acssm,attr"`
	// Transaction does not have to exist in the transaction, thus a pointer
	// and we we have a POSLog type with nothing in it (unless there is
	// another top level array type)
	Transaction []*Transaction `xml:"Transaction" json:"Transaction"`
}

// Transaction is the main body of a POSLog XML. Each Transaction is not
// a literal transaction at the register but each complete actions
// at the register so a sign in to register would be a transactions
type Transaction struct {
	RetailStoreID     int    `xml:"RetailStoreID" json:"RetailStoreID"`
	WorkstationID     int    `xml:"WorkstationID" json:"WorkstationID"`
	SequenceNumber    int    `xml:"SequenceNumber" json:"SequenceNumber"`
	BusinessDayDate   string `xml:"BusinessDayDate" json:"BusinessDayDate"`
	EndDateTime       string `xml:"EndDateTime" json:"EndDateTime"`
	OperatorID        *OperatorID
	CurrencyCode      *string `xml:"CurrencyCode" json:"CurrencyCode"`
	RetailTransaction *RetailTransaction
}

// OperatorID is the operator associeated with the transacions, some
// are done with system operators
type OperatorID struct {
	OperatorID   *int    `xml:",chardata" json:"OperatorID"`
	OperatorName *string `xml:"OperatorName,attr" json:"OperatorName"`
}

type RetailTransaction struct {
	Version            *string `xml:"Version,attr" json:"Version"`
	ReceiptDateTime    *string `xml:"ReceiptDateTime" json:"ReceiptDateTime"`
	TransactionCount   *int    `xml:"TransactionCount" json:"TransactionCount"`
	LineItem           []*LineItem
	Total              []*Total
	ItemCode           *int `xml:"ItemCount" json:"ItemCount"`
	PerformanceMetrics *PerformanceMetrics
}

// LineItem is a each action at the register, SequenceNumber will show actual sequence at register
type LineItem struct {
	EntryMethod    string  `xml:"EntryMethod,attr" json:"EntryMethod"`
	SequenceNumber int     `xml:"SequenceNumber" json:"SequenceNumber"`
	Sale           *Sale   `xml:"Sale,omitempty" json:"Sale,omitempty"`
	Tender         *Tender `xml:"Tender,omitempty" json:"Tender,omitempty"`
}

// Sale is a subtype of LineItem, the literal sale of an item
type Sale struct {
	ItemType               *string `xml:"ItemType,attr,omitempty" json:"ItemType,omitempty"`
	POSIdentity            *POSIdentity
	ItemID                 *string `xml:"ItemID" json:"ItemID"`
	MerchandiseHierarchy   *MerchandiseHierarchy
	Description            *string `Description:"ItemID"`
	RegularSalesUnitPrice  *string `xml:"RegularSalesUnitPrice" json:"RegularSalesUnitPrice"`
	ExtendedAmount         *string `xml:"ExtendedAmount" json:"ExtendedAmount"`
	DiscountAmount         *string `xml:"IteDiscountAmountmID" json:"IteDiscountAmountmID"`
	ExtendedDiscountAmount *string `ExtendedDiscountAmount:"ItemID"`
	Quantity               *string `xml:"Quantity" json:"Quantity"`
	OperatorSequence       *string `xml:"OperatorSequence" json:"OperatorSequence"`
	ReportCode             *string `xml:"ReportCode" json:"ReportCode"`
	SaleableMediaID        *string `xml:"SaleableMediaID" json:"SaleableMediaID"`
	Itemizers              *Itemizers
}

type POSIdentity struct {
	POSIDType *string `xml:"POSIDType,attr" json:"POSIDType"`
	POSItemID *string `xml:"POSItemID" json:"POSItemID"`
	Qualifier *int    `xml:"Qualifier" json:"Qualifier"`
}

type MerchandiseHierarchy struct {
	MerchandiseHierarchy     *int    `xml:",chardata" json:"MerchandiseHierarchy"`
	Level                    *string `xml:"Level,attr" json:"Level"`
	ACSDepartmentDescription *string `xml:"DepartmentDescription,attr" json:"DepartmentDescription"`
}

type Itemizers struct {
	Itemizers     int    `xml:",chardata" json:"Itemizers"`
	FoodStampable string `xml:"FoodStampable,attr,omitempty" json:"FoodStampable,omitempty"`
	Itemizer1     string `xml:"Itemizer1,attr,omitempty" json:"Itemizer1,omitempty"`
	Itemizer2     string `xml:"Itemizer2,attr,omitempty" json:"Itemizer2,omitempty"`
}

type Tender struct {
	TenderType        *string `xml:"TenderType,attr,omitempty" json:"TenderType,omitempty"`
	TypeCode          *string `xml:"TypeCode,attr,omitempty" json:"TypeCode,omitempty"`
	TenderDescription *string `xml:"TenderDescription,attr,omitempty" json:"TenderDescription,omitempty"`
	TenderID          *int    `xml:"TenderID,omitempty" json:"TenderID,omitempty"`
	Amount            *string `xml:"Amount,omitempty" json:"Amount,omitempty"`
}

type Total struct {
	Total     string `xml:",chardata" json:"Total"`
	TotalType string `xml:"TotalType,attr" json:"TotalType"`
}

type PerformanceMetrics struct {
	RingTime   int `xml:"RingTime" json:"RingTime"`
	IdleTime   int `xml:"IdleTime" json:"IdleTime"`
	TenderTime int `xml:"TenderTime" json:"TenderTime"`
}

// DayID no a part of POSLog XML directly but is used as a simple way of
// grouping and sorting by day. Format is YYYYMMDD, which will always sort
// an makes for easy ranges. This type will be expanded with validation
type DayID struct {
	DayID string
	Year  string
	Month string
	Day   string
}

func (p *POSLog) appendDayID() {
	var bds []string
	for _, t := range p.Transaction {
		if len(bds) > 0 {
			for _, c := range bds {
				if t.BusinessDayDate == c {
					continue
				} else {
					bds = append(bds, t.BusinessDayDate)
					continue
				}
			}
		} else {
			bds = append(bds, t.BusinessDayDate)
		}

	}
	if len(bds) > 1 {
		panic("More Buisness days")
	} else {
		ymd := strings.Split(bds[0], "-")
		p.DayID.toDayID(ymd[0], ymd[1], ymd[2])
	}
	return
}

func (p *POSLog) appendFilename(filename string) {
	p.Filename = filename
}

func (d *DayID) toDayID(year string, month string, day string) {
	d.Year = year
	d.Month = month
	d.Day = day
	d.DayID = year + month + day
	return
}
<|endoftext|>


<commit_before>package psbt

import (
	"bytes"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcec/v2/ecdsa"
)

// PartialSig encapsulate a (BTC public key, ECDSA signature)
// pair, note that the fields are stored as byte slices, not
// btcec.PublicKey or btcec.Signature (because manipulations will
// be with the former not the latter, here); compliance with consensus
// serialization is enforced with .checkValid()
type PartialSig struct {
	PubKey    []byte
	Signature []byte
}

// PartialSigSorter implements sort.Interface for PartialSig.
type PartialSigSorter []*PartialSig

func (s PartialSigSorter) Len() int { return len(s) }

func (s PartialSigSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

func (s PartialSigSorter) Less(i, j int) bool {
	return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0
}

// validatePubkey checks if pubKey is *any* valid pubKey serialization in a
// Bitcoin context (compressed/uncomp. OK).
func validatePubkey(pubKey []byte) bool {
	_, err := btcec.ParsePubKey(pubKey)
	return err == nil
}

// validateSignature checks that the passed byte slice is a valid DER-encoded
// ECDSA signature, including the sighash flag.  It does *not* of course
// validate the signature against any message or public key.
func validateSignature(sig []byte) bool {
	_, err := ecdsa.ParseDERSignature(sig)
	return err == nil
}

// checkValid checks that both the pbukey and sig are valid. See the methods
// (PartialSig, validatePubkey, validateSignature) for more details.
//
// TODO(waxwing): update for Schnorr will be needed here if/when that
// activates.
func (ps *PartialSig) checkValid() bool {
	return validatePubkey(ps.PubKey) && validateSignature(ps.Signature)
}
<commit_msg>psbt: fix typo, remove TODO<commit_after>package psbt

import (
	"bytes"
	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcec/v2/ecdsa"
)

// PartialSig encapsulate a (BTC public key, ECDSA signature)
// pair, note that the fields are stored as byte slices, not
// btcec.PublicKey or btcec.Signature (because manipulations will
// be with the former not the latter, here); compliance with consensus
// serialization is enforced with .checkValid()
type PartialSig struct {
	PubKey    []byte
	Signature []byte
}

// PartialSigSorter implements sort.Interface for PartialSig.
type PartialSigSorter []*PartialSig

func (s PartialSigSorter) Len() int { return len(s) }

func (s PartialSigSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

func (s PartialSigSorter) Less(i, j int) bool {
	return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0
}

// validatePubkey checks if pubKey is *any* valid pubKey serialization in a
// Bitcoin context (compressed/uncomp. OK).
func validatePubkey(pubKey []byte) bool {
	_, err := btcec.ParsePubKey(pubKey)
	return err == nil
}

// validateSignature checks that the passed byte slice is a valid DER-encoded
// ECDSA signature, including the sighash flag.  It does *not* of course
// validate the signature against any message or public key.
func validateSignature(sig []byte) bool {
	_, err := ecdsa.ParseDERSignature(sig)
	return err == nil
}

// checkValid checks that both the pubkey and sig are valid. See the methods
// (PartialSig, validatePubkey, validateSignature) for more details.
func (ps *PartialSig) checkValid() bool {
	return validatePubkey(ps.PubKey) && validateSignature(ps.Signature)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"log"
	"net"
	"strconv"
	"strings"
)

func main() {
	var port = flag.Int("port", 8000, "the port on which to listen")
	flag.Parse()

	log.Printf("starting on %d\n", *port)
	server, err := net.Listen("tcp", ":"+strconv.Itoa(*port))
	if server == nil {
		log.Fatal(err)
	}
	conns := clientConns(server)
	for {
		go handleConn(<-conns)
	}
}

func clientConns(listener net.Listener) chan net.Conn {
	ch := make(chan net.Conn)
	i := 0
	go func() {
		for {
			client, err := listener.Accept()
			if client == nil {
				log.Fatal(err)
				continue
			}
			i++
			log.Printf("%d: %v <-> %v\n", i, client.LocalAddr(), client.RemoteAddr())
			ch <- client
		}
	}()
	return ch
}

func handleConn(client net.Conn) {
	b := bufio.NewReader(client)
	for {
		line, err := b.ReadString('\n')
		if err != nil { // EOF, or worse
			break
		}
		log.Println(strings.TrimSpace(line))
	}
}
<commit_msg>go doc<commit_after>// `prnsrv`` provides a simple tcp server that can be used to debug the output
// of applications pushing data to another tcp server.
//
// To use run, use either
//   `go run prnsrv.go` or `prnsrv` if you've installed the package
// To listen on port other than `8000`, use the -p flag
//   `go run prnsrv.go -p 8181` or `prnsrv -p 8181`
package main

import (
	"bufio"
	"flag"
	"log"
	"net"
	"strconv"
	"strings"
)

func main() {
	var port = flag.Int("port", 8000, "the port on which to listen")
	flag.Parse()

	log.Printf("starting on %d\n", *port)
	server, err := net.Listen("tcp", ":"+strconv.Itoa(*port))
	if server == nil {
		log.Fatal(err)
	}
	conns := clientConns(server)
	for {
		go handleConn(<-conns)
	}
}

func clientConns(listener net.Listener) chan net.Conn {
	ch := make(chan net.Conn)
	i := 0
	go func() {
		for {
			client, err := listener.Accept()
			if client == nil {
				log.Fatal(err)
				continue
			}
			i++
			log.Printf("%d: %v <-> %v\n", i, client.LocalAddr(), client.RemoteAddr())
			ch <- client
		}
	}()
	return ch
}

func handleConn(client net.Conn) {
	b := bufio.NewReader(client)
	for {
		line, err := b.ReadString('\n')
		if err != nil { // EOF, or worse
			break
		}
		log.Println(strings.TrimSpace(line))
	}
}
<|endoftext|>


<commit_before>package radix

import (
	"bufio"
	"bytes"
	"errors"
	"io"
	"net"
	"sync"
	"time"

	"github.com/mediocregopher/radix/v3/resp"
	"github.com/mediocregopher/radix/v3/resp/resp2"
)

// PubSubMessage describes a message being published to a subscribed channel
type PubSubMessage struct {
	Type    string // "message" or "pmessage"
	Pattern string // will be set if Type is "pmessage"
	Channel string
	Message []byte
}

// MarshalRESP implements the Marshaler interface. It will assume the
// PubSubMessage is a PMESSAGE if Pattern is non-empty.
func (m PubSubMessage) MarshalRESP(w io.Writer) error {
	var err error
	marshal := func(m resp.Marshaler) {
		if err == nil {
			err = m.MarshalRESP(w)
		}
	}

	if m.Type == "message" {
		marshal(resp2.ArrayHeader{N: 3})
		marshal(resp2.BulkString{S: m.Type})
	} else if m.Type == "pmessage" {
		marshal(resp2.ArrayHeader{N: 4})
		marshal(resp2.BulkString{S: m.Type})
		marshal(resp2.BulkString{S: m.Pattern})
	} else {
		return errors.New("unknown message Type")
	}
	marshal(resp2.BulkString{S: m.Channel})
	marshal(resp2.BulkStringBytes{B: m.Message})
	return err
}

var errNotPubSubMessage = errors.New("message is not a PubSubMessage")

// UnmarshalRESP implements the Unmarshaler interface
func (m *PubSubMessage) UnmarshalRESP(br *bufio.Reader) error {
	// This method will fully consume the message on the wire, regardless of if
	// it is a PubSubMessage or not. If it is not then errNotPubSubMessage is
	// returned.

	// When in subscribe mode redis only allows (P)(UN)SUBSCRIBE commands, which
	// all return arrays, and PING, which returns an array when in subscribe
	// mode. HOWEVER, when all channels have been unsubscribed from then the
	// connection will be taken _out_ of subscribe mode. This is theoretically
	// fine, since the driver will still only allow the 5 commands, except PING
	// will return a simple string when in the non-subscribed state. So this
	// needs to check for that.
	if prefix, err := br.Peek(1); err != nil {
		return err
	} else if bytes.Equal(prefix, resp2.SimpleStringPrefix) {
		// if it's a simple string, discard it (it's probably PONG) and error
		if err := (resp2.Any{}).UnmarshalRESP(br); err != nil {
			return err
		}
		return errNotPubSubMessage
	}

	var ah resp2.ArrayHeader
	if err := ah.UnmarshalRESP(br); err != nil {
		return err
	} else if ah.N < 2 {
		return errors.New("message has too few elements")
	}

	var msgType resp2.BulkStringBytes
	if err := msgType.UnmarshalRESP(br); err != nil {
		return err
	}

	switch string(msgType.B) {
	case "message":
		m.Type = "message"
		if ah.N != 3 {
			return errors.New("message has wrong number of elements")
		}
	case "pmessage":
		m.Type = "pmessage"
		if ah.N != 4 {
			return errors.New("message has wrong number of elements")
		}

		var pattern resp2.BulkString
		if err := pattern.UnmarshalRESP(br); err != nil {
			return err
		}
		m.Pattern = pattern.S
	default:
		// if it's not a PubSubMessage then discard the rest of the array
		for i := 1; i < ah.N; i++ {
			if err := (resp2.Any{}).UnmarshalRESP(br); err != nil {
				return err
			}
		}
		return errNotPubSubMessage
	}

	var channel resp2.BulkString
	if err := channel.UnmarshalRESP(br); err != nil {
		return err
	}
	m.Channel = channel.S

	var msg resp2.BulkStringBytes
	if err := msg.UnmarshalRESP(br); err != nil {
		return err
	}
	m.Message = msg.B

	return nil
}

////////////////////////////////////////////////////////////////////////////////

type chanSet map[string]map[chan<- PubSubMessage]bool

func (cs chanSet) add(s string, ch chan<- PubSubMessage) {
	m, ok := cs[s]
	if !ok {
		m = map[chan<- PubSubMessage]bool{}
		cs[s] = m
	}
	m[ch] = true
}

func (cs chanSet) del(s string, ch chan<- PubSubMessage) bool {
	m, ok := cs[s]
	if !ok {
		return true
	}
	delete(m, ch)
	if len(m) == 0 {
		delete(cs, s)
		return true
	}
	return false
}

func (cs chanSet) missing(ss []string) []string {
	out := ss[:0]
	for _, s := range ss {
		if _, ok := cs[s]; !ok {
			out = append(out, s)
		}
	}
	return out
}

func (cs chanSet) inverse() map[chan<- PubSubMessage][]string {
	inv := map[chan<- PubSubMessage][]string{}
	for s, m := range cs {
		for ch := range m {
			inv[ch] = append(inv[ch], s)
		}
	}
	return inv
}

////////////////////////////////////////////////////////////////////////////////

// PubSubConn wraps an existing Conn to support redis' pubsub system.
// User-created channels can be subscribed to redis channels to receive
// PubSubMessages which have been published.
//
// If any methods return an error it means the PubSubConn has been Close'd and
// subscribed msgCh's will no longer receive PubSubMessages from it. All methods
// are threadsafe and non-blocking.
//
// NOTE if any channels block when being written to they will block all other
// channels from receiving a publish.
type PubSubConn interface {
	// Subscribe subscribes the PubSubConn to the given set of channels. msgCh
	// will receieve a PubSubMessage for every publish written to any of the
	// channels. This may be called multiple times for the same channels and
	// different msgCh's, each msgCh will receieve a copy of the PubSubMessage
	// for each publish.
	Subscribe(msgCh chan<- PubSubMessage, channels ...string) error

	// Unsubscribe unsubscribes the msgCh from the given set of channels, if it
	// was subscribed at all.
	//
	// NOTE even if msgCh is not subscribed to any other redis channels, it
	// should still be considered "active", and therefore still be having
	// messages read from it, until Unsubscribe has returned
	Unsubscribe(msgCh chan<- PubSubMessage, channels ...string) error

	// PSubscribe is like Subscribe, but it subscribes msgCh to a set of
	// patterns and not individual channels.
	PSubscribe(msgCh chan<- PubSubMessage, patterns ...string) error

	// PUnsubscribe is like Unsubscribe, but it unsubscribes msgCh from a set of
	// patterns and not individual channels.
	//
	// NOTE even if msgCh is not subscribed to any other redis channels, it
	// should still be considered "active", and therefore still be having
	// messages read from it, until PUnsubscribe has returned
	PUnsubscribe(msgCh chan<- PubSubMessage, patterns ...string) error

	// Ping performs a simple Ping command on the PubSubConn, returning an error
	// if it failed for some reason
	Ping() error

	// Close closes the PubSubConn so it can't be used anymore. All subscribed
	// channels will stop receiving PubSubMessages from this Conn (but will not
	// themselves be closed).
	//
	// NOTE all msgChs should be considered "active", and therefore still be
	// having messages read from them, until Close has returned.
	Close() error
}

type pubSubConn struct {
	conn Conn

	csL   sync.RWMutex
	subs  chanSet
	psubs chanSet

	// These are used for writing commands and waiting for their response (e.g.
	// SUBSCRIBE, PING). See the do method for how that works.
	cmdL     sync.Mutex
	cmdResCh chan error

	close    sync.Once
	closeErr error

	// This one is optional, and kind of cheating. We use it in persistent to
	// get on-the-fly updates of when the connection fails. Maybe one day this
	// could be exposed if there's a clean way of doing so, or another way
	// accomplishing the same thing could be done instead.
	closeErrCh chan error

	// only used during testing
	testEventCh chan string
}

// PubSub wraps the given Conn so that it becomes a PubSubConn. The passed in
// Conn should not be used after this call.
func PubSub(rc Conn) PubSubConn {
	return newPubSub(rc, nil)
}

func newPubSub(rc Conn, closeErrCh chan error) PubSubConn {
	c := &pubSubConn{
		conn:       rc,
		subs:       chanSet{},
		psubs:      chanSet{},
		cmdResCh:   make(chan error, 1),
		closeErrCh: closeErrCh,
	}
	go c.spin()

	// Periodically call Ping so the connection has a keepalive on the
	// application level. If the Conn is closed Ping will return an error and
	// this will clean itself up.
	go func() {
		t := time.NewTicker(5 * time.Second)
		defer t.Stop()
		for range t.C {
			if err := c.Ping(); err != nil {
				return
			}
		}
	}()

	return c
}

func (c *pubSubConn) testEvent(str string) {
	if c.testEventCh != nil {
		c.testEventCh <- str
	}
}

func (c *pubSubConn) publish(m PubSubMessage) {
	c.csL.RLock()
	defer c.csL.RUnlock()

	var subs map[chan<- PubSubMessage]bool
	if m.Type == "pmessage" {
		subs = c.psubs[m.Pattern]
	} else {
		subs = c.subs[m.Channel]
	}

	for ch := range subs {
		ch <- m
	}
}

func (c *pubSubConn) spin() {
	for {
		var m PubSubMessage
		err := c.conn.Decode(&m)
		if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
			c.testEvent("timeout")
			continue
		} else if err == errNotPubSubMessage {
			c.cmdResCh <- nil
			continue
		} else if err != nil {
			c.closeInner(err)
			return
		}
		c.publish(m)
	}
}

// NOTE cmdL _must_ be held to use do
func (c *pubSubConn) do(exp int, cmd string, args ...string) error {
	rcmd := Cmd(nil, cmd, args...)
	if err := c.conn.Encode(rcmd); err != nil {
		return err
	}

	for i := 0; i < exp; i++ {
		err, ok := <-c.cmdResCh
		if err != nil {
			return err
		} else if !ok {
			return errors.New("connection closed")
		}
	}
	return nil
}

func (c *pubSubConn) closeInner(cmdResErr error) error {
	c.close.Do(func() {
		c.csL.Lock()
		defer c.csL.Unlock()
		c.closeErr = c.conn.Close()
		c.subs = nil
		c.psubs = nil

		if cmdResErr != nil {
			select {
			case c.cmdResCh <- cmdResErr:
			default:
			}
		}
		if c.closeErrCh != nil {
			c.closeErrCh <- cmdResErr
			close(c.closeErrCh)
		}
		close(c.cmdResCh)
	})
	return c.closeErr
}

func (c *pubSubConn) Close() error {
	return c.closeInner(nil)
}

func (c *pubSubConn) Subscribe(msgCh chan<- PubSubMessage, channels ...string) error {
	c.cmdL.Lock()
	defer c.cmdL.Unlock()

	c.csL.RLock()
	missing := c.subs.missing(channels)
	c.csL.RUnlock()

	if len(missing) > 0 {
		if err := c.do(len(missing), "SUBSCRIBE", missing...); err != nil {
			return err
		}
	}

	c.csL.Lock()
	for _, channel := range channels {
		c.subs.add(channel, msgCh)
	}
	c.csL.Unlock()

	return nil
}

func (c *pubSubConn) Unsubscribe(msgCh chan<- PubSubMessage, channels ...string) error {
	c.cmdL.Lock()
	defer c.cmdL.Unlock()

	c.csL.Lock()
	emptyChannels := make([]string, 0, len(channels))
	for _, channel := range channels {
		if empty := c.subs.del(channel, msgCh); empty {
			emptyChannels = append(emptyChannels, channel)
		}
	}
	c.csL.Unlock()

	if len(emptyChannels) == 0 {
		return nil
	}

	return c.do(len(emptyChannels), "UNSUBSCRIBE", emptyChannels...)
}

func (c *pubSubConn) PSubscribe(msgCh chan<- PubSubMessage, patterns ...string) error {
	c.cmdL.Lock()
	defer c.cmdL.Unlock()

	c.csL.RLock()
	missing := c.psubs.missing(patterns)
	c.csL.RUnlock()

	if len(missing) > 0 {
		if err := c.do(len(missing), "PSUBSCRIBE", missing...); err != nil {
			return err
		}
	}

	c.csL.Lock()
	for _, pattern := range patterns {
		c.psubs.add(pattern, msgCh)
	}
	c.csL.Unlock()

	return nil
}

func (c *pubSubConn) PUnsubscribe(msgCh chan<- PubSubMessage, patterns ...string) error {
	c.cmdL.Lock()
	defer c.cmdL.Unlock()

	c.csL.Lock()
	emptyPatterns := make([]string, 0, len(patterns))
	for _, pattern := range patterns {
		if empty := c.psubs.del(pattern, msgCh); empty {
			emptyPatterns = append(emptyPatterns, pattern)
		}
	}
	c.csL.Unlock()

	if len(emptyPatterns) == 0 {
		return nil
	}

	return c.do(len(emptyPatterns), "PUNSUBSCRIBE", emptyPatterns...)
}

func (c *pubSubConn) Ping() error {
	c.cmdL.Lock()
	defer c.cmdL.Unlock()

	return c.do(1, "PING")
}
<commit_msg>Remove a wrong comment on PubSubMessage<commit_after>package radix

import (
	"bufio"
	"bytes"
	"errors"
	"io"
	"net"
	"sync"
	"time"

	"github.com/mediocregopher/radix/v3/resp"
	"github.com/mediocregopher/radix/v3/resp/resp2"
)

// PubSubMessage describes a message being published to a subscribed channel
type PubSubMessage struct {
	Type    string // "message" or "pmessage"
	Pattern string // will be set if Type is "pmessage"
	Channel string
	Message []byte
}

// MarshalRESP implements the Marshaler interface.
func (m PubSubMessage) MarshalRESP(w io.Writer) error {
	var err error
	marshal := func(m resp.Marshaler) {
		if err == nil {
			err = m.MarshalRESP(w)
		}
	}

	if m.Type == "message" {
		marshal(resp2.ArrayHeader{N: 3})
		marshal(resp2.BulkString{S: m.Type})
	} else if m.Type == "pmessage" {
		marshal(resp2.ArrayHeader{N: 4})
		marshal(resp2.BulkString{S: m.Type})
		marshal(resp2.BulkString{S: m.Pattern})
	} else {
		return errors.New("unknown message Type")
	}
	marshal(resp2.BulkString{S: m.Channel})
	marshal(resp2.BulkStringBytes{B: m.Message})
	return err
}

var errNotPubSubMessage = errors.New("message is not a PubSubMessage")

// UnmarshalRESP implements the Unmarshaler interface
func (m *PubSubMessage) UnmarshalRESP(br *bufio.Reader) error {
	// This method will fully consume the message on the wire, regardless of if
	// it is a PubSubMessage or not. If it is not then errNotPubSubMessage is
	// returned.

	// When in subscribe mode redis only allows (P)(UN)SUBSCRIBE commands, which
	// all return arrays, and PING, which returns an array when in subscribe
	// mode. HOWEVER, when all channels have been unsubscribed from then the
	// connection will be taken _out_ of subscribe mode. This is theoretically
	// fine, since the driver will still only allow the 5 commands, except PING
	// will return a simple string when in the non-subscribed state. So this
	// needs to check for that.
	if prefix, err := br.Peek(1); err != nil {
		return err
	} else if bytes.Equal(prefix, resp2.SimpleStringPrefix) {
		// if it's a simple string, discard it (it's probably PONG) and error
		if err := (resp2.Any{}).UnmarshalRESP(br); err != nil {
			return err
		}
		return errNotPubSubMessage
	}

	var ah resp2.ArrayHeader
	if err := ah.UnmarshalRESP(br); err != nil {
		return err
	} else if ah.N < 2 {
		return errors.New("message has too few elements")
	}

	var msgType resp2.BulkStringBytes
	if err := msgType.UnmarshalRESP(br); err != nil {
		return err
	}

	switch string(msgType.B) {
	case "message":
		m.Type = "message"
		if ah.N != 3 {
			return errors.New("message has wrong number of elements")
		}
	case "pmessage":
		m.Type = "pmessage"
		if ah.N != 4 {
			return errors.New("message has wrong number of elements")
		}

		var pattern resp2.BulkString
		if err := pattern.UnmarshalRESP(br); err != nil {
			return err
		}
		m.Pattern = pattern.S
	default:
		// if it's not a PubSubMessage then discard the rest of the array
		for i := 1; i < ah.N; i++ {
			if err := (resp2.Any{}).UnmarshalRESP(br); err != nil {
				return err
			}
		}
		return errNotPubSubMessage
	}

	var channel resp2.BulkString
	if err := channel.UnmarshalRESP(br); err != nil {
		return err
	}
	m.Channel = channel.S

	var msg resp2.BulkStringBytes
	if err := msg.UnmarshalRESP(br); err != nil {
		return err
	}
	m.Message = msg.B

	return nil
}

////////////////////////////////////////////////////////////////////////////////

type chanSet map[string]map[chan<- PubSubMessage]bool

func (cs chanSet) add(s string, ch chan<- PubSubMessage) {
	m, ok := cs[s]
	if !ok {
		m = map[chan<- PubSubMessage]bool{}
		cs[s] = m
	}
	m[ch] = true
}

func (cs chanSet) del(s string, ch chan<- PubSubMessage) bool {
	m, ok := cs[s]
	if !ok {
		return true
	}
	delete(m, ch)
	if len(m) == 0 {
		delete(cs, s)
		return true
	}
	return false
}

func (cs chanSet) missing(ss []string) []string {
	out := ss[:0]
	for _, s := range ss {
		if _, ok := cs[s]; !ok {
			out = append(out, s)
		}
	}
	return out
}

func (cs chanSet) inverse() map[chan<- PubSubMessage][]string {
	inv := map[chan<- PubSubMessage][]string{}
	for s, m := range cs {
		for ch := range m {
			inv[ch] = append(inv[ch], s)
		}
	}
	return inv
}

////////////////////////////////////////////////////////////////////////////////

// PubSubConn wraps an existing Conn to support redis' pubsub system.
// User-created channels can be subscribed to redis channels to receive
// PubSubMessages which have been published.
//
// If any methods return an error it means the PubSubConn has been Close'd and
// subscribed msgCh's will no longer receive PubSubMessages from it. All methods
// are threadsafe and non-blocking.
//
// NOTE if any channels block when being written to they will block all other
// channels from receiving a publish.
type PubSubConn interface {
	// Subscribe subscribes the PubSubConn to the given set of channels. msgCh
	// will receieve a PubSubMessage for every publish written to any of the
	// channels. This may be called multiple times for the same channels and
	// different msgCh's, each msgCh will receieve a copy of the PubSubMessage
	// for each publish.
	Subscribe(msgCh chan<- PubSubMessage, channels ...string) error

	// Unsubscribe unsubscribes the msgCh from the given set of channels, if it
	// was subscribed at all.
	//
	// NOTE even if msgCh is not subscribed to any other redis channels, it
	// should still be considered "active", and therefore still be having
	// messages read from it, until Unsubscribe has returned
	Unsubscribe(msgCh chan<- PubSubMessage, channels ...string) error

	// PSubscribe is like Subscribe, but it subscribes msgCh to a set of
	// patterns and not individual channels.
	PSubscribe(msgCh chan<- PubSubMessage, patterns ...string) error

	// PUnsubscribe is like Unsubscribe, but it unsubscribes msgCh from a set of
	// patterns and not individual channels.
	//
	// NOTE even if msgCh is not subscribed to any other redis channels, it
	// should still be considered "active", and therefore still be having
	// messages read from it, until PUnsubscribe has returned
	PUnsubscribe(msgCh chan<- PubSubMessage, patterns ...string) error

	// Ping performs a simple Ping command on the PubSubConn, returning an error
	// if it failed for some reason
	Ping() error

	// Close closes the PubSubConn so it can't be used anymore. All subscribed
	// channels will stop receiving PubSubMessages from this Conn (but will not
	// themselves be closed).
	//
	// NOTE all msgChs should be considered "active", and therefore still be
	// having messages read from them, until Close has returned.
	Close() error
}

type pubSubConn struct {
	conn Conn

	csL   sync.RWMutex
	subs  chanSet
	psubs chanSet

	// These are used for writing commands and waiting for their response (e.g.
	// SUBSCRIBE, PING). See the do method for how that works.
	cmdL     sync.Mutex
	cmdResCh chan error

	close    sync.Once
	closeErr error

	// This one is optional, and kind of cheating. We use it in persistent to
	// get on-the-fly updates of when the connection fails. Maybe one day this
	// could be exposed if there's a clean way of doing so, or another way
	// accomplishing the same thing could be done instead.
	closeErrCh chan error

	// only used during testing
	testEventCh chan string
}

// PubSub wraps the given Conn so that it becomes a PubSubConn. The passed in
// Conn should not be used after this call.
func PubSub(rc Conn) PubSubConn {
	return newPubSub(rc, nil)
}

func newPubSub(rc Conn, closeErrCh chan error) PubSubConn {
	c := &pubSubConn{
		conn:       rc,
		subs:       chanSet{},
		psubs:      chanSet{},
		cmdResCh:   make(chan error, 1),
		closeErrCh: closeErrCh,
	}
	go c.spin()

	// Periodically call Ping so the connection has a keepalive on the
	// application level. If the Conn is closed Ping will return an error and
	// this will clean itself up.
	go func() {
		t := time.NewTicker(5 * time.Second)
		defer t.Stop()
		for range t.C {
			if err := c.Ping(); err != nil {
				return
			}
		}
	}()

	return c
}

func (c *pubSubConn) testEvent(str string) {
	if c.testEventCh != nil {
		c.testEventCh <- str
	}
}

func (c *pubSubConn) publish(m PubSubMessage) {
	c.csL.RLock()
	defer c.csL.RUnlock()

	var subs map[chan<- PubSubMessage]bool
	if m.Type == "pmessage" {
		subs = c.psubs[m.Pattern]
	} else {
		subs = c.subs[m.Channel]
	}

	for ch := range subs {
		ch <- m
	}
}

func (c *pubSubConn) spin() {
	for {
		var m PubSubMessage
		err := c.conn.Decode(&m)
		if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
			c.testEvent("timeout")
			continue
		} else if err == errNotPubSubMessage {
			c.cmdResCh <- nil
			continue
		} else if err != nil {
			c.closeInner(err)
			return
		}
		c.publish(m)
	}
}

// NOTE cmdL _must_ be held to use do
func (c *pubSubConn) do(exp int, cmd string, args ...string) error {
	rcmd := Cmd(nil, cmd, args...)
	if err := c.conn.Encode(rcmd); err != nil {
		return err
	}

	for i := 0; i < exp; i++ {
		err, ok := <-c.cmdResCh
		if err != nil {
			return err
		} else if !ok {
			return errors.New("connection closed")
		}
	}
	return nil
}

func (c *pubSubConn) closeInner(cmdResErr error) error {
	c.close.Do(func() {
		c.csL.Lock()
		defer c.csL.Unlock()
		c.closeErr = c.conn.Close()
		c.subs = nil
		c.psubs = nil

		if cmdResErr != nil {
			select {
			case c.cmdResCh <- cmdResErr:
			default:
			}
		}
		if c.closeErrCh != nil {
			c.closeErrCh <- cmdResErr
			close(c.closeErrCh)
		}
		close(c.cmdResCh)
	})
	return c.closeErr
}

func (c *pubSubConn) Close() error {
	return c.closeInner(nil)
}

func (c *pubSubConn) Subscribe(msgCh chan<- PubSubMessage, channels ...string) error {
	c.cmdL.Lock()
	defer c.cmdL.Unlock()

	c.csL.RLock()
	missing := c.subs.missing(channels)
	c.csL.RUnlock()

	if len(missing) > 0 {
		if err := c.do(len(missing), "SUBSCRIBE", missing...); err != nil {
			return err
		}
	}

	c.csL.Lock()
	for _, channel := range channels {
		c.subs.add(channel, msgCh)
	}
	c.csL.Unlock()

	return nil
}

func (c *pubSubConn) Unsubscribe(msgCh chan<- PubSubMessage, channels ...string) error {
	c.cmdL.Lock()
	defer c.cmdL.Unlock()

	c.csL.Lock()
	emptyChannels := make([]string, 0, len(channels))
	for _, channel := range channels {
		if empty := c.subs.del(channel, msgCh); empty {
			emptyChannels = append(emptyChannels, channel)
		}
	}
	c.csL.Unlock()

	if len(emptyChannels) == 0 {
		return nil
	}

	return c.do(len(emptyChannels), "UNSUBSCRIBE", emptyChannels...)
}

func (c *pubSubConn) PSubscribe(msgCh chan<- PubSubMessage, patterns ...string) error {
	c.cmdL.Lock()
	defer c.cmdL.Unlock()

	c.csL.RLock()
	missing := c.psubs.missing(patterns)
	c.csL.RUnlock()

	if len(missing) > 0 {
		if err := c.do(len(missing), "PSUBSCRIBE", missing...); err != nil {
			return err
		}
	}

	c.csL.Lock()
	for _, pattern := range patterns {
		c.psubs.add(pattern, msgCh)
	}
	c.csL.Unlock()

	return nil
}

func (c *pubSubConn) PUnsubscribe(msgCh chan<- PubSubMessage, patterns ...string) error {
	c.cmdL.Lock()
	defer c.cmdL.Unlock()

	c.csL.Lock()
	emptyPatterns := make([]string, 0, len(patterns))
	for _, pattern := range patterns {
		if empty := c.psubs.del(pattern, msgCh); empty {
			emptyPatterns = append(emptyPatterns, pattern)
		}
	}
	c.csL.Unlock()

	if len(emptyPatterns) == 0 {
		return nil
	}

	return c.do(len(emptyPatterns), "PUNSUBSCRIBE", emptyPatterns...)
}

func (c *pubSubConn) Ping() error {
	c.cmdL.Lock()
	defer c.cmdL.Unlock()

	return c.do(1, "PING")
}
<|endoftext|>


<commit_before>package gore

import (
	"sync"
	"time"
)

// Message is a nofitication from a subscribed channel or pchannel
type Message struct {
	// message or pmessage
	Type string
	// The channel/pchannel the client subscribed to. For example: "test", "te*"
	Channel string
	// The channel that publisher published to. For example "test", "text"
	OriginalChannel string
	// The payload
	Message []byte
}

// Publish a message to a channel over conn
func Publish(conn *Conn, channel string, message interface{}) error {
	_, err := NewCommand("PUBLISH", channel, message).Run(conn)
	return err
}

// Subscriptions keeps all SUBSCRIBE and PSUBSCRIBE channels, and handles
// all errors and re-subcription process when the connection is down then reconnected.
// A dedicated connection should be used for Subscriptions
type Subscriptions struct {
	channels       map[string]struct{}
	pchannels      map[string]struct{}
	conn           *Conn
	closed         bool
	messageChannel chan *Message
	lock           sync.Mutex
	ready          bool
	readyChannel   chan bool
	// Sentinel will set this to true to handle read error from sentinel server.
	throwError bool
}

// NewSubscriptions returns new Subscriptions
func NewSubscriptions(conn *Conn) *Subscriptions {
	s := &Subscriptions{
		channels:       make(map[string]struct{}),
		pchannels:      make(map[string]struct{}),
		conn:           conn,
		messageChannel: make(chan *Message, 100),
		ready:          true,
		readyChannel:   make(chan bool, 1),
		throwError:     false,
	}
	go s.receive()
	return s
}

// Subscribe subscribes to a list of channels
func (s *Subscriptions) Subscribe(channel ...string) error {
	return s.do("SUBSCRIBE", channel...)
}

// PSubscribe subscribes to a list of channels with given pattern
func (s *Subscriptions) PSubscribe(channel ...string) error {
	return s.do("PSUBSCRIBE", channel...)
}

// Unsubscribe unsubscribes to a list of channels
func (s *Subscriptions) Unsubscribe(channel ...string) error {
	return s.do("UNSUBSCRIBE", channel...)
}

// PUnsubscribe unsubscribes to a list of channels with given pattern
func (s *Subscriptions) PUnsubscribe(channel ...string) error {
	return s.do("PUNSUBSCRIBE", channel...)
}

// Close terminates the subscriptions.
// The connection is NOT closed. You should close it if you do not want to
// use anymore.
func (s *Subscriptions) Close() {
	s.lock.Lock()
	defer s.lock.Unlock()
	if s.closed {
		return
	}
	s.closed = true
	close(s.readyChannel)
	close(s.messageChannel)
}

// Message returns a channel for receiving message event.
// A nil message indicates the channel is closed.
// The channel should be used from a separated goroutine.
// For example:
//
//    for message := range subs.Message() {
//       if message == nil {
//           break
//       }
//       ...
//    }
func (s *Subscriptions) Message() chan *Message {
	return s.messageChannel
}

func (s *Subscriptions) receive() {
	for {
		if s.closed {
			break
		}
		for !s.ready {
			<-s.readyChannel
			if s.closed {
				break
			}
		}
		for {
			rep, err := readReply(s.conn)
			if err != nil {
				if s.throwError {
					s.messageChannel <- nil
					return
				}
				s.ready = false
				go s.resubscribe()
				break
			}
			if !rep.IsArray() {
				continue
			}
			replies, _ := rep.Array()
			if len(replies) < 3 || !replies[0].IsString() || !replies[1].IsString() || !replies[2].IsString() {
				continue
			}
			switch string(replies[0].stringValue) {
			case "message":
				s.messageChannel <- &Message{
					Type:            "message",
					Channel:         string(replies[1].stringValue),
					OriginalChannel: string(replies[1].stringValue),
					Message:         replies[2].stringValue,
				}
			case "pmessage":
				s.messageChannel <- &Message{
					Type:            "pmessage",
					Channel:         string(replies[1].stringValue),
					OriginalChannel: string(replies[2].stringValue),
					Message:         replies[3].stringValue,
				}
			}
		}

	}
}

func (s *Subscriptions) do(command string, channel ...string) error {
	if len(channel) == 0 {
		return nil
	}
	if s.conn.state != connStateConnected {
		return ErrNotConnected
	}
	s.lock.Lock()
	defer s.lock.Unlock()
	err := NewCommand(command, s.makeArgs(channel...)...).Send(s.conn)
	if err == nil {
		switch {
		case command[0] == 'S':
			for _, ch := range channel {
				s.channels[ch] = struct{}{}
			}
		case command[0] == 'U':
			for _, ch := range channel {
				delete(s.channels, ch)
			}
		case command[1] == 'S':
			for _, ch := range channel {
				s.pchannels[ch] = struct{}{}
			}
		default:
			for _, ch := range channel {
				delete(s.pchannels, ch)
			}
		}
	}
	return err
}

func (s *Subscriptions) makeArgs(channel ...string) []interface{} {
	args := make([]interface{}, len(channel))
	for i := range channel {
		args[i] = channel[i]
	}
	return args
}

func (s *Subscriptions) resubscribe() {
	s.lock.Lock()
	defer s.lock.Unlock()
	for {
		if s.conn.state != connStateConnected {
			time.Sleep(2 * time.Second)
			continue
		}
		channels := []string{}
		for ch := range s.channels {
			channels = append(channels, ch)
		}
		pchannels := []string{}
		for ch := range s.pchannels {
			pchannels = append(pchannels, ch)
		}
		err := NewCommand("SUBSCRIBE", s.makeArgs(channels...)...).Send(s.conn)
		if err != nil {
			continue
		}
		err = NewCommand("PSUBSCRIBE", s.makeArgs(pchannels...)...).Send(s.conn)
		if err != nil {
			continue
		}
		break
	}
	s.ready = true
	s.readyChannel <- true
}
<commit_msg>Fixing data race on subscription closure<commit_after>package gore

import (
	"sync"
	"time"
)

// Message is a nofitication from a subscribed channel or pchannel
type Message struct {
	// message or pmessage
	Type string
	// The channel/pchannel the client subscribed to. For example: "test", "te*"
	Channel string
	// The channel that publisher published to. For example "test", "text"
	OriginalChannel string
	// The payload
	Message []byte
}

// Publish a message to a channel over conn
func Publish(conn *Conn, channel string, message interface{}) error {
	_, err := NewCommand("PUBLISH", channel, message).Run(conn)
	return err
}

// Subscriptions keeps all SUBSCRIBE and PSUBSCRIBE channels, and handles
// all errors and re-subcription process when the connection is down then reconnected.
// A dedicated connection should be used for Subscriptions
type Subscriptions struct {
	channels       map[string]struct{}
	pchannels      map[string]struct{}
	conn           *Conn
	closed         bool
	messageChannel chan *Message
	lock           sync.RWMutex
	ready          bool
	readyChannel   chan bool
	// Sentinel will set this to true to handle read error from sentinel server.
	throwError bool
}

// NewSubscriptions returns new Subscriptions
func NewSubscriptions(conn *Conn) *Subscriptions {
	s := &Subscriptions{
		channels:       make(map[string]struct{}),
		pchannels:      make(map[string]struct{}),
		conn:           conn,
		messageChannel: make(chan *Message, 100),
		ready:          true,
		readyChannel:   make(chan bool, 1),
		throwError:     false,
	}
	go s.receive()
	return s
}

// Subscribe subscribes to a list of channels
func (s *Subscriptions) Subscribe(channel ...string) error {
	return s.do("SUBSCRIBE", channel...)
}

// PSubscribe subscribes to a list of channels with given pattern
func (s *Subscriptions) PSubscribe(channel ...string) error {
	return s.do("PSUBSCRIBE", channel...)
}

// Unsubscribe unsubscribes to a list of channels
func (s *Subscriptions) Unsubscribe(channel ...string) error {
	return s.do("UNSUBSCRIBE", channel...)
}

// PUnsubscribe unsubscribes to a list of channels with given pattern
func (s *Subscriptions) PUnsubscribe(channel ...string) error {
	return s.do("PUNSUBSCRIBE", channel...)
}

// Close terminates the subscriptions.
// The connection is NOT closed. You should close it if you do not want to
// use anymore.
func (s *Subscriptions) Close() {
	s.lock.Lock()
	defer s.lock.Unlock()
	if s.closed {
		return
	}
	s.closed = true
	close(s.readyChannel)
	close(s.messageChannel)
}

// IsClosed returns true if the subscription is no longer available
func (s *Subscriptions) IsClosed() bool {
	s.lock.RLock()
	defer s.lock.RUnlock()
	return s.closed
}

// Message returns a channel for receiving message event.
// A nil message indicates the channel is closed.
// The channel should be used from a separated goroutine.
// For example:
//
//    for message := range subs.Message() {
//       if message == nil {
//           break
//       }
//       ...
//    }
func (s *Subscriptions) Message() chan *Message {
	return s.messageChannel
}

func (s *Subscriptions) receive() {
	for {
		if s.IsClosed() {
			break
		}
		for !s.ready {
			<-s.readyChannel
			if s.IsClosed() {
				break
			}
		}
		for {
			rep, err := readReply(s.conn)
			if err != nil {
				if s.throwError {
					s.messageChannel <- nil
					return
				}
				s.ready = false
				go s.resubscribe()
				break
			}
			if !rep.IsArray() {
				continue
			}
			replies, _ := rep.Array()
			if len(replies) < 3 || !replies[0].IsString() || !replies[1].IsString() || !replies[2].IsString() {
				continue
			}
			switch string(replies[0].stringValue) {
			case "message":
				s.messageChannel <- &Message{
					Type:            "message",
					Channel:         string(replies[1].stringValue),
					OriginalChannel: string(replies[1].stringValue),
					Message:         replies[2].stringValue,
				}
			case "pmessage":
				s.messageChannel <- &Message{
					Type:            "pmessage",
					Channel:         string(replies[1].stringValue),
					OriginalChannel: string(replies[2].stringValue),
					Message:         replies[3].stringValue,
				}
			}
		}

	}
}

func (s *Subscriptions) do(command string, channel ...string) error {
	if len(channel) == 0 {
		return nil
	}
	if s.conn.state != connStateConnected {
		return ErrNotConnected
	}
	s.lock.Lock()
	defer s.lock.Unlock()
	err := NewCommand(command, s.makeArgs(channel...)...).Send(s.conn)
	if err == nil {
		switch {
		case command[0] == 'S':
			for _, ch := range channel {
				s.channels[ch] = struct{}{}
			}
		case command[0] == 'U':
			for _, ch := range channel {
				delete(s.channels, ch)
			}
		case command[1] == 'S':
			for _, ch := range channel {
				s.pchannels[ch] = struct{}{}
			}
		default:
			for _, ch := range channel {
				delete(s.pchannels, ch)
			}
		}
	}
	return err
}

func (s *Subscriptions) makeArgs(channel ...string) []interface{} {
	args := make([]interface{}, len(channel))
	for i := range channel {
		args[i] = channel[i]
	}
	return args
}

func (s *Subscriptions) resubscribe() {
	s.lock.Lock()
	defer s.lock.Unlock()
	for {
		if s.conn.state != connStateConnected {
			time.Sleep(2 * time.Second)
			continue
		}
		channels := []string{}
		for ch := range s.channels {
			channels = append(channels, ch)
		}
		pchannels := []string{}
		for ch := range s.pchannels {
			pchannels = append(pchannels, ch)
		}
		err := NewCommand("SUBSCRIBE", s.makeArgs(channels...)...).Send(s.conn)
		if err != nil {
			continue
		}
		err = NewCommand("PSUBSCRIBE", s.makeArgs(pchannels...)...).Send(s.conn)
		if err != nil {
			continue
		}
		break
	}
	s.ready = true
	s.readyChannel <- true
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/go-martini/martini"
	"github.com/martini-contrib/cors"
	"github.com/soveran/redisurl"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"text/template"
	"time"
)

// Expire caches in an hour
const CACHE_TTL = 60 * 60

type Server struct {
	Redis   *redis.Conn
	Martini *martini.ClassicMartini
}

type Score struct {
	TotalScore     float32              `json:"total_score"`
	Breakdown      map[string]float32   `json:"breakdown"`
	HumanBreakdown map[string][]float32 `json:"human_breakdown"`
}

type ScoreResponse struct {
	Score     float32            `json:"score"`
	URL       string             `json:"url"`
	Breakdown map[string]float32 `json:"breakdown"`
}

type HumanScoreResponse struct {
	Score     float32              `json:"score"`
	URL       string               `json:"url"`
	Breakdown map[string][]float32 `json:"breakdown"`
}

type ErrorResponse struct {
	Error string `json:"error"`
}

func MarshalToJsonBytes(res interface{}) []byte {
	resAsJson, _ := json.Marshal(res)
	return ([]byte(resAsJson))
}

func GetScoreResponseAsJson(score Score, url_or_slug string) []byte {
	var res interface{}

	if score.Breakdown != nil {
		res = &ScoreResponse{
			Score:     score.TotalScore,
			Breakdown: score.Breakdown,
			URL:       url_or_slug}
	} else {
		res = &HumanScoreResponse{
			Score:     score.TotalScore,
			Breakdown: score.HumanBreakdown,
			URL:       url_or_slug}
	}

	return MarshalToJsonBytes(res)
}

func (score Score) AsColor() string {
	if score.TotalScore < 25 {
		return "#E74C3C"
	}
	if score.TotalScore < 80 {
		return "#F39C12"
	}
	return "#2ECC71"
}

var score_template_string = ""
var score_template = template.New("score template")

func GetScoreResponseAsSVG(score Score, url_or_slug string) []byte {
	var doc bytes.Buffer
	var err error

	if score_template_string == "" {
		var score_template_bytes []byte
		if score_template_bytes, err = ioutil.ReadFile("./templates/score.svg"); err == nil {
			score_template_string = string(score_template_bytes)
		}
		score_template, err = score_template.Parse(score_template_string)
	}

	if err == nil {
		err = score_template.Execute(&doc, score)
	}
	HandleError(err)

	return doc.Bytes()
}

var error_template = ""

func GetScoreErrorAsSVG() []byte {
	if error_template == "" {
		if error_template_bytes, err := ioutil.ReadFile("./templates/error.svg"); err != nil {
			HandleError(err)
		} else {
			error_template = string(error_template_bytes)
		}
	}

	return ([]byte(error_template))
}

func GetScoreErrorAsJson(url_or_slug string) []byte {
	res := &ErrorResponse{
		Error: "Could not determine score for " + url_or_slug}
	return MarshalToJsonBytes(res)
}

func CacheKeyForUrlOrSlug(url_or_slug string, human_arg string) string {
	return "url_or_slug_v3:" + url_or_slug + ":" + human_arg
}

func WriteSVGWithETag(res http.ResponseWriter, body []byte) {
	hash := md5.New()
	io.WriteString(hash, string(body))
	etag := fmt.Sprintf("%x", hash.Sum(nil))
	res.Header().Set("ETag", etag)
	res.Write(body)
}

func (server *Server) GetScore(res http.ResponseWriter, req *http.Request, params martini.Params) {
	query_params := req.URL.Query()
	url_or_slug := ""
	ok := false
	human_breakdown := false
	format := params["format"]
	if format == "svg" {
		res.Header().Set("Content-Type", "image/svg+xml")
		res.Header().Set("Cache-Control", "no-cache")
	} else if format == "txt" {
		res.Header().Set("Content-Type", "text/plain")
	} else {
		res.Header().Set("Content-Type", "application/json")
	}
	var param_matches []string
	var score *Score
	var err error

	if param_matches, ok = query_params["url"]; !ok {
		param_matches = query_params["github"]
	}
	if len(param_matches) == 0 {
		err = errors.New("No value for :url or :github query parameter")
	} else {
		url_or_slug = param_matches[0]

		if param_matches, ok = query_params["human_breakdown"]; ok {
			human_breakdown = param_matches[0] == "true"
		}

		score, err = server.GetScoreForUrlOrSlug(url_or_slug, human_breakdown)
	}
	HandleError(err)

	if score == nil {
		if format == "svg" {
			WriteSVGWithETag(res, GetScoreErrorAsSVG())
		} else if format == "txt" {
			res.Write([]byte("error"))
		} else {
			res.Write(GetScoreErrorAsJson(url_or_slug))
		}
	} else {
		if format == "svg" {
			WriteSVGWithETag(res, GetScoreResponseAsSVG(*score, url_or_slug))
		} else if format == "txt" {
			res.Write([]byte(strconv.Itoa(int(score.TotalScore))))
		} else {
			res.Write(GetScoreResponseAsJson(*score, url_or_slug))
		}
	}

}

func (server *Server) GetCachedScoreForUrlOrSlug(url_or_slug string, human_arg string) (*Score, error) {
	var score *Score
	scoreJson, err := redis.String((*server.Redis).Do("GET", CacheKeyForUrlOrSlug(url_or_slug, human_arg)))
	if scoreJson != "" {
		score = &Score{}
		if err = json.Unmarshal([]byte(scoreJson), &score); err != nil {
			score = nil
		}
	}

	return score, err
}

func (server *Server) CacheScoreForUrlOrSlug(scoreJson string, url_or_slug string, human_arg string) {
	(*server.Redis).Do("SET", CacheKeyForUrlOrSlug(url_or_slug, human_arg), scoreJson)
	(*server.Redis).Do("EXPIRE", CacheKeyForUrlOrSlug(url_or_slug, human_arg), CACHE_TTL)
}

func (server *Server) GetScoreForUrlOrSlug(url_or_slug string, human_breakdown bool) (*Score, error) {
	var score *Score
	var err error
	humanArg := "false"
	if human_breakdown {
		humanArg = "true"
	}
	if score, err = server.GetCachedScoreForUrlOrSlug(url_or_slug, humanArg); err != nil {
		rubyCmd := exec.Command("./get_score.rb", url_or_slug, humanArg)
		var scoreOut []byte
		if scoreOut, err = rubyCmd.Output(); err == nil {
			lines := strings.Split(string(scoreOut), "\n")
			scoreJson := lines[len(lines)-2]
			server.CacheScoreForUrlOrSlug(scoreJson, url_or_slug, humanArg)
			score = &Score{}
			if err = json.Unmarshal([]byte(scoreJson), &score); err != nil {
				score = nil
			}
		}
	}

	return score, err
}

func ConnectRedis(redisChannel chan redis.Conn) {
	redisAddress := os.Getenv("REDIS_URL")
	if redisAddress == "" {
		redisAddress = os.Getenv("REDISCLOUD_URL")
		if redisAddress == "" {
			redisAddress = "redis://localhost:6379"
		}
	}

	connection, redisError := redisurl.ConnectToURL(redisAddress)
	if connection != nil {
		redisChannel <- connection
	} else if redisError != nil {
		fmt.Println(redisError)
	} else {
		fmt.Println("Everyting was nil?")
	}
}

func CreateServer(server *Server) {
	m := martini.Classic()
	m.Use(cors.Allow(&cors.Options{
		AllowOrigins: []string{"*"},
		AllowMethods: []string{"GET"},
		AllowHeaders: []string{"Origin"},
	}))
	m.Get("/score(\\.(?P<format>json|html|svg|txt))?", server.GetScore)
	server.Martini = m
	m.Run()
}

func Start(redisChannel chan redis.Conn, retryCount int, server *Server) {
	go ConnectRedis(redisChannel)
	select {
	case redisConnection := <-redisChannel:
		server.Redis = &redisConnection
		fmt.Println("Redis connected")
		defer redisConnection.Close()
		CreateServer(server)
	case <-time.After(time.Second * 1):
		retryCount += 1
		if retryCount < 5 {
			fmt.Println("Retrying Redis connection")
			Start(redisChannel, retryCount, server)
		} else {
			panic("Something is going wrong")
		}
	}

}

func main() {
	redisChannel := make(chan redis.Conn, 1)
	retryCount := 0
	server := Server{}
	Start(redisChannel, retryCount, &server)
}
<commit_msg>support force query parameter<commit_after>package main

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/go-martini/martini"
	"github.com/martini-contrib/cors"
	"github.com/soveran/redisurl"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"text/template"
	"time"
)

// Expire caches in an hour
const CACHE_TTL = 60 * 60

type Server struct {
	Redis   *redis.Conn
	Martini *martini.ClassicMartini
}

type Score struct {
	TotalScore     float32              `json:"total_score"`
	Breakdown      map[string]float32   `json:"breakdown"`
	HumanBreakdown map[string][]float32 `json:"human_breakdown"`
}

type ScoreResponse struct {
	Score     float32            `json:"score"`
	URL       string             `json:"url"`
	Breakdown map[string]float32 `json:"breakdown"`
}

type HumanScoreResponse struct {
	Score     float32              `json:"score"`
	URL       string               `json:"url"`
	Breakdown map[string][]float32 `json:"breakdown"`
}

type ErrorResponse struct {
	Error string `json:"error"`
}

func MarshalToJsonBytes(res interface{}) []byte {
	resAsJson, _ := json.Marshal(res)
	return ([]byte(resAsJson))
}

func GetScoreResponseAsJson(score Score, url_or_slug string) []byte {
	var res interface{}

	if score.Breakdown != nil {
		res = &ScoreResponse{
			Score:     score.TotalScore,
			Breakdown: score.Breakdown,
			URL:       url_or_slug}
	} else {
		res = &HumanScoreResponse{
			Score:     score.TotalScore,
			Breakdown: score.HumanBreakdown,
			URL:       url_or_slug}
	}

	return MarshalToJsonBytes(res)
}

func (score Score) AsColor() string {
	if score.TotalScore < 25 {
		return "#E74C3C"
	}
	if score.TotalScore < 80 {
		return "#F39C12"
	}
	return "#2ECC71"
}

var score_template_string = ""
var score_template = template.New("score template")

func GetScoreResponseAsSVG(score Score, url_or_slug string) []byte {
	var doc bytes.Buffer
	var err error

	if score_template_string == "" {
		var score_template_bytes []byte
		if score_template_bytes, err = ioutil.ReadFile("./templates/score.svg"); err == nil {
			score_template_string = string(score_template_bytes)
		}
		score_template, err = score_template.Parse(score_template_string)
	}

	if err == nil {
		err = score_template.Execute(&doc, score)
	}
	HandleError(err)

	return doc.Bytes()
}

var error_template = ""

func GetScoreErrorAsSVG() []byte {
	if error_template == "" {
		if error_template_bytes, err := ioutil.ReadFile("./templates/error.svg"); err != nil {
			HandleError(err)
		} else {
			error_template = string(error_template_bytes)
		}
	}

	return ([]byte(error_template))
}

func GetScoreErrorAsJson(url_or_slug string) []byte {
	res := &ErrorResponse{
		Error: "Could not determine score for " + url_or_slug}
	return MarshalToJsonBytes(res)
}

func CacheKeyForUrlOrSlug(url_or_slug string, human_arg string) string {
	return "url_or_slug_v3:" + url_or_slug + ":" + human_arg
}

func WriteSVGWithETag(res http.ResponseWriter, body []byte) {
	hash := md5.New()
	io.WriteString(hash, string(body))
	etag := fmt.Sprintf("%x", hash.Sum(nil))
	res.Header().Set("ETag", etag)
	res.Write(body)
}

func (server *Server) GetScore(res http.ResponseWriter, req *http.Request, params martini.Params) {
	query_params := req.URL.Query()
	url_or_slug := ""
	ok := false
	human_breakdown := false
	hit_cache := true
	format := params["format"]
	if format == "svg" {
		res.Header().Set("Content-Type", "image/svg+xml")
		res.Header().Set("Cache-Control", "no-cache")
	} else if format == "txt" {
		res.Header().Set("Content-Type", "text/plain")
	} else {
		res.Header().Set("Content-Type", "application/json")
	}
	var param_matches []string
	var score *Score
	var err error

	if param_matches, ok = query_params["url"]; !ok {
		param_matches = query_params["github"]
	}
	if len(param_matches) == 0 {
		err = errors.New("No value for :url or :github query parameter")
	} else {
		url_or_slug = param_matches[0]

		if param_matches, ok = query_params["human_breakdown"]; ok {
			human_breakdown = param_matches[0] == "true"
		}

		if param_matches, ok = query_params["force"]; ok {
			hit_cache = false
		}

		score, err = server.GetScoreForUrlOrSlug(url_or_slug, human_breakdown, hit_cache)
	}
	HandleError(err)

	if score == nil {
		if format == "svg" {
			WriteSVGWithETag(res, GetScoreErrorAsSVG())
		} else if format == "txt" {
			res.Write([]byte("error"))
		} else {
			res.Write(GetScoreErrorAsJson(url_or_slug))
		}
	} else {
		if format == "svg" {
			WriteSVGWithETag(res, GetScoreResponseAsSVG(*score, url_or_slug))
		} else if format == "txt" {
			res.Write([]byte(strconv.Itoa(int(score.TotalScore))))
		} else {
			res.Write(GetScoreResponseAsJson(*score, url_or_slug))
		}
	}

}

func (server *Server) GetCachedScoreForUrlOrSlug(url_or_slug string, human_arg string) (*Score, error) {
	var score *Score
	scoreJson, err := redis.String((*server.Redis).Do("GET", CacheKeyForUrlOrSlug(url_or_slug, human_arg)))
	if scoreJson != "" {
		score = &Score{}
		if err = json.Unmarshal([]byte(scoreJson), &score); err != nil {
			score = nil
		}
	}

	return score, err
}

func (server *Server) CacheScoreForUrlOrSlug(scoreJson string, url_or_slug string, human_arg string) {
	(*server.Redis).Do("SET", CacheKeyForUrlOrSlug(url_or_slug, human_arg), scoreJson)
	(*server.Redis).Do("EXPIRE", CacheKeyForUrlOrSlug(url_or_slug, human_arg), CACHE_TTL)
}

func (server *Server) GetScoreForUrlOrSlug(url_or_slug string, human_breakdown bool, hit_cache bool) (*Score, error) {
	var score *Score
	var err error
	humanArg := "false"
	if human_breakdown {
		humanArg = "true"
	}
	if score, err = server.GetCachedScoreForUrlOrSlug(url_or_slug, humanArg); err != nil || !hit_cache {
		rubyCmd := exec.Command("./get_score.rb", url_or_slug, humanArg)
		var scoreOut []byte
		if scoreOut, err = rubyCmd.Output(); err == nil {
			lines := strings.Split(string(scoreOut), "\n")
			scoreJson := lines[len(lines)-2]
			server.CacheScoreForUrlOrSlug(scoreJson, url_or_slug, humanArg)
			score = &Score{}
			if err = json.Unmarshal([]byte(scoreJson), &score); err != nil {
				score = nil
			}
		}
	}

	return score, err
}

func ConnectRedis(redisChannel chan redis.Conn) {
	redisAddress := os.Getenv("REDIS_URL")
	if redisAddress == "" {
		redisAddress = os.Getenv("REDISCLOUD_URL")
		if redisAddress == "" {
			redisAddress = "redis://localhost:6379"
		}
	}

	connection, redisError := redisurl.ConnectToURL(redisAddress)
	if connection != nil {
		redisChannel <- connection
	} else if redisError != nil {
		fmt.Println(redisError)
	} else {
		fmt.Println("Everyting was nil?")
	}
}

func CreateServer(server *Server) {
	m := martini.Classic()
	m.Use(cors.Allow(&cors.Options{
		AllowOrigins: []string{"*"},
		AllowMethods: []string{"GET"},
		AllowHeaders: []string{"Origin"},
	}))
	m.Get("/score(\\.(?P<format>json|html|svg|txt))?", server.GetScore)
	server.Martini = m
	m.Run()
}

func Start(redisChannel chan redis.Conn, retryCount int, server *Server) {
	go ConnectRedis(redisChannel)
	select {
	case redisConnection := <-redisChannel:
		server.Redis = &redisConnection
		fmt.Println("Redis connected")
		defer redisConnection.Close()
		CreateServer(server)
	case <-time.After(time.Second * 1):
		retryCount += 1
		if retryCount < 5 {
			fmt.Println("Retrying Redis connection")
			Start(redisChannel, retryCount, server)
		} else {
			panic("Something is going wrong")
		}
	}

}

func main() {
	redisChannel := make(chan redis.Conn, 1)
	retryCount := 0
	server := Server{}
	Start(redisChannel, retryCount, &server)
}
<|endoftext|>


<commit_before>//go:generate go-bindata -pkg realtime -o realtime_embed.go realtime.js

package realtime

import (
	"fmt"
	"net/http"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

var proto = "v1"

type Config struct {
	Throttle time.Duration
}

type Handler struct {
	config   Config
	upgrader websocket.Upgrader
	ws       http.Handler
	mut      sync.Mutex //protects object and user maps
	objs     map[key]*Object
	users    map[string]*User

	watchingUsers bool
	userEvents    chan *User
}

func NewHandler() *Handler {
	return NewHandlerConfig(Config{})
}

func NewHandlerConfig(c Config) *Handler {
	if c.Throttle < 15*time.Millisecond {
		//15ms is approximately highest resolution on the JS eventloop
		c.Throttle = 200 * time.Millisecond
	}
	r := &Handler{config: c}

	r.upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
	}
	r.objs = map[key]*Object{}
	r.users = map[string]*User{}
	r.userEvents = make(chan *User)
	//continually batches and sends updates
	go r.flusher()
	return r
}

func (r *Handler) UserEvents() <-chan *User {
	if r.watchingUsers {
		panic("Already watching user changes")
	}
	r.watchingUsers = true
	return r.userEvents
}

func (r *Handler) flusher() {
	//loops at Throttle speed
	for {
		//compute updates for each object for each subscriber
		//and append each update to the users pending updates
		for _, o := range r.objs {
			o.computeUpdate()
		}
		//send all pending updates
		r.mut.Lock()
		for _, u := range r.users {
			u.sendPending()
		}
		r.mut.Unlock()
		time.Sleep(r.config.Throttle)
	}
}

type addable interface {
	add(key string, val interface{}) (*Object, error)
}

func (r *Handler) MustAdd(k string, v interface{}) {
	if err := r.Add(k, v); err != nil {
		panic(err)
	}
}

func (r *Handler) Add(k string, v interface{}) error {
	r.mut.Lock()
	defer r.mut.Unlock()

	t := reflect.TypeOf(v)
	if t.Kind() != reflect.Ptr {
		return fmt.Errorf("Cannot add '%s' - it is not a pointer type", k)
	}

	if _, ok := r.objs[key(k)]; ok {
		return fmt.Errorf("Cannot add '%s' - already exists", k)
	}
	//access v.object via interfaces:
	a, ok := v.(addable)
	if !ok {
		return fmt.Errorf("Cannot add '%s' - does not embed realtime.Object", k)
	}
	//pass v into v.object and get v.object back out
	o, err := a.add(k, v)
	if err != nil {
		return fmt.Errorf("Cannot add '%s' %s", k, err)
	}
	r.objs[key(k)] = o
	return nil
}

func (r *Handler) UpdateAll() {
	r.mut.Lock()
	for _, obj := range r.objs {
		obj.checked = false
	}
	r.mut.Unlock()
}

func (r *Handler) Update(k string) {
	r.mut.Lock()
	if obj, ok := r.objs[key(k)]; ok {
		obj.checked = false
	}
	r.mut.Unlock()
}

func (r *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if req.Header.Get("Upgrade") == "websocket" {
		conn, err := r.upgrader.Upgrade(w, req, nil)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		r.serveWS(conn)
	} else if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
		JS.ServeHTTP(w, req) //gziped realtime.js script file
	} else {
		http.NotFound(w, req)
	}
}

func (r *Handler) serveWS(conn *websocket.Conn) {
	handshake := struct {
		Protocol       string
		ObjectVersions objectVersions
	}{}

	//first message is the rt handshake
	if err := conn.ReadJSON(&handshake); err != nil {
		conn.WriteMessage(websocket.TextMessage, []byte("Invalid rt handshake"))
		conn.Close()
		return
	}
	if handshake.Protocol != proto {
		conn.WriteMessage(websocket.TextMessage, []byte("Invalid rt protocol version"))
		conn.Close()
		return
	}
	//ready
	u := &User{
		ID:        conn.RemoteAddr().String(),
		Connected: true,
		uptime:    time.Now(),
		conn:      conn,
		versions:  handshake.ObjectVersions,
		pending:   []*update{},
	}

	//add user and subscribe to each obj
	r.mut.Lock()
	for k := range u.versions {
		if _, ok := r.objs[k]; !ok {
			conn.WriteMessage(websocket.TextMessage, []byte("missing object: "+k))
			r.mut.Unlock()
			return
		}
	}
	r.users[u.ID] = u
	if r.watchingUsers {
		r.userEvents <- u
	}
	for k := range u.versions {
		obj := r.objs[k]
		obj.subscribers[u.ID] = u
		//create initial update
		u.pending = append(u.pending, &update{
			Key: k, Version: obj.version, Data: obj.bytes,
		})
		obj.Update()
	}
	r.mut.Unlock()

	//loop here during connection
	for {
		//msgType, msgBytes, err
		_, _, err := conn.ReadMessage()
		if err != nil {
			break
		}
		//TODO(jpillora): allow changing of subscriptions
	}
	conn.Close() //ensure closed
	u.Connected = false
	//remove user and unsubscribe to each obj
	r.mut.Lock()
	delete(r.users, u.ID)
	if r.watchingUsers {
		r.userEvents <- u
	}
	for k := range u.versions {
		obj := r.objs[k]
		delete(obj.subscribers, u.ID)
	}
	r.mut.Unlock()
	//disconnected
}

//embedded JS file
var JSBytes = _realtimeJs

type jsServe []byte

var JS = jsServe(JSBytes)

func (j jsServe) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Encoding", "gzip")
	w.Header().Set("Content-Type", "text/javascript")
	w.Header().Set("Content-Length", strconv.Itoa(len(JSBytes)))
	w.Write(JSBytes)
}
<commit_msg>Disable origin check by default<commit_after>//go:generate go-bindata -pkg realtime -o realtime_embed.go realtime.js

package realtime

import (
	"fmt"
	"net/http"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

var proto = "v1"

type Config struct {
	Throttle    time.Duration
	CheckOrigin bool
}

type Handler struct {
	config   Config
	upgrader websocket.Upgrader
	ws       http.Handler
	mut      sync.Mutex //protects object and user maps
	objs     map[key]*Object
	users    map[string]*User

	watchingUsers bool
	userEvents    chan *User
}

func NewHandler() *Handler {
	return NewHandlerConfig(Config{})
}

func NewHandlerConfig(c Config) *Handler {
	if c.Throttle < 15*time.Millisecond {
		//15ms is approximately highest resolution on the JS eventloop
		c.Throttle = 200 * time.Millisecond
	}
	r := &Handler{config: c}

	r.upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
	}
	if !c.CheckOrigin {
		r.upgrader.CheckOrigin = func(r *http.Request) bool {
			return true
		}
	}
	r.objs = map[key]*Object{}
	r.users = map[string]*User{}
	r.userEvents = make(chan *User)
	//continually batches and sends updates
	go r.flusher()
	return r
}

func (r *Handler) UserEvents() <-chan *User {
	if r.watchingUsers {
		panic("Already watching user changes")
	}
	r.watchingUsers = true
	return r.userEvents
}

func (r *Handler) flusher() {
	//loops at Throttle speed
	for {
		//compute updates for each object for each subscriber
		//and append each update to the users pending updates
		for _, o := range r.objs {
			o.computeUpdate()
		}
		//send all pending updates
		r.mut.Lock()
		for _, u := range r.users {
			u.sendPending()
		}
		r.mut.Unlock()
		time.Sleep(r.config.Throttle)
	}
}

type addable interface {
	add(key string, val interface{}) (*Object, error)
}

func (r *Handler) MustAdd(k string, v interface{}) {
	if err := r.Add(k, v); err != nil {
		panic(err)
	}
}

func (r *Handler) Add(k string, v interface{}) error {
	r.mut.Lock()
	defer r.mut.Unlock()

	t := reflect.TypeOf(v)
	if t.Kind() != reflect.Ptr {
		return fmt.Errorf("Cannot add '%s' - it is not a pointer type", k)
	}

	if _, ok := r.objs[key(k)]; ok {
		return fmt.Errorf("Cannot add '%s' - already exists", k)
	}
	//access v.object via interfaces:
	a, ok := v.(addable)
	if !ok {
		return fmt.Errorf("Cannot add '%s' - does not embed realtime.Object", k)
	}
	//pass v into v.object and get v.object back out
	o, err := a.add(k, v)
	if err != nil {
		return fmt.Errorf("Cannot add '%s' %s", k, err)
	}
	r.objs[key(k)] = o
	return nil
}

func (r *Handler) UpdateAll() {
	r.mut.Lock()
	for _, obj := range r.objs {
		obj.checked = false
	}
	r.mut.Unlock()
}

func (r *Handler) Update(k string) {
	r.mut.Lock()
	if obj, ok := r.objs[key(k)]; ok {
		obj.checked = false
	}
	r.mut.Unlock()
}

func (r *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if req.Header.Get("Upgrade") == "websocket" {
		conn, err := r.upgrader.Upgrade(w, req, nil)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		r.serveWS(conn)
	} else if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
		JS.ServeHTTP(w, req) //gziped realtime.js script file
	} else {
		http.NotFound(w, req)
	}
}

func (r *Handler) serveWS(conn *websocket.Conn) {
	handshake := struct {
		Protocol       string
		ObjectVersions objectVersions
	}{}

	//first message is the rt handshake
	if err := conn.ReadJSON(&handshake); err != nil {
		conn.WriteMessage(websocket.TextMessage, []byte("Invalid rt handshake"))
		conn.Close()
		return
	}
	if handshake.Protocol != proto {
		conn.WriteMessage(websocket.TextMessage, []byte("Invalid rt protocol version"))
		conn.Close()
		return
	}
	//ready
	u := &User{
		ID:        conn.RemoteAddr().String(),
		Connected: true,
		uptime:    time.Now(),
		conn:      conn,
		versions:  handshake.ObjectVersions,
		pending:   []*update{},
	}

	//add user and subscribe to each obj
	r.mut.Lock()
	for k := range u.versions {
		if _, ok := r.objs[k]; !ok {
			conn.WriteMessage(websocket.TextMessage, []byte("missing object: "+k))
			r.mut.Unlock()
			return
		}
	}
	r.users[u.ID] = u
	if r.watchingUsers {
		r.userEvents <- u
	}
	for k := range u.versions {
		obj := r.objs[k]
		obj.subscribers[u.ID] = u
		//create initial update
		u.pending = append(u.pending, &update{
			Key: k, Version: obj.version, Data: obj.bytes,
		})
		obj.Update()
	}
	r.mut.Unlock()

	//loop here during connection
	for {
		//msgType, msgBytes, err
		_, _, err := conn.ReadMessage()
		if err != nil {
			break
		}
		//TODO(jpillora): allow changing of subscriptions
	}
	conn.Close() //ensure closed
	u.Connected = false
	//remove user and unsubscribe to each obj
	r.mut.Lock()
	delete(r.users, u.ID)
	if r.watchingUsers {
		r.userEvents <- u
	}
	for k := range u.versions {
		obj := r.objs[k]
		delete(obj.subscribers, u.ID)
	}
	r.mut.Unlock()
	//disconnected
}

//embedded JS file
var JSBytes = _realtimeJs

type jsServe []byte

var JS = jsServe(JSBytes)

func (j jsServe) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Encoding", "gzip")
	w.Header().Set("Content-Type", "text/javascript")
	w.Header().Set("Content-Length", strconv.Itoa(len(JSBytes)))
	w.Write(JSBytes)
}
<|endoftext|>


<commit_before>package redis2

import (
	"bufio"
	"errors"
	"log"
	"net"
	"sync"
	"time"

	"github.com/gallir/smart-relayer/lib"
	"github.com/gallir/smart-relayer/redis/pool"
	"github.com/mediocregopher/radix.v2/redis"
)

// Server is the thread that listen for clients' connections
type Server struct {
	sync.Mutex
	config   lib.RelayerConfig
	pool     *pool.Pool
	mode     int
	done     chan bool
	listener net.Listener
}

const (
	listenTimeout     = 15
	connectionRetries = 3
	requestBufferSize = 1024
	connectTimeout    = 5 * time.Second
	serverReadTimeout = 5 * time.Second
	writeTimeout      = 5 * time.Second
	maxIdle           = 10 * time.Second
	responseTimeout   = 20 * time.Second
	localReadTimeout  = 600 * time.Second
	selectCommand     = "SELECT"
)

var (
	errBadCmd      = errors.New("ERR bad command")
	errKO          = errors.New("fatal error")
	respOK         = redis.NewRespSimple("OK")
	respTrue       = redis.NewResp(1)
	respBadCommand = redis.NewResp(errBadCmd)
	respKO         = redis.NewResp(errKO)
	commands       map[string]*redis.Resp
)

func init() {
	// These are the commands that can be sent in "background" when in smart mode
	// The values are the immediate responses to the clients
	commands = map[string]*redis.Resp{
		"SET":       respOK,
		"SETEX":     respOK,
		"PSETEX":    respOK,
		"MSET":      respOK,
		"HMSET":     respOK,
		"SELECT":    respOK,
		"HSET":      respTrue,
		"EXPIRE":    respTrue,
		"EXPIREAT":  respTrue,
		"PEXPIRE":   respTrue,
		"PEXPIREAT": respTrue,
	}
}

// New creates a new Redis local server
func New(c lib.RelayerConfig, done chan bool) (*Server, error) {
	srv := &Server{
		done: done,
	}
	srv.Reload(&c)
	return srv, nil
}

// Start accepts incoming connections on the Listener
func (srv *Server) Start() (e error) {
	srv.Lock()
	defer srv.Unlock()

	srv.listener, e = lib.Listener(&srv.config)
	if e != nil {
		return e
	}

	// Serve clients
	go func() {
		for {
			netConn, e := srv.listener.Accept()
			if e != nil {
				log.Println("Exiting", srv.config.ListenHost())
				return
			}
			go srv.handleConnection(netConn)
		}
	}()

	return nil
}

// Reload the configuration
func (srv *Server) Reload(c *lib.RelayerConfig) error {
	srv.Lock()
	defer srv.Unlock()

	reset := false
	if srv.config.URL != c.URL {
		reset = true
	}
	srv.config = *c
	srv.mode = c.Type()

	if reset {
		if srv.pool != nil {
			log.Printf("Reset redis server at port %s for target %s", srv.config.Listen, srv.config.Host())
			srv.pool.Reset()
		}
		srv.pool = pool.New(c, NewClient)
	} else {
		log.Printf("Reload redis config at port %s for target %s", srv.config.Listen, srv.config.Host())
		srv.pool.ReadConfig(c)
	}

	return nil
}

// Exit closes the listener and send done to main
func (srv *Server) Exit() {
	if srv.listener != nil {
		srv.listener.Close()
	}
	srv.done <- true
}

func (srv *Server) handleConnection(netCon net.Conn) {
	defer netCon.Close()

	conn := bufio.NewReadWriter(bufio.NewReader(netCon), bufio.NewWriter(netCon))
	defer conn.Flush()

	reader := redis.NewRespReader(conn)
	pooled, ok := srv.pool.Get()
	if !ok {
		log.Println("Redis server, no clients available from pool")
		return
	}
	defer srv.pool.Close(pooled)
	client := pooled.Client
	responseCh := make(chan *redis.Resp, 1)
	defer close(responseCh)

	currentDB := 0

	for {
		conn.Flush()
		err := netCon.SetReadDeadline(time.Now().Add(listenTimeout * time.Second))
		if err != nil {
			log.Printf("error setting read deadline: %s", err)
			return
		}

		r := reader.Read()
		if redis.IsTimeout(r) {
			continue
		} else if r.IsType(redis.IOErr) {
			return
		}

		req := newRequest(r, &srv.config)
		if req == nil {
			respBadCommand.WriteTo(conn)
			continue
		}

		if req.database != unknownDB && req.database != currentDB {
			currentDB = req.database
		}
		req.database = currentDB

		// Smart mode, answer immediately and forget
		if srv.mode == lib.ModeSmart {
			fastResponse, ok := commands[string(req.command)]
			if ok {
				fastResponse.WriteTo(conn)
				ok = client.Send(req)
				if !ok {
					log.Printf("Error sending request to redis client, exiting")
					return
				}
				continue
			}
		}

		// Synchronized mode
		req.responseChannel = responseCh

		ok = client.Send(req)
		if !ok {
			log.Printf("Error sending request to redis client, exiting")
			respKO.WriteTo(conn)
			return
		}

		select {
		case response, more := <-responseCh:
			if !more {
				// The client has closed the channel
				lib.Debugf("Redis client has closed channel, exiting")
				return
			}
			response.WriteTo(conn)
		case <-time.After(responseTimeout):
			// Something very wrong happened in the client
			log.Println("Timeout waiting a response, closing client connection")
			respKO.WriteTo(conn)
			return
		}
	}
}

func sendRequest(c chan *Request, r *Request) (ok bool) {
	defer func() {
		e := recover() // To avoid panic due to closed channels
		if e != nil {
			log.Printf("sendRequest: Recovered from error %s, channel length %d", e, len(c))
			ok = false
		}
	}()

	if c == nil {
		lib.Debugf("Nil channel in send request")
		return false
	}

	c <- r
	return true
}

func sendAsyncRequest(c chan *Request, r *Request) (ok bool) {
	defer func() {
		e := recover() // To avoid panic due to closed channels
		if e != nil {
			log.Printf("sendAsyncRequest: Recovered from error %s, channel length %d", e, len(c))
			ok = false
		}
	}()

	if c == nil {
		return
	}

	select {
	case c <- r:
		ok = true
	default:
		lib.Debugf("Error sending request, channel length %d", len(c))
		ok = false
	}
	return
}

func sendAsyncResponse(c chan *redis.Resp, r *redis.Resp) (ok bool) {
	defer func() {
		e := recover() // To avoid panic due to closed channels
		if e != nil {
			log.Printf("AsyncResponseyncRequest: Recovered from error %s, channel length %d", e, len(c))
			ok = false
		}
	}()

	if c == nil {
		return
	}

	select {
	case c <- r:
		ok = true
	default:
		lib.Debugf("Error sending response to channel length %d", len(c))
		ok = false
	}
	return
}
<commit_msg>Not needed<commit_after>package redis2

import (
	"bufio"
	"errors"
	"log"
	"net"
	"sync"
	"time"

	"github.com/gallir/smart-relayer/lib"
	"github.com/gallir/smart-relayer/redis/pool"
	"github.com/mediocregopher/radix.v2/redis"
)

// Server is the thread that listen for clients' connections
type Server struct {
	sync.Mutex
	config   lib.RelayerConfig
	pool     *pool.Pool
	mode     int
	done     chan bool
	listener net.Listener
}

const (
	listenTimeout     = 15
	connectionRetries = 3
	requestBufferSize = 1024
	connectTimeout    = 5 * time.Second
	serverReadTimeout = 5 * time.Second
	writeTimeout      = 5 * time.Second
	maxIdle           = 10 * time.Second
	responseTimeout   = 20 * time.Second
	localReadTimeout  = 600 * time.Second
	selectCommand     = "SELECT"
)

var (
	errBadCmd      = errors.New("ERR bad command")
	errKO          = errors.New("fatal error")
	respOK         = redis.NewRespSimple("OK")
	respTrue       = redis.NewResp(1)
	respBadCommand = redis.NewResp(errBadCmd)
	respKO         = redis.NewResp(errKO)
	commands       map[string]*redis.Resp
)

func init() {
	// These are the commands that can be sent in "background" when in smart mode
	// The values are the immediate responses to the clients
	commands = map[string]*redis.Resp{
		"SET":       respOK,
		"SETEX":     respOK,
		"PSETEX":    respOK,
		"MSET":      respOK,
		"HMSET":     respOK,
		"SELECT":    respOK,
		"HSET":      respTrue,
		"EXPIRE":    respTrue,
		"EXPIREAT":  respTrue,
		"PEXPIRE":   respTrue,
		"PEXPIREAT": respTrue,
	}
}

// New creates a new Redis local server
func New(c lib.RelayerConfig, done chan bool) (*Server, error) {
	srv := &Server{
		done: done,
	}
	srv.Reload(&c)
	return srv, nil
}

// Start accepts incoming connections on the Listener
func (srv *Server) Start() (e error) {
	srv.Lock()
	defer srv.Unlock()

	srv.listener, e = lib.Listener(&srv.config)
	if e != nil {
		return e
	}

	// Serve clients
	go func() {
		for {
			netConn, e := srv.listener.Accept()
			if e != nil {
				log.Println("Exiting", srv.config.ListenHost())
				return
			}
			go srv.handleConnection(netConn)
		}
	}()

	return nil
}

// Reload the configuration
func (srv *Server) Reload(c *lib.RelayerConfig) error {
	srv.Lock()
	defer srv.Unlock()

	reset := false
	if srv.config.URL != c.URL {
		reset = true
	}
	srv.config = *c
	srv.mode = c.Type()

	if reset {
		if srv.pool != nil {
			log.Printf("Reset redis server at port %s for target %s", srv.config.Listen, srv.config.Host())
			srv.pool.Reset()
		}
		srv.pool = pool.New(c, NewClient)
	} else {
		log.Printf("Reload redis config at port %s for target %s", srv.config.Listen, srv.config.Host())
		srv.pool.ReadConfig(c)
	}

	return nil
}

// Exit closes the listener and send done to main
func (srv *Server) Exit() {
	if srv.listener != nil {
		srv.listener.Close()
	}
	srv.done <- true
}

func (srv *Server) handleConnection(netCon net.Conn) {
	defer netCon.Close()

	conn := bufio.NewReadWriter(bufio.NewReader(netCon), bufio.NewWriter(netCon))
	defer conn.Flush()

	reader := redis.NewRespReader(conn)
	pooled, ok := srv.pool.Get()
	if !ok {
		log.Println("Redis server, no clients available from pool")
		return
	}
	defer srv.pool.Close(pooled)
	client := pooled.Client
	responseCh := make(chan *redis.Resp, 1)
	defer close(responseCh)

	currentDB := 0

	for {
		conn.Flush()
		err := netCon.SetReadDeadline(time.Now().Add(listenTimeout * time.Second))
		if err != nil {
			log.Printf("error setting read deadline: %s", err)
			return
		}

		r := reader.Read()
		if redis.IsTimeout(r) {
			continue
		} else if r.IsType(redis.IOErr) {
			return
		}

		req := newRequest(r, &srv.config)
		if req == nil {
			respBadCommand.WriteTo(conn)
			continue
		}

		if req.database != unknownDB && req.database != currentDB {
			currentDB = req.database
		}
		req.database = currentDB

		// Smart mode, answer immediately and forget
		if srv.mode == lib.ModeSmart {
			fastResponse, ok := commands[string(req.command)]
			if ok {
				fastResponse.WriteTo(conn)
				ok = client.Send(req)
				if !ok {
					log.Printf("Error sending request to redis client, exiting")
					return
				}
				continue
			}
		}

		// Synchronized mode
		req.responseChannel = responseCh

		ok = client.Send(req)
		if !ok {
			log.Printf("Error sending request to redis client, exiting")
			respKO.WriteTo(conn)
			return
		}

		select {
		case response, more := <-responseCh:
			if !more {
				// The client has closed the channel
				lib.Debugf("Redis client has closed channel, exiting")
				return
			}
			response.WriteTo(conn)
		case <-time.After(responseTimeout):
			// Something very wrong happened in the client
			log.Println("Timeout waiting a response, closing client connection")
			respKO.WriteTo(conn)
			return
		}
	}
}

func sendRequest(c chan *Request, r *Request) (ok bool) {
	defer func() {
		e := recover() // To avoid panic due to closed channels
		if e != nil {
			log.Printf("sendRequest: Recovered from error %s, channel length %d", e, len(c))
			ok = false
		}
	}()

	if c == nil {
		lib.Debugf("Nil channel in send request")
		return false
	}

	c <- r
	return true
}

func sendAsyncResponse(c chan *redis.Resp, r *redis.Resp) (ok bool) {
	defer func() {
		e := recover() // To avoid panic due to closed channels
		if e != nil {
			log.Printf("AsyncResponseyncRequest: Recovered from error %s, channel length %d", e, len(c))
			ok = false
		}
	}()

	if c == nil {
		return
	}

	select {
	case c <- r:
		ok = true
	default:
		lib.Debugf("Error sending response to channel length %d", len(c))
		ok = false
	}
	return
}
<|endoftext|>


<commit_before>package reflect

import (
	"go/ast"
	"testing"
)

func TestProcessFieldList(t *testing.T) {
	pkg := getPackage(t, `package test
			type Sample struct {
				Something            *something.Cool
				FirstName, LastName  *Name
				GPA                  float64
			}
		`,
	)
	expRes := []Arg{
		{
			Name: "Something",
			Type: &Type{
				Name:    "Cool",
				Package: "something",
				Star:    true,
			},
		},
		{
			Name: "FirstName",
			Type: &Type{
				Name: "Name",
				Star: true,
			},
		},
		{
			Name: "LastName",
			Type: &Type{
				Name: "Name",
				Star: true,
			},
		},
		{
			Name: "GPA",
			Type: &Type{
				Name: "float64",
			},
		},
	}
	f := getFields(t, pkg)

	args := processFieldList(f)
	for i, exp := range expRes {
		if args[i].Name != exp.Name || args[i].Tag != exp.Tag ||
			!deepEqualType(args[i].Type, exp.Type) {

			t.Errorf(
				"Cannot process '%s' field. Expected %#v of type %#v, received: %#v of type %#v.",
				exp.Name, exp, exp.Type, args[i], args[i].Type,
			)
		}
	}
}

func TestProcessField_UnknownType(t *testing.T) {
	pkg := getPackage(t, `package test
			type Sample struct {
				Smth struct {
					Field1 string
					Field2 string
				}
			}
		`,
	)
	f := getFields(t, pkg).List
	args := processField(f[0])
	if len(args) != 0 {
		t.Errorf("Fields of anonymous struct type should be skipped. Instead received %#v.", args)
	}
}

func TestProcessField(t *testing.T) {
	pkg := getPackage(t, `package test
			type Sample struct {
				Something            *something.Cool
				FirstName, LastName  *Name
				GPA                  float64
			}
		`,
	)
	expRes := [][]Arg{
		{
			{
				Name: "Something",
				Type: &Type{
					Name:    "Cool",
					Package: "something",
					Star:    true,
				},
			},
		},
		{
			{
				Name: "FirstName",
				Type: &Type{
					Name: "Name",
					Star: true,
				},
			},
			{
				Name: "LastName",
				Type: &Type{
					Name: "Name",
					Star: true,
				},
			},
		},
		{
			{
				Name: "GPA",
				Type: &Type{
					Name: "float64",
				},
			},
		},
	}

	for i, v := range getFields(t, pkg).List {
		args := processField(v)
		if len(args) == 0 {
			t.Error("Arguments expected, nothing is returned.")
		}
		for j, arg := range args {
			if arg.Name != expRes[i][j].Name || !deepEqualType(arg.Type, expRes[i][j].Type) {
				t.Errorf(
					"Cannot process '%s' field. Expected %#v of type %#v, received: %#v of type %#v.",
					arg.Name, expRes[i][j], expRes[i][j].Type, arg, arg.Type,
				)
			}
		}
	}
}

// getFields is a test function that receives test package file and
// returns a list of fields of the first struct having been found there.
func getFields(t *testing.T, pkg *ast.File) *ast.FieldList {
	decl, ok := pkg.Decls[0].(*ast.GenDecl)
	if !ok {
		t.Error("Incorrect test package, cannot find general declaration.")
	}

	spec, ok := decl.Specs[0].(*ast.TypeSpec)
	if !ok {
		t.Error("Incorrect test package, cannot find spec.")
	}

	s, ok := spec.Type.(*ast.StructType)
	if !ok {
		t.Error("Incorrect test package, cannot find a struct.")
	}

	return s.Fields
}

// deepEqualArg is a function that is used by tests to compare two arguments.
func deepEqualArg(a1, a2 *Arg) bool {
	if a1 == nil || a2 == nil {
		if a1 == a2 {
			return true
		}
		return false
	}
	if a1.Name == a2.Name && a1.Tag == a2.Tag && deepEqualType(a1.Type, a2.Type) {
		return true
	}
	return false
}
<commit_msg>Add one more test for processFieldList<commit_after>package reflect

import (
	"go/ast"
	"testing"
)

func TestProcessFieldList_EmptyInput(t *testing.T) {
	args := processFieldList(nil)
	if len(args) != 0 {
		t.Errorf("Empty arguments list expected, got %#v instead.", args)
	}
}

func TestProcessFieldList(t *testing.T) {
	pkg := getPackage(t, `package test
			type Sample struct {
				Something            *something.Cool
				FirstName, LastName  *Name
				GPA                  float64
			}
		`,
	)
	expRes := []Arg{
		{
			Name: "Something",
			Type: &Type{
				Name:    "Cool",
				Package: "something",
				Star:    true,
			},
		},
		{
			Name: "FirstName",
			Type: &Type{
				Name: "Name",
				Star: true,
			},
		},
		{
			Name: "LastName",
			Type: &Type{
				Name: "Name",
				Star: true,
			},
		},
		{
			Name: "GPA",
			Type: &Type{
				Name: "float64",
			},
		},
	}
	f := getFields(t, pkg)

	args := processFieldList(f)
	for i, exp := range expRes {
		if args[i].Name != exp.Name || args[i].Tag != exp.Tag ||
			!deepEqualType(args[i].Type, exp.Type) {

			t.Errorf(
				"Cannot process '%s' field. Expected %#v of type %#v, received: %#v of type %#v.",
				exp.Name, exp, exp.Type, args[i], args[i].Type,
			)
		}
	}
}

func TestProcessField_UnknownType(t *testing.T) {
	pkg := getPackage(t, `package test
			type Sample struct {
				Smth struct {
					Field1 string
					Field2 string
				}
			}
		`,
	)
	f := getFields(t, pkg).List
	args := processField(f[0])
	if len(args) != 0 {
		t.Errorf("Fields of anonymous struct type should be skipped. Instead received %#v.", args)
	}
}

func TestProcessField(t *testing.T) {
	pkg := getPackage(t, `package test
			type Sample struct {
				Something            *something.Cool
				FirstName, LastName  *Name
				GPA                  float64
			}
		`,
	)
	expRes := [][]Arg{
		{
			{
				Name: "Something",
				Type: &Type{
					Name:    "Cool",
					Package: "something",
					Star:    true,
				},
			},
		},
		{
			{
				Name: "FirstName",
				Type: &Type{
					Name: "Name",
					Star: true,
				},
			},
			{
				Name: "LastName",
				Type: &Type{
					Name: "Name",
					Star: true,
				},
			},
		},
		{
			{
				Name: "GPA",
				Type: &Type{
					Name: "float64",
				},
			},
		},
	}

	for i, v := range getFields(t, pkg).List {
		args := processField(v)
		if len(args) == 0 {
			t.Error("Arguments expected, nothing is returned.")
		}
		for j, arg := range args {
			if arg.Name != expRes[i][j].Name || !deepEqualType(arg.Type, expRes[i][j].Type) {
				t.Errorf(
					"Cannot process '%s' field. Expected %#v of type %#v, received: %#v of type %#v.",
					arg.Name, expRes[i][j], expRes[i][j].Type, arg, arg.Type,
				)
			}
		}
	}
}

// getFields is a test function that receives test package file and
// returns a list of fields of the first struct having been found there.
func getFields(t *testing.T, pkg *ast.File) *ast.FieldList {
	decl, ok := pkg.Decls[0].(*ast.GenDecl)
	if !ok {
		t.Error("Incorrect test package, cannot find general declaration.")
	}

	spec, ok := decl.Specs[0].(*ast.TypeSpec)
	if !ok {
		t.Error("Incorrect test package, cannot find spec.")
	}

	s, ok := spec.Type.(*ast.StructType)
	if !ok {
		t.Error("Incorrect test package, cannot find a struct.")
	}

	return s.Fields
}

// deepEqualArg is a function that is used by tests to compare two arguments.
func deepEqualArg(a1, a2 *Arg) bool {
	if a1 == nil || a2 == nil {
		if a1 == a2 {
			return true
		}
		return false
	}
	if a1.Name == a2.Name && a1.Tag == a2.Tag && deepEqualType(a1.Type, a2.Type) {
		return true
	}
	return false
}
<|endoftext|>