<commit_before>package testrail

import "fmt"

// Case represents a Test Case
type Case struct {
	CreatedBy            int          `json:"created_by"`
	CreatedOn            int          `json:"created_on"`
	CustomExpected       string       `json:"custom_expected"`
	CustomPreconds       string       `json:"custom_preconds"`
	CustomSteps          string       `json:"custom_steps"`
	CustomStepsSeparated []CustomStep `json:"custom_steps_separated"`
	Estimate             string       `json:"estimate"`
	EstimateForecast     string       `json:"estimate_forecast"`
	ID                   int          `json:"id"`
	MilestoneID          int          `json:"milestone_id"`
	PriorityID           int          `json:"priority_id"`
	Refs                 string       `json:"refs"`
	SectionID            int          `json:"section_id"`
	SuiteID              int          `json:"suite_id"`
	Title                string       `json:"title"`
	TypeID               int          `json:"type_id"`
	UpdatedBy            int          `json:"updated_by"`
	UdpatedOn            int          `json:"updated_on"`
}

// CustomStep represents the custom steps
// a Test Case can have
type CustomStep struct {
	Content  string `json:"content"`
	Expected string `json:"expected"`
}

// RequestFilterForCases represents the filters
// usable to get the test cases
type RequestFilterForCases struct {
	CreatedAfter  string `json:"created_after"`
	CreatedBefore string `json:"created_before"`
	CreatedBy     []int  `json:"created_by"`
	MilestoneID   []int  `json:"milestone_id"`
	PriorityID    []int  `json:"priority_id"`
	TypeID        []int  `json:"type_id"`
	UpdatedAfter  string `json:"updated_after"`
	UpdatedBefore string `json:"updated_before"`
	UpdatedBy     []int  `json:"updated_by"`
}

// SendableCase represents a Test Case
// that can be created or updated via the api
type SendableCase struct {
	Title       string       `json:"title"`
	TypeID      int          `json:"type_id,omitempty"`
	PriorityID  int          `json:"priority_id,omitempty"`
	Estimate    string       `json:"estimate,omitempty"`
	MilestoneID int          `json:"milestone_id,omitempty"`
	Refs        string       `json:"refs,omitempty"`
	Checkbox    bool         `json:"custom_checkbox,omitempty"`
	Date        string       `json:"custom_date,omitempty"`
	Dropdown    int          `json:"custom_dropdown,omitempty"`
	Integer     int          `json:"custom_integer,omitempty"`
	Milestone   int          `json:"custom_milestone,omitempty"`
	MultiSelect []int        `json:"custom_multi-select,omitempty"`
	Steps       []CustomStep `json:"custom_steps,omitempty"`
	String      string       `json:"custom_string,omitempty"`
	TemplateId  int          `json:"template_id,omitempty"`
	Text        string       `json:"custom_text,omitempty"`
	URL         string       `json:"custom_url,omitempty"`
	User        int          `json:"custom_user,omitempty"`
}

// GetCase returns the existing Test Case caseID
func (c *Client) GetCase(caseID int) (Case, error) {
	returnCase := Case{}
	err := c.sendRequest("GET", fmt.Sprintf("get_case/%d", caseID), nil, &returnCase)
	return returnCase, err
}

// GetCases returns a list of Test Cases on project projectID
// for a Test Suite suiteID
// or for specific section sectionID in a Test Suite
func (c *Client) GetCases(projectID, suiteID int, sectionID ...int) ([]Case, error) {
	uri := fmt.Sprintf("get_cases/%d&suite_id=%d", projectID, suiteID)
	if len(sectionID) > 0 {
		uri = fmt.Sprintf("%s&section_id=%d", uri, sectionID[0])
	}

	returnCases := []Case{}
	err := c.sendRequest("GET", uri, nil, &returnCases)
	return returnCases, err
}

// GetCasesWithFilters returns a list of Test Cases on project projectID
// for a Test Suite suiteID validating the filters
func (c *Client) GetCasesWithFilters(projectID, suiteID int, filters ...RequestFilterForCases) ([]Case, error) {
	uri := fmt.Sprintf("get_cases/%d&suite_id=%d", projectID, suiteID)
	if len(filters) > 0 {
		uri = applyFiltersForCase(uri, filters[0])
	}

	returnCases := []Case{}
	fmt.Println(uri)
	err := c.sendRequest("GET", uri, nil, &returnCases)
	return returnCases, err
}

// AddCase creates a new Test Case newCase and returns it
func (c *Client) AddCase(sectionID int, newCase SendableCase) (Case, error) {
	createdCase := Case{}
	err := c.sendRequest("POST", fmt.Sprintf("add_case/%d", sectionID), newCase, &createdCase)
	return createdCase, err
}

// UpdateCase updates an existing Test Case caseID and returns it
func (c *Client) UpdateCase(caseID int, updates SendableCase) (Case, error) {
	updatedCase := Case{}
	err := c.sendRequest("POST", fmt.Sprintf("update_case/%d", caseID), updates, &updatedCase)
	return updatedCase, err
}

// DeleteCase deletes the existing Test Case caseID
func (c *Client) DeleteCase(caseID int) error {
	return c.sendRequest("POST", fmt.Sprintf("delete_case/%d", caseID), nil, nil)
}

// applyFiltersForCase go through each possible filters and create the
// uri for the wanted ones
func applyFiltersForCase(uri string, filters RequestFilterForCases) string {
	if filters.CreatedAfter != "" {
		uri = fmt.Sprintf("%s&created_after=%s", uri, filters.CreatedAfter)
	}
	if filters.CreatedBefore != "" {
		uri = fmt.Sprintf("%s&created_before=%s", uri, filters.CreatedBefore)
	}
	if len(filters.CreatedBy) != 0 {
		uri = applySpecificFilter(uri, "created_by", filters.CreatedBy)
	}
	if len(filters.MilestoneID) != 0 {
		uri = applySpecificFilter(uri, "milestone_id", filters.MilestoneID)
	}
	if len(filters.PriorityID) != 0 {
		uri = applySpecificFilter(uri, "priority_id", filters.PriorityID)
	}
	if len(filters.TypeID) != 0 {
		uri = applySpecificFilter(uri, "type_id", filters.TypeID)
	}
	if filters.UpdatedAfter != "" {
		uri = fmt.Sprintf("%s&updated_after=%s", uri, filters.UpdatedAfter)
	}
	if filters.UpdatedBefore != "" {
		uri = fmt.Sprintf("%s&updated_before=%s", uri, filters.UpdatedBefore)
	}
	if len(filters.UpdatedBy) != 0 {
		uri = applySpecificFilter(uri, "updated_by", filters.UpdatedBy)
	}

	return uri
}
<commit_msg>Add custom_test_description to SendableCase for issue 18<commit_after>package testrail

import "fmt"

// Case represents a Test Case
type Case struct {
	CreatedBy            int          `json:"created_by"`
	CreatedOn            int          `json:"created_on"`
	CustomExpected       string       `json:"custom_expected"`
	CustomPreconds       string       `json:"custom_preconds"`
	CustomSteps          string       `json:"custom_steps"`
	CustomStepsSeparated []CustomStep `json:"custom_steps_separated"`
	Estimate             string       `json:"estimate"`
	EstimateForecast     string       `json:"estimate_forecast"`
	ID                   int          `json:"id"`
	MilestoneID          int          `json:"milestone_id"`
	PriorityID           int          `json:"priority_id"`
	Refs                 string       `json:"refs"`
	SectionID            int          `json:"section_id"`
	SuiteID              int          `json:"suite_id"`
	Title                string       `json:"title"`
	TypeID               int          `json:"type_id"`
	UpdatedBy            int          `json:"updated_by"`
	UdpatedOn            int          `json:"updated_on"`
}

// CustomStep represents the custom steps
// a Test Case can have
type CustomStep struct {
	Content  string `json:"content"`
	Expected string `json:"expected"`
}

// RequestFilterForCases represents the filters
// usable to get the test cases
type RequestFilterForCases struct {
	CreatedAfter  string `json:"created_after"`
	CreatedBefore string `json:"created_before"`
	CreatedBy     []int  `json:"created_by"`
	MilestoneID   []int  `json:"milestone_id"`
	PriorityID    []int  `json:"priority_id"`
	TypeID        []int  `json:"type_id"`
	UpdatedAfter  string `json:"updated_after"`
	UpdatedBefore string `json:"updated_before"`
	UpdatedBy     []int  `json:"updated_by"`
}

// SendableCase represents a Test Case
// that can be created or updated via the api
type SendableCase struct {
	Title           string       `json:"title"`
	TypeID          int          `json:"type_id,omitempty"`
	PriorityID      int          `json:"priority_id,omitempty"`
	Estimate        string       `json:"estimate,omitempty"`
	MilestoneID     int          `json:"milestone_id,omitempty"`
	Refs            string       `json:"refs,omitempty"`
	Checkbox        bool         `json:"custom_checkbox,omitempty"`
	Date            string       `json:"custom_date,omitempty"`
	Dropdown        int          `json:"custom_dropdown,omitempty"`
	Integer         int          `json:"custom_integer,omitempty"`
	Milestone       int          `json:"custom_milestone,omitempty"`
	MultiSelect     []int        `json:"custom_multi-select,omitempty"`
	Steps           []CustomStep `json:"custom_steps,omitempty"`
	String          string       `json:"custom_string,omitempty"`
	TemplateId      int          `json:"template_id,omitempty"`
	TestDescription string       `json:"custom_test_description,omitempty"`
	Text            string       `json:"custom_text,omitempty"`
	URL             string       `json:"custom_url,omitempty"`
	User            int          `json:"custom_user,omitempty"`
}

// GetCase returns the existing Test Case caseID
func (c *Client) GetCase(caseID int) (Case, error) {
	returnCase := Case{}
	err := c.sendRequest("GET", fmt.Sprintf("get_case/%d", caseID), nil, &returnCase)
	return returnCase, err
}

// GetCases returns a list of Test Cases on project projectID
// for a Test Suite suiteID
// or for specific section sectionID in a Test Suite
func (c *Client) GetCases(projectID, suiteID int, sectionID ...int) ([]Case, error) {
	uri := fmt.Sprintf("get_cases/%d&suite_id=%d", projectID, suiteID)
	if len(sectionID) > 0 {
		uri = fmt.Sprintf("%s&section_id=%d", uri, sectionID[0])
	}

	returnCases := []Case{}
	err := c.sendRequest("GET", uri, nil, &returnCases)
	return returnCases, err
}

// GetCasesWithFilters returns a list of Test Cases on project projectID
// for a Test Suite suiteID validating the filters
func (c *Client) GetCasesWithFilters(projectID, suiteID int, filters ...RequestFilterForCases) ([]Case, error) {
	uri := fmt.Sprintf("get_cases/%d&suite_id=%d", projectID, suiteID)
	if len(filters) > 0 {
		uri = applyFiltersForCase(uri, filters[0])
	}

	returnCases := []Case{}
	fmt.Println(uri)
	err := c.sendRequest("GET", uri, nil, &returnCases)
	return returnCases, err
}

// AddCase creates a new Test Case newCase and returns it
func (c *Client) AddCase(sectionID int, newCase SendableCase) (Case, error) {
	createdCase := Case{}
	err := c.sendRequest("POST", fmt.Sprintf("add_case/%d", sectionID), newCase, &createdCase)
	return createdCase, err
}

// UpdateCase updates an existing Test Case caseID and returns it
func (c *Client) UpdateCase(caseID int, updates SendableCase) (Case, error) {
	updatedCase := Case{}
	err := c.sendRequest("POST", fmt.Sprintf("update_case/%d", caseID), updates, &updatedCase)
	return updatedCase, err
}

// DeleteCase deletes the existing Test Case caseID
func (c *Client) DeleteCase(caseID int) error {
	return c.sendRequest("POST", fmt.Sprintf("delete_case/%d", caseID), nil, nil)
}

// applyFiltersForCase go through each possible filters and create the
// uri for the wanted ones
func applyFiltersForCase(uri string, filters RequestFilterForCases) string {
	if filters.CreatedAfter != "" {
		uri = fmt.Sprintf("%s&created_after=%s", uri, filters.CreatedAfter)
	}
	if filters.CreatedBefore != "" {
		uri = fmt.Sprintf("%s&created_before=%s", uri, filters.CreatedBefore)
	}
	if len(filters.CreatedBy) != 0 {
		uri = applySpecificFilter(uri, "created_by", filters.CreatedBy)
	}
	if len(filters.MilestoneID) != 0 {
		uri = applySpecificFilter(uri, "milestone_id", filters.MilestoneID)
	}
	if len(filters.PriorityID) != 0 {
		uri = applySpecificFilter(uri, "priority_id", filters.PriorityID)
	}
	if len(filters.TypeID) != 0 {
		uri = applySpecificFilter(uri, "type_id", filters.TypeID)
	}
	if filters.UpdatedAfter != "" {
		uri = fmt.Sprintf("%s&updated_after=%s", uri, filters.UpdatedAfter)
	}
	if filters.UpdatedBefore != "" {
		uri = fmt.Sprintf("%s&updated_before=%s", uri, filters.UpdatedBefore)
	}
	if len(filters.UpdatedBy) != 0 {
		uri = applySpecificFilter(uri, "updated_by", filters.UpdatedBy)
	}

	return uri
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/rand"
	"crypto/sha1"
	"encoding/gob"
	"fmt"
	"github.com/apcera/nats"
	"golang.org/x/crypto/nacl/box"
	"golang.org/x/crypto/ssh/terminal"
	"io"
	"log"
	"os"
	"runtime"
	"strings"
)

func MarshalPublicKey(c *[32]byte) string {
	return fmt.Sprintf("%x", *c)
}

func UnmarshalPublicKey(s string) *[32]byte {
	if len(s) != 64 {
		panic("Malformed public key (wrong length)")
	}
	for _, v := range s {
		switch v {
		default:
			panic("Malformed public key (not lowercase hexidecimal)")
		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f':
		}
	}
	result := new([32]byte)
	fmt.Sscanf(s, "%x", result)
	return result
}

type Room struct {
	Room string
}

type Party struct {
	Nick string
	Key  string
}

// messages from clients to secretary
type JoinRequest struct {
	Party
}

type LeaveRequest struct {
	Party
}

// messages from secretary to clients
type Message struct {
	Party
	Message string
}

type Members struct {
	Parties []Party
}

func Init() {
	gob.Register(JoinRequest{})
	gob.Register(LeaveRequest{})
	gob.Register(Message{})
	gob.Register(Members{})
}

type RoomState struct {
	Sub     *nats.Subscription
	Members []Party
}

func encode(p interface{}) []byte {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	err := enc.Encode(&p)
	if err != nil {
		log.Fatal("encode:", err)
	}
	return buf.Bytes()
}

var MyPrivateKey *[32]byte
var MyPublicKey string
var MyParty Party
var MyRooms map[string]*RoomState
var MyRoom string
var MyNick string

func initialize() {
	Init()
	rawPrivateKey, rawPublicKey, _ := box.GenerateKey(rand.Reader)
	MyPrivateKey = rawPrivateKey
	MyPublicKey = MarshalPublicKey(rawPublicKey)
	MyNick = "AnonymousCoward"
	MyParty = Party{MyNick, MyPublicKey}
	MyRooms = make(map[string]*RoomState)
}

func changeNick(nick string) {
	MyNick = nick
	MyParty = Party{MyNick, MyPublicKey}
}

func escape(s string) []byte {
	in := []byte(s)
	var out []byte
	for _, b := range in {
		switch {
		case b == '\t':
			out = append(out, ' ')
		case b == '\r':
			continue
		case b == '\n':
			out = append(out, '\n')
		case b < 32:
			out = append(out, '?')
		default:
			out = append(out, b)
		}
	}
	return out
}

func Tprintf(term *terminal.Terminal, format string, v ...interface{}) {
	term.Write(escape(fmt.Sprintf(format, v...)))
}

func client() {
	initialize()
	oldState, err := terminal.MakeRaw(0)
	if err != nil {
		panic(err.Error())
	}
	defer terminal.Restore(0, oldState)

	term := terminal.NewTerminal(os.Stdin, "> ")
	Tprintf(term, "Greetings, %s!\n", MyNick)
	Tprintf(term, "Commands:\n")
	Tprintf(term, "join foo      (join the chatroom named foo)\n")
	Tprintf(term, "leave foo     (leave the chatroom named foo)\n")
	Tprintf(term, "nick foo      (change your nickname to foo)\n")
	Tprintf(term, "members       (list the parties in the current room)\n")
	Tprintf(term, "^D            (buh-bye)\n")
	Tprintf(term, "anything else (send anything else to your current chatroom)\n")

	nc, err := nats.Connect(nats.DefaultURL)
	if err != nil {
		panic("unable to connect to NATS server")
	}

	for {
		line, err := term.ReadLine()
		if err != nil {
			break
		}
		words := strings.Fields(line)
		if len(words) == 0 {
			continue
		}
		switch words[0] {
		case "nick":
			if len(words) == 2 {
				changeNick(words[1])
				Tprintf(term, "Your nickname is now %s\n", MyNick)

			} else {
				Tprintf(term, "Nicknames must be one word\n")
			}
		case "join":
			if len(words) == 2 {
				room := words[1]
				MyRoom = room
				term.SetPrompt(fmt.Sprintf("[%s]> ", MyRoom))
				_, ok := MyRooms[room]
				if !ok {
					joinTerm(nc, term, room)
				}
			} else {
				Tprintf(term, "Join what room?\n")
			}
		case "rooms":
			for room, _ := range MyRooms {
				Tprintf(term, "%s\n", room)
			}
		case "leave":
			words = words[1:]
			for _, room := range words {
				if st, ok := MyRooms[room]; ok {
					delete(MyRooms, room)
					err = st.Sub.Unsubscribe()
					checkError(err)
					if MyRoom == room {
						if len(MyRooms) == 0 {
							MyRoom = ""
							term.SetPrompt(fmt.Sprintf("> "))
						} else {
							for r, _ := range MyRooms {
								MyRoom = r
								term.SetPrompt(fmt.Sprintf("[%s]> ", MyRoom))
								break
							}
						}
					}
				} else {
					Tprintf(term, "Not a member of %s\n", room)
				}
			}
		case "members":
			if MyRoom == "" {
				Tprintf(term, "You must join a room before you can see the members of the room\n")
			} else {
				h := sha1.New()
				st := MyRooms[MyRoom]
				for _, member := range st.Members {
					io.WriteString(h, member.Key)
					Tprintf(term, "%s (%s)\n", member.Key, member.Nick)
				}
				Tprintf(term, "Hash: %x\n", h.Sum(nil))
			}
		default:
			if MyRoom != "" {
				msg := strings.TrimSpace(line)
				err = nc.Publish(MyRoom, encode(Message{MyParty, msg}))
				checkError(err)
			} else {
				Tprintf(term, "You must join a room first\n")
			}
		}
	}
}

func joinTerm(nc *nats.Conn, term *terminal.Terminal, rm string) {
	err := nc.Publish(fmt.Sprintf("secretary.%s", rm), encode(JoinRequest{MyParty}))
	checkError(err)
	sub, err := nc.Subscribe(rm, func(m *nats.Msg) {
		dec := gob.NewDecoder(bytes.NewBuffer(m.Data))
		var p interface{}
		err := dec.Decode(&p)
		if err != nil {
			log.Fatal("decode:", err)
		}
		switch r := p.(type) {
		case Message:
			if r.Party != MyParty {
				Tprintf(term, "[%s]: %s -- (%s)\n", rm, r.Message, r.Party.Nick)
			}
		case Members:
			MyRooms[rm].Members = r.Parties
		}
	})
	checkError(err)
	MyRooms[rm] = &RoomState{sub, nil} // needs lock
}

func secretary() {
	log.Println("starting secretary")
	initialize()
	changeNick("ChatAdministrator")
	rooms := make(map[string]bool)
	members := make(map[string](map[Party]bool))
	nc, err := nats.Connect(nats.DefaultURL)
	if err != nil {
		panic("unable to connect to NATS server")
	}
	nc.Subscribe("secretary.>", func(m *nats.Msg) {
		dec := gob.NewDecoder(bytes.NewBuffer(m.Data))
		var p interface{}
		err := dec.Decode(&p)
		if err != nil {
			log.Fatal("decode:", err)
		}
		room := m.Subject[len("secretary."):]
		if !rooms[room] {
			rooms[room] = true
		}
		switch r := p.(type) {
		case LeaveRequest:
			delete(members[room], r.Party)
			_ = nc.Publish(room, encode(Message{MyParty, fmt.Sprintf("%s has left the room", r.Party.Nick)}))
			log.Println("Leave", room, r)
		case JoinRequest:
			if _, ok := members[room]; !ok {
				members[room] = make(map[Party]bool)
			}
			members[room][r.Party] = true
			_ = nc.Publish(room, encode(Message{MyParty, fmt.Sprintf("%s has joined %s", r.Party.Nick, room)}))
			log.Println("Join", room, r)
			numMembers := len(members[room])
			parties := make([]Party, 0, numMembers)
			for party, _ := range members[room] {
				parties = append(parties, party)
			}
			_ = nc.Publish(room, encode(Members{parties}))
			log.Println("Members", room, members[room])
		case Message:
			log.Println("Message", r.Message)
		}
	})
	runtime.Goexit()
}

func checkError(err error) {
	if err != nil {
		log.Fatal("Error:", err)
	}
}

func main() {
	if len(os.Args) > 1 && os.Args[1] == "secretary" {
		secretary()
	} else {
		client()
	}
}
<commit_msg>chat: maintain hash of room membership<commit_after>package main

import (
	"bytes"
	"crypto/rand"
	"crypto/sha1"
	"encoding/gob"
	"fmt"
	"github.com/apcera/nats"
	"golang.org/x/crypto/nacl/box"
	"golang.org/x/crypto/ssh/terminal"
	"io"
	"log"
	"os"
	"runtime"
	"strings"
)

func MarshalPublicKey(c *[32]byte) string {
	return fmt.Sprintf("%x", *c)
}

func UnmarshalPublicKey(s string) *[32]byte {
	if len(s) != 64 {
		panic("Malformed public key (wrong length)")
	}
	for _, v := range s {
		switch v {
		default:
			panic("Malformed public key (not lowercase hexidecimal)")
		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f':
		}
	}
	result := new([32]byte)
	fmt.Sscanf(s, "%x", result)
	return result
}

type Room struct {
	Room string
}

type Party struct {
	Nick string
	Key  string
}

// messages from clients to secretary
type JoinRequest struct {
	Party
}

type LeaveRequest struct {
	Party
}

// messages from secretary to clients
type Message struct {
	Party
	Message string
}

type Members struct {
	Parties []Party
}

func Init() {
	gob.Register(JoinRequest{})
	gob.Register(LeaveRequest{})
	gob.Register(Message{})
	gob.Register(Members{})
}

type RoomState struct {
	Sub     *nats.Subscription
	Members []Party
	Hash    []byte
}

func encode(p interface{}) []byte {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	err := enc.Encode(&p)
	if err != nil {
		log.Fatal("encode:", err)
	}
	return buf.Bytes()
}

var MyPrivateKey *[32]byte
var MyPublicKey string
var MyParty Party
var MyRooms map[string]*RoomState
var MyRoom string
var MyNick string

func initialize() {
	Init()
	rawPrivateKey, rawPublicKey, _ := box.GenerateKey(rand.Reader)
	MyPrivateKey = rawPrivateKey
	MyPublicKey = MarshalPublicKey(rawPublicKey)
	MyNick = "AnonymousCoward"
	MyParty = Party{MyNick, MyPublicKey}
	MyRooms = make(map[string]*RoomState)
}

func changeNick(nick string) {
	MyNick = nick
	MyParty = Party{MyNick, MyPublicKey}
}

func escape(s string) []byte {
	in := []byte(s)
	var out []byte
	for _, b := range in {
		switch {
		case b == '\t':
			out = append(out, ' ')
		case b == '\r':
			continue
		case b == '\n':
			out = append(out, '\n')
		case b < 32:
			out = append(out, '?')
		default:
			out = append(out, b)
		}
	}
	return out
}

func Tprintf(term *terminal.Terminal, format string, v ...interface{}) {
	term.Write(escape(fmt.Sprintf(format, v...)))
}

func client() {
	initialize()
	oldState, err := terminal.MakeRaw(0)
	if err != nil {
		panic(err.Error())
	}
	defer terminal.Restore(0, oldState)

	term := terminal.NewTerminal(os.Stdin, "> ")
	Tprintf(term, "Greetings, %s!\n", MyNick)
	Tprintf(term, "Commands:\n")
	Tprintf(term, "join foo      (join the chatroom named foo)\n")
	Tprintf(term, "leave foo     (leave the chatroom named foo)\n")
	Tprintf(term, "nick foo      (change your nickname to foo)\n")
	Tprintf(term, "members       (list the parties in the current room)\n")
	Tprintf(term, "^D            (buh-bye)\n")
	Tprintf(term, "anything else (send anything else to your current chatroom)\n")

	nc, err := nats.Connect(nats.DefaultURL)
	if err != nil {
		panic("unable to connect to NATS server")
	}

	for {
		line, err := term.ReadLine()
		if err != nil {
			break
		}
		words := strings.Fields(line)
		if len(words) == 0 {
			continue
		}
		switch words[0] {
		case "nick":
			if len(words) == 2 {
				changeNick(words[1])
				Tprintf(term, "Your nickname is now %s\n", MyNick)

			} else {
				Tprintf(term, "Nicknames must be one word\n")
			}
		case "join":
			if len(words) == 2 {
				room := words[1]
				MyRoom = room
				term.SetPrompt(fmt.Sprintf("[%s]> ", MyRoom))
				_, ok := MyRooms[room]
				if !ok {
					joinTerm(nc, term, room)
				}
			} else {
				Tprintf(term, "Join what room?\n")
			}
		case "rooms":
			for room, _ := range MyRooms {
				Tprintf(term, "%s\n", room)
			}
		case "leave":
			words = words[1:]
			for _, room := range words {
				if st, ok := MyRooms[room]; ok {
					delete(MyRooms, room)
					err = st.Sub.Unsubscribe()
					checkError(err)
					if MyRoom == room {
						if len(MyRooms) == 0 {
							MyRoom = ""
							term.SetPrompt(fmt.Sprintf("> "))
						} else {
							for r, _ := range MyRooms {
								MyRoom = r
								term.SetPrompt(fmt.Sprintf("[%s]> ", MyRoom))
								break
							}
						}
					}
				} else {
					Tprintf(term, "Not a member of %s\n", room)
				}
			}
		case "members":
			if MyRoom == "" {
				Tprintf(term, "You must join a room before you can see the members of the room\n")
			} else {
				st := MyRooms[MyRoom]
				for _, member := range st.Members {
					Tprintf(term, "%s (%s)\n", member.Key, member.Nick)
				}
				Tprintf(term, "Hash: %x\n", st.Hash)
			}
		default:
			if MyRoom != "" {
				msg := strings.TrimSpace(line)
				err = nc.Publish(MyRoom, encode(Message{MyParty, msg}))
				checkError(err)
			} else {
				Tprintf(term, "You must join a room first\n")
			}
		}
	}
}

func joinTerm(nc *nats.Conn, term *terminal.Terminal, rm string) {
	err := nc.Publish(fmt.Sprintf("secretary.%s", rm), encode(JoinRequest{MyParty}))
	checkError(err)
	sub, err := nc.Subscribe(rm, func(m *nats.Msg) {
		dec := gob.NewDecoder(bytes.NewBuffer(m.Data))
		var p interface{}
		err := dec.Decode(&p)
		if err != nil {
			log.Fatal("decode:", err)
		}
		switch r := p.(type) {
		case Message:
			if r.Party != MyParty {
				Tprintf(term, "[%s]: %s -- (%s)\n", rm, r.Message, r.Party.Nick)
			}
		case Members:
			st := MyRooms[rm]
			st.Members = r.Parties
			h := sha1.New()
			for _, member := range st.Members {
				io.WriteString(h, member.Key)
			}
			st.Hash = h.Sum(nil)
		}
	})
	checkError(err)
	MyRooms[rm] = &RoomState{sub, nil, nil} // needs lock
}

func secretary() {
	log.Println("starting secretary")
	initialize()
	changeNick("ChatAdministrator")
	rooms := make(map[string]bool)
	members := make(map[string](map[Party]bool))
	nc, err := nats.Connect(nats.DefaultURL)
	if err != nil {
		panic("unable to connect to NATS server")
	}
	nc.Subscribe("secretary.>", func(m *nats.Msg) {
		dec := gob.NewDecoder(bytes.NewBuffer(m.Data))
		var p interface{}
		err := dec.Decode(&p)
		if err != nil {
			log.Fatal("decode:", err)
		}
		room := m.Subject[len("secretary."):]
		if !rooms[room] {
			rooms[room] = true
		}
		switch r := p.(type) {
		case LeaveRequest:
			delete(members[room], r.Party)
			_ = nc.Publish(room, encode(Message{MyParty, fmt.Sprintf("%s has left the room", r.Party.Nick)}))
			log.Println("Leave", room, r)
		case JoinRequest:
			if _, ok := members[room]; !ok {
				members[room] = make(map[Party]bool)
			}
			members[room][r.Party] = true
			_ = nc.Publish(room, encode(Message{MyParty, fmt.Sprintf("%s has joined %s", r.Party.Nick, room)}))
			log.Println("Join", room, r)
			numMembers := len(members[room])
			parties := make([]Party, 0, numMembers)
			for party, _ := range members[room] {
				parties = append(parties, party)
			}
			_ = nc.Publish(room, encode(Members{parties}))
			log.Println("Members", room, members[room])
		case Message:
			log.Println("Message", r.Message)
		}
	})
	runtime.Goexit()
}

func checkError(err error) {
	if err != nil {
		log.Fatal("Error:", err)
	}
}

func main() {
	if len(os.Args) > 1 && os.Args[1] == "secretary" {
		secretary()
	} else {
		client()
	}
}
<|endoftext|>


<commit_before>// Package cobs implements Consistent Overhead Byte Stuffing algorithm
//
// COBS encoder breaks a packet into one or more sequences of non-zero
// bytes.  The encoding routine searches through the first 254 bytes of
// the packet looking for the first occurrence of a zero byte.  If no
// zero is found, then a code of 0xFF is output, followed by the 254
// non-zero bytes. If a zero is found, then the number of bytes
// examined, n, is output as the code byte, followed by the actual
// values of the (n-1) non-zero bytes up to (but not including) the zero
// byte. This process is repeated until all the bytes of the packet have
// been encoded.
//
// See also: http://www.stuartcheshire.org/papers/COBSforToN.pdf
package cobs

import "bytes"

// EncodedSize calculates size of encoded message
func EncodedSize(n int) int {
	return n + n/254 + 1
}

// Encode a slice of bytes to a null-terminated frame
func Encode(p []byte) []byte {
	buf := new(bytes.Buffer)
	block := func(p []byte) {
		buf.WriteByte(byte(len(p) + 1))
		buf.Write(p)
	}
	for _, ch := range bytes.Split(p, []byte{0}) {
		for len(ch) > 0xfe {
			block(ch[:0xfe])
			ch = ch[0xfe:]
		}
		block(ch)
	}
	buf.WriteByte(0)
	return buf.Bytes()
}

// Decode a null-terminated frame to a slice of bytes
func Decode(b []byte) []byte {
	if len(b) == 0 {
		return nil
	}
	buf := new(bytes.Buffer)
	for n := b[0]; n > 0; n = b[0] {
		if int(n) >= len(b) {
			return nil
		}
		buf.Write(b[1:n])
		b = b[n:]
		if n < 0xff && b[0] > 0 {
			buf.WriteByte(0)
		}
	}
	return buf.Bytes()
}
<commit_msg>Rename writeBlock<commit_after>// Package cobs implements Consistent Overhead Byte Stuffing algorithm
//
// COBS encoder breaks a packet into one or more sequences of non-zero
// bytes.  The encoding routine searches through the first 254 bytes of
// the packet looking for the first occurrence of a zero byte.  If no
// zero is found, then a code of 0xFF is output, followed by the 254
// non-zero bytes. If a zero is found, then the number of bytes
// examined, n, is output as the code byte, followed by the actual
// values of the (n-1) non-zero bytes up to (but not including) the zero
// byte. This process is repeated until all the bytes of the packet have
// been encoded.
//
// See also: http://www.stuartcheshire.org/papers/COBSforToN.pdf
package cobs

import "bytes"

// EncodedSize calculates size of encoded message
func EncodedSize(n int) int {
	return n + n/254 + 1
}

// Encode a slice of bytes to a null-terminated frame
func Encode(p []byte) []byte {
	buf := new(bytes.Buffer)
	writeBlock := func(p []byte) {
		buf.WriteByte(byte(len(p) + 1))
		buf.Write(p)
	}
	for _, ch := range bytes.Split(p, []byte{0}) {
		for len(ch) > 0xfe {
			writeBlock(ch[:0xfe])
			ch = ch[0xfe:]
		}
		writeBlock(ch)
	}
	buf.WriteByte(0)
	return buf.Bytes()
}

// Decode a null-terminated frame to a slice of bytes
func Decode(b []byte) []byte {
	if len(b) == 0 {
		return nil
	}
	buf := new(bytes.Buffer)
	for n := b[0]; n > 0; n = b[0] {
		if int(n) >= len(b) {
			return nil
		}
		buf.Write(b[1:n])
		b = b[n:]
		if n < 0xff && b[0] > 0 {
			buf.WriteByte(0)
		}
	}
	return buf.Bytes()
}
<|endoftext|>


<commit_before>package doozer

import (
	"encoding/binary"
	"github.com/kr/pretty.go"
	"goprotobuf.googlecode.com/hg/proto"
	"http"
	"io"
	"log"
	"net"
	"os"
	"rand"
	"strings"
)

var (
	uriPrefix = "doozer:?"
)

var (
	ErrInvalidUri = os.NewError("invalid uri")
)

type txn struct {
	req  request
	resp *response
	err  os.Error
	done chan bool
}

type Conn struct {
	addr    string
	conn    net.Conn
	send    chan *txn
	msg     chan []byte
	err     os.Error
	stop    chan bool
	stopped chan bool
}

// Dial connects to a single doozer server.
func Dial(addr string) (*Conn, os.Error) {
	var c Conn
	var err os.Error
	c.addr = addr
	c.conn, err = net.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}

	c.send = make(chan *txn)
	c.msg = make(chan []byte)
	c.stop = make(chan bool, 1)
	c.stopped = make(chan bool)
	errch := make(chan os.Error, 1)
	go c.mux(errch)
	go c.readAll(errch)
	return &c, nil
}

// DialUri connects to one of the doozer servers given in `uri`. If `uri`
// contains a secret key, then DialUri will call `Access` with the secret.
func DialUri(uri string) (*Conn, os.Error) {
	if !strings.HasPrefix(uri, uriPrefix) {
		return nil, ErrInvalidUri
	}

	q := uri[len(uriPrefix):]
	p, err := http.ParseQuery(q)
	if err != nil {
		return nil, err
	}

	addrs, ok := p["ca"]
	if !ok {
		return nil, ErrInvalidUri
	}

	c, err := Dial(addrs[rand.Int()%len(addrs)])
	if err != nil {
		return nil, err
	}

	secret, ok := p["sk"]
	if ok {
		err = c.Access(secret[0])
		if err != nil {
			c.Close()
			return nil, err
		}
	}

	return c, nil
}

func (c *Conn) call(t *txn) os.Error {
	t.done = make(chan bool)
	select {
	case <-c.stopped:
		return c.err
	case c.send <- t:
		<-t.done
		if t.err != nil {
			return t.err
		}
		if t.resp.ErrCode != nil {
			return newError(t)
		}
	}
	return nil
}

// After Close is called, operations on c will return ErrClosed.
func (c *Conn) Close() {
	select {
	case c.stop <- true:
	default:
	}
}

func (c *Conn) mux(errch chan os.Error) {
	txns := make(map[int32]*txn)
	var n int32 // next tag
	var err os.Error

	for {
		select {
		case t := <-c.send:
			// find an unused tag
			for t := txns[n]; t != nil; t = txns[n] {
				n++
			}
			txns[n] = t

			// don't take n's address; it will change
			tag := n
			t.req.Tag = &tag

			var buf []byte
			buf, err = proto.Marshal(&t.req)
			if err != nil {
				txns[n] = nil
				t.err = err
				t.done <- true
				continue
			}

			err = c.write(buf)
			if err != nil {
				goto error
			}
		case buf := <-c.msg:
			var r response
			err = proto.Unmarshal(buf, &r)
			if err != nil {
				log.Print(err)
				continue
			}

			if r.Tag == nil {
				log.Printf("nil tag: %# v", pretty.Formatter(r))
				continue
			}
			t := txns[*r.Tag]
			if t == nil {
				log.Printf("unexpected: %# v", pretty.Formatter(r))
				continue
			}

			txns[*r.Tag] = nil, false
			t.resp = &r
			t.done <- true
		case err = <-errch:
			goto error
		case <-c.stop:
			err = ErrClosed
			goto error
		}
	}

error:
	c.err = err
	for _, t := range txns {
		t.err = err
		t.done <- true
	}
	c.conn.Close()
	close(c.stopped)
}

func (c *Conn) readAll(errch chan os.Error) {
	for {
		buf, err := c.read()
		if err != nil {
			errch <- err
			return
		}

		c.msg <- buf
	}
}

func (c *Conn) read() ([]byte, os.Error) {
	var size int32
	err := binary.Read(c.conn, binary.BigEndian, &size)
	if err != nil {
		return nil, err
	}

	buf := make([]byte, size)
	_, err = io.ReadFull(c.conn, buf)
	if err != nil {
		return nil, err
	}

	return buf, nil
}

func (c *Conn) write(buf []byte) os.Error {
	err := binary.Write(c.conn, binary.BigEndian, int32(len(buf)))
	if err != nil {
		return err
	}

	_, err = c.conn.Write(buf)
	return err
}

// Attempts access to the store
func (c *Conn) Access(token string) os.Error {
	var t txn
	t.req.Verb = newRequest_Verb(request_ACCESS)
	t.req.Value = []byte(token)
	return c.call(&t)
}

// Sets the contents of file to body, if it hasn't been modified since oldRev.
func (c *Conn) Set(file string, oldRev int64, body []byte) (newRev int64, err os.Error) {
	var t txn
	t.req.Verb = newRequest_Verb(request_SET)
	t.req.Path = &file
	t.req.Value = body
	t.req.Rev = &oldRev

	err = c.call(&t)
	if err != nil {
		return
	}

	return proto.GetInt64(t.resp.Rev), nil
}

// Deletes file, if it hasn't been modified since rev.
func (c *Conn) Del(file string, rev int64) os.Error {
	var t txn
	t.req.Verb = newRequest_Verb(request_DEL)
	t.req.Path = &file
	t.req.Rev = &rev
	return c.call(&t)
}

func (c *Conn) Nop() os.Error {
	var t txn
	t.req.Verb = newRequest_Verb(request_NOP)
	return c.call(&t)
}

// Returns the body and revision of the file at path,
// as of store revision *rev.
// If rev is nil, uses the current state.
func (c *Conn) Get(file string, rev *int64) ([]byte, int64, os.Error) {
	var t txn
	t.req.Verb = newRequest_Verb(request_GET)
	t.req.Path = &file
	t.req.Rev = rev

	err := c.call(&t)
	if err != nil {
		return nil, 0, err
	}

	return t.resp.Value, proto.GetInt64(t.resp.Rev), nil
}

// Getdir reads up to lim names from dir, at revision rev, into an array.
// Names are read in lexicographical order, starting at position off.
// A negative lim means to read until the end.
func (c *Conn) Getdir(dir string, rev int64, off, lim int) (names []string, err os.Error) {
	for lim != 0 {
		var t txn
		t.req.Verb = newRequest_Verb(request_GETDIR)
		t.req.Rev = &rev
		t.req.Path = &dir
		t.req.Offset = proto.Int32(int32(off))
		err = c.call(&t)
		if err, ok := err.(*Error); ok && err.Err == ErrRange {
			return names, nil
		}
		if err != nil {
			return nil, err
		}
		names = append(names, *t.resp.Path)
		off++
		lim--
	}
	return
}

// Stat returns metadata about the file or directory at path,
// in revision *storeRev. If storeRev is nil, uses the current
// revision.
func (c *Conn) Stat(path string, storeRev *int64) (len int32, fileRev int64, err os.Error) {
	var t txn
	t.req.Verb = newRequest_Verb(request_STAT)
	t.req.Path = &path
	t.req.Rev = storeRev

	err = c.call(&t)
	if err != nil {
		return 0, 0, err
	}

	return proto.GetInt32(t.resp.Len), proto.GetInt64(t.resp.Rev), nil
}

// Walk reads up to lim entries matching glob, in revision rev, into an array.
// Entries are read in lexicographical order, starting at position off.
// A negative lim means to read until the end.
func (c *Conn) Walk(glob string, rev int64, off, lim int) (info []Event, err os.Error) {
	for lim != 0 {
		var t txn
		t.req.Verb = newRequest_Verb(request_WALK)
		t.req.Rev = &rev
		t.req.Path = &glob
		t.req.Offset = proto.Int32(int32(off))
		err = c.call(&t)
		if err, ok := err.(*Error); ok && err.Err == ErrRange {
			return info, nil
		}
		if err != nil {
			return nil, err
		}
		info = append(info, Event{
			*t.resp.Rev,
			*t.resp.Path,
			t.resp.Value,
			*t.resp.Flags,
		})
		off++
		lim--
	}
	return
}

// Waits for the first change, on or after rev, to any file matching glob.
func (c *Conn) Wait(glob string, rev int64) (ev Event, err os.Error) {
	var t txn
	t.req.Verb = newRequest_Verb(request_WAIT)
	t.req.Path = &glob
	t.req.Rev = &rev

	err = c.call(&t)
	if err != nil {
		return
	}

	ev.Rev = *t.resp.Rev
	ev.Path = *t.resp.Path
	ev.Body = t.resp.Value
	ev.Flag = *t.resp.Flags & (set | del)
	return
}

// Rev returns the current revision of the store.
func (c *Conn) Rev() (int64, os.Error) {
	var t txn
	t.req.Verb = newRequest_Verb(request_REV)

	err := c.call(&t)
	if err != nil {
		return 0, err
	}

	return *t.resp.Rev, nil
}
<commit_msg>simplify return type of Conn.Stat<commit_after>package doozer

import (
	"encoding/binary"
	"github.com/kr/pretty.go"
	"goprotobuf.googlecode.com/hg/proto"
	"http"
	"io"
	"log"
	"net"
	"os"
	"rand"
	"strings"
)

var (
	uriPrefix = "doozer:?"
)

var (
	ErrInvalidUri = os.NewError("invalid uri")
)

type txn struct {
	req  request
	resp *response
	err  os.Error
	done chan bool
}

type Conn struct {
	addr    string
	conn    net.Conn
	send    chan *txn
	msg     chan []byte
	err     os.Error
	stop    chan bool
	stopped chan bool
}

// Dial connects to a single doozer server.
func Dial(addr string) (*Conn, os.Error) {
	var c Conn
	var err os.Error
	c.addr = addr
	c.conn, err = net.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}

	c.send = make(chan *txn)
	c.msg = make(chan []byte)
	c.stop = make(chan bool, 1)
	c.stopped = make(chan bool)
	errch := make(chan os.Error, 1)
	go c.mux(errch)
	go c.readAll(errch)
	return &c, nil
}

// DialUri connects to one of the doozer servers given in `uri`. If `uri`
// contains a secret key, then DialUri will call `Access` with the secret.
func DialUri(uri string) (*Conn, os.Error) {
	if !strings.HasPrefix(uri, uriPrefix) {
		return nil, ErrInvalidUri
	}

	q := uri[len(uriPrefix):]
	p, err := http.ParseQuery(q)
	if err != nil {
		return nil, err
	}

	addrs, ok := p["ca"]
	if !ok {
		return nil, ErrInvalidUri
	}

	c, err := Dial(addrs[rand.Int()%len(addrs)])
	if err != nil {
		return nil, err
	}

	secret, ok := p["sk"]
	if ok {
		err = c.Access(secret[0])
		if err != nil {
			c.Close()
			return nil, err
		}
	}

	return c, nil
}

func (c *Conn) call(t *txn) os.Error {
	t.done = make(chan bool)
	select {
	case <-c.stopped:
		return c.err
	case c.send <- t:
		<-t.done
		if t.err != nil {
			return t.err
		}
		if t.resp.ErrCode != nil {
			return newError(t)
		}
	}
	return nil
}

// After Close is called, operations on c will return ErrClosed.
func (c *Conn) Close() {
	select {
	case c.stop <- true:
	default:
	}
}

func (c *Conn) mux(errch chan os.Error) {
	txns := make(map[int32]*txn)
	var n int32 // next tag
	var err os.Error

	for {
		select {
		case t := <-c.send:
			// find an unused tag
			for t := txns[n]; t != nil; t = txns[n] {
				n++
			}
			txns[n] = t

			// don't take n's address; it will change
			tag := n
			t.req.Tag = &tag

			var buf []byte
			buf, err = proto.Marshal(&t.req)
			if err != nil {
				txns[n] = nil
				t.err = err
				t.done <- true
				continue
			}

			err = c.write(buf)
			if err != nil {
				goto error
			}
		case buf := <-c.msg:
			var r response
			err = proto.Unmarshal(buf, &r)
			if err != nil {
				log.Print(err)
				continue
			}

			if r.Tag == nil {
				log.Printf("nil tag: %# v", pretty.Formatter(r))
				continue
			}
			t := txns[*r.Tag]
			if t == nil {
				log.Printf("unexpected: %# v", pretty.Formatter(r))
				continue
			}

			txns[*r.Tag] = nil, false
			t.resp = &r
			t.done <- true
		case err = <-errch:
			goto error
		case <-c.stop:
			err = ErrClosed
			goto error
		}
	}

error:
	c.err = err
	for _, t := range txns {
		t.err = err
		t.done <- true
	}
	c.conn.Close()
	close(c.stopped)
}

func (c *Conn) readAll(errch chan os.Error) {
	for {
		buf, err := c.read()
		if err != nil {
			errch <- err
			return
		}

		c.msg <- buf
	}
}

func (c *Conn) read() ([]byte, os.Error) {
	var size int32
	err := binary.Read(c.conn, binary.BigEndian, &size)
	if err != nil {
		return nil, err
	}

	buf := make([]byte, size)
	_, err = io.ReadFull(c.conn, buf)
	if err != nil {
		return nil, err
	}

	return buf, nil
}

func (c *Conn) write(buf []byte) os.Error {
	err := binary.Write(c.conn, binary.BigEndian, int32(len(buf)))
	if err != nil {
		return err
	}

	_, err = c.conn.Write(buf)
	return err
}

// Attempts access to the store
func (c *Conn) Access(token string) os.Error {
	var t txn
	t.req.Verb = newRequest_Verb(request_ACCESS)
	t.req.Value = []byte(token)
	return c.call(&t)
}

// Sets the contents of file to body, if it hasn't been modified since oldRev.
func (c *Conn) Set(file string, oldRev int64, body []byte) (newRev int64, err os.Error) {
	var t txn
	t.req.Verb = newRequest_Verb(request_SET)
	t.req.Path = &file
	t.req.Value = body
	t.req.Rev = &oldRev

	err = c.call(&t)
	if err != nil {
		return
	}

	return proto.GetInt64(t.resp.Rev), nil
}

// Deletes file, if it hasn't been modified since rev.
func (c *Conn) Del(file string, rev int64) os.Error {
	var t txn
	t.req.Verb = newRequest_Verb(request_DEL)
	t.req.Path = &file
	t.req.Rev = &rev
	return c.call(&t)
}

func (c *Conn) Nop() os.Error {
	var t txn
	t.req.Verb = newRequest_Verb(request_NOP)
	return c.call(&t)
}

// Returns the body and revision of the file at path,
// as of store revision *rev.
// If rev is nil, uses the current state.
func (c *Conn) Get(file string, rev *int64) ([]byte, int64, os.Error) {
	var t txn
	t.req.Verb = newRequest_Verb(request_GET)
	t.req.Path = &file
	t.req.Rev = rev

	err := c.call(&t)
	if err != nil {
		return nil, 0, err
	}

	return t.resp.Value, proto.GetInt64(t.resp.Rev), nil
}

// Getdir reads up to lim names from dir, at revision rev, into an array.
// Names are read in lexicographical order, starting at position off.
// A negative lim means to read until the end.
func (c *Conn) Getdir(dir string, rev int64, off, lim int) (names []string, err os.Error) {
	for lim != 0 {
		var t txn
		t.req.Verb = newRequest_Verb(request_GETDIR)
		t.req.Rev = &rev
		t.req.Path = &dir
		t.req.Offset = proto.Int32(int32(off))
		err = c.call(&t)
		if err, ok := err.(*Error); ok && err.Err == ErrRange {
			return names, nil
		}
		if err != nil {
			return nil, err
		}
		names = append(names, *t.resp.Path)
		off++
		lim--
	}
	return
}

// Stat returns metadata about the file or directory at path,
// in revision *storeRev. If storeRev is nil, uses the current
// revision.
func (c *Conn) Stat(path string, storeRev *int64) (len int, fileRev int64, err os.Error) {
	var t txn
	t.req.Verb = newRequest_Verb(request_STAT)
	t.req.Path = &path
	t.req.Rev = storeRev

	err = c.call(&t)
	if err != nil {
		return 0, 0, err
	}

	return int(proto.GetInt32(t.resp.Len)), proto.GetInt64(t.resp.Rev), nil
}

// Walk reads up to lim entries matching glob, in revision rev, into an array.
// Entries are read in lexicographical order, starting at position off.
// A negative lim means to read until the end.
func (c *Conn) Walk(glob string, rev int64, off, lim int) (info []Event, err os.Error) {
	for lim != 0 {
		var t txn
		t.req.Verb = newRequest_Verb(request_WALK)
		t.req.Rev = &rev
		t.req.Path = &glob
		t.req.Offset = proto.Int32(int32(off))
		err = c.call(&t)
		if err, ok := err.(*Error); ok && err.Err == ErrRange {
			return info, nil
		}
		if err != nil {
			return nil, err
		}
		info = append(info, Event{
			*t.resp.Rev,
			*t.resp.Path,
			t.resp.Value,
			*t.resp.Flags,
		})
		off++
		lim--
	}
	return
}

// Waits for the first change, on or after rev, to any file matching glob.
func (c *Conn) Wait(glob string, rev int64) (ev Event, err os.Error) {
	var t txn
	t.req.Verb = newRequest_Verb(request_WAIT)
	t.req.Path = &glob
	t.req.Rev = &rev

	err = c.call(&t)
	if err != nil {
		return
	}

	ev.Rev = *t.resp.Rev
	ev.Path = *t.resp.Path
	ev.Body = t.resp.Value
	ev.Flag = *t.resp.Flags & (set | del)
	return
}

// Rev returns the current revision of the store.
func (c *Conn) Rev() (int64, os.Error) {
	var t txn
	t.req.Verb = newRequest_Verb(request_REV)

	err := c.call(&t)
	if err != nil {
		return 0, err
	}

	return *t.resp.Rev, nil
}
<|endoftext|>


<commit_before>package peerstream

import (
	"errors"
	"net"
	"net/http"
	"sync"

	ss "github.com/docker/spdystream"
)

// ConnHandler is a function which receives a Conn. It allows
// clients to set a function to receive newly accepted
// connections. It works like StreamHandler, but is usually
// less useful than usual as most services will only use
// Streams. It is safe to pass or store the *Conn elsewhere.
// Note: the ConnHandler is called sequentially, so spawn
// goroutines or pass the Conn. See EchoHandler.
type ConnHandler func(s *Conn)

// SelectConn selects a connection out of list. It allows
// delegation of decision making to clients. Clients can
// make SelectConn functons that check things connection
// qualities -- like latency andbandwidth -- or pick from
// a logical set of connections.
type SelectConn func([]*Conn) *Conn

// ErrInvalidConnSelected signals that a connection selected
// with a SelectConn function is invalid. This may be due to
// the Conn not being part of the original set given to the
// function, or the value being nil.
var ErrInvalidConnSelected = errors.New("invalid selected connection")

// ErrNoConnections signals that no connections are available
var ErrNoConnections = errors.New("no connections")

// Conn is a Swarm-associated connection.
type Conn struct {
	ssConn  *ss.Connection
	netConn net.Conn // underlying connection

	swarm  *Swarm
	groups groupSet

	streams    map[*Stream]struct{}
	streamLock sync.RWMutex
}

func newConn(nconn net.Conn, sconn *ss.Connection, s *Swarm) *Conn {
	return &Conn{
		netConn: nconn,
		ssConn:  sconn,
		swarm:   s,
		groups:  groupSet{m: make(map[Group]struct{})},
		streams: make(map[*Stream]struct{}),
	}
}

// Swarm returns the Swarm associated with this Conn
func (c *Conn) Swarm() *Swarm {
	return c.swarm
}

// NetConn returns the underlying net.Conn
func (c *Conn) NetConn() net.Conn {
	return c.netConn
}

// SPDYConn returns the spdystream.Connection we use
// Warning: modifying this object is undefined.
func (c *Conn) SPDYConn() *ss.Connection {
	return c.ssConn
}

// Groups returns the Groups this Conn belongs to
func (c *Conn) Groups() []Group {
	return c.groups.Groups()
}

// InGroup returns whether this Conn belongs to a Group
func (c *Conn) InGroup(g Group) bool {
	return c.groups.Has(g)
}

// AddGroup assigns given Group to Conn
func (c *Conn) AddGroup(g Group) {
	c.groups.Add(g)
}

// Stream returns a stream associated with this Conn
func (c *Conn) NewStream() (*Stream, error) {
	return c.swarm.NewStreamWithConn(c)
}

func (c *Conn) Streams() []*Stream {
	c.streamLock.RLock()
	defer c.streamLock.RUnlock()

	streams := make([]*Stream, 0, len(c.streams))
	for s := range c.streams {
		streams = append(streams, s)
	}
	return streams
}

// Close closes this connection
func (c *Conn) Close() error {
	// close streams
	streams := c.Streams()
	for _, s := range streams {
		s.Close()
	}

	// close underlying connection
	c.netConn.Close()
	return c.swarm.removeConn(c)
}

// ConnsWithGroup narrows down a set of connections to those in a given group.
func ConnsWithGroup(g Group, conns []*Conn) []*Conn {
	var out []*Conn
	for _, c := range conns {
		if c.InGroup(g) {
			out = append(out, c)
		}
	}
	return out
}

func ConnInConns(c1 *Conn, conns []*Conn) bool {
	for _, c2 := range conns {
		if c2 == c1 {
			return true
		}
	}
	return false
}

// ------------------------------------------------------------------
// All the connection setup logic here, in one place.
// these are mostly *Swarm methods, but i wanted a less-crowded place
// for them.
// ------------------------------------------------------------------

// addConn is the internal version of AddConn. we need the server bool
// as spdystream requires it.
func (s *Swarm) addConn(netConn net.Conn, server bool) (*Conn, error) {
	if netConn == nil {
		return nil, errors.New("nil conn")
	}

	// this function is so we can defer our lock, which needs to be
	// unlocked **before** the Handler is called (which needs to be
	// sequential). This was the simplest thing :)
	setupConn := func() (*Conn, error) {
		s.connLock.Lock()
		defer s.connLock.Unlock()

		// first, check if we already have it...
		for c := range s.conns {
			if c.netConn == netConn {
				s.connLock.Unlock()
				return c, nil
			}
		}

		// create a new spdystream connection
		ssConn, err := ss.NewConnection(netConn, server)
		if err != nil {
			s.connLock.Unlock()
			return nil, err
		}

		// add the connection
		c := newConn(netConn, ssConn, s)
		s.conns[c] = struct{}{}
		return c, nil
	}

	c, err := setupConn()
	if err != nil {
		return nil, err
	}

	s.ConnHandler()(c)

	// go listen for incoming streams on this connection
	go c.ssConn.Serve(func(ssS *ss.Stream) {
		// log.Printf("accepted stream %d from %s\n", ssS.Identifier(), netConn.RemoteAddr())
		ssS.SendReply(http.Header{}, false)
		stream := s.setupSSStream(ssS, c)
		s.StreamHandler()(stream) // call our handler
	})

	return c, nil
}

// createStream is the internal function that creates a new stream. assumes
// all validation has happened.
func (s *Swarm) createStream(c *Conn) (*Stream, error) {

	// Create a new ss.Stream
	ssStream, err := c.ssConn.CreateStream(http.Header{}, nil, false)
	if err != nil {
		return nil, err
	}

	// create a new stream
	return s.setupSSStream(ssStream, c), nil
}

// newStream is the internal function that creates a new stream. assumes
// all validation has happened.
func (s *Swarm) setupSSStream(ssS *ss.Stream, c *Conn) *Stream {
	// create a new *Stream
	stream := newStream(ssS, c)

	// add it to our streams maps

	// add it to our map
	s.streamLock.Lock()
	c.streamLock.Lock()
	s.streams[stream] = struct{}{}
	c.streams[stream] = struct{}{}
	s.streamLock.Unlock()
	c.streamLock.Unlock()
	return stream
}

func (s *Swarm) removeStream(stream *Stream) error {

	// remove from our maps
	s.streamLock.Lock()
	stream.conn.streamLock.Lock()
	delete(s.streams, stream)
	delete(stream.conn.streams, stream)
	s.streamLock.Unlock()
	stream.conn.streamLock.Unlock()

	return stream.ssStream.Close()
}

func (s *Swarm) removeConn(conn *Conn) error {
	// remove from our maps
	s.connLock.Lock()
	delete(s.conns, conn)
	s.connLock.Unlock()
	return nil
}
<commit_msg>superfluous unlocking race #2<commit_after>package peerstream

import (
	"errors"
	"net"
	"net/http"
	"sync"

	ss "github.com/docker/spdystream"
)

// ConnHandler is a function which receives a Conn. It allows
// clients to set a function to receive newly accepted
// connections. It works like StreamHandler, but is usually
// less useful than usual as most services will only use
// Streams. It is safe to pass or store the *Conn elsewhere.
// Note: the ConnHandler is called sequentially, so spawn
// goroutines or pass the Conn. See EchoHandler.
type ConnHandler func(s *Conn)

// SelectConn selects a connection out of list. It allows
// delegation of decision making to clients. Clients can
// make SelectConn functons that check things connection
// qualities -- like latency andbandwidth -- or pick from
// a logical set of connections.
type SelectConn func([]*Conn) *Conn

// ErrInvalidConnSelected signals that a connection selected
// with a SelectConn function is invalid. This may be due to
// the Conn not being part of the original set given to the
// function, or the value being nil.
var ErrInvalidConnSelected = errors.New("invalid selected connection")

// ErrNoConnections signals that no connections are available
var ErrNoConnections = errors.New("no connections")

// Conn is a Swarm-associated connection.
type Conn struct {
	ssConn  *ss.Connection
	netConn net.Conn // underlying connection

	swarm  *Swarm
	groups groupSet

	streams    map[*Stream]struct{}
	streamLock sync.RWMutex
}

func newConn(nconn net.Conn, sconn *ss.Connection, s *Swarm) *Conn {
	return &Conn{
		netConn: nconn,
		ssConn:  sconn,
		swarm:   s,
		groups:  groupSet{m: make(map[Group]struct{})},
		streams: make(map[*Stream]struct{}),
	}
}

// Swarm returns the Swarm associated with this Conn
func (c *Conn) Swarm() *Swarm {
	return c.swarm
}

// NetConn returns the underlying net.Conn
func (c *Conn) NetConn() net.Conn {
	return c.netConn
}

// SPDYConn returns the spdystream.Connection we use
// Warning: modifying this object is undefined.
func (c *Conn) SPDYConn() *ss.Connection {
	return c.ssConn
}

// Groups returns the Groups this Conn belongs to
func (c *Conn) Groups() []Group {
	return c.groups.Groups()
}

// InGroup returns whether this Conn belongs to a Group
func (c *Conn) InGroup(g Group) bool {
	return c.groups.Has(g)
}

// AddGroup assigns given Group to Conn
func (c *Conn) AddGroup(g Group) {
	c.groups.Add(g)
}

// Stream returns a stream associated with this Conn
func (c *Conn) NewStream() (*Stream, error) {
	return c.swarm.NewStreamWithConn(c)
}

func (c *Conn) Streams() []*Stream {
	c.streamLock.RLock()
	defer c.streamLock.RUnlock()

	streams := make([]*Stream, 0, len(c.streams))
	for s := range c.streams {
		streams = append(streams, s)
	}
	return streams
}

// Close closes this connection
func (c *Conn) Close() error {
	// close streams
	streams := c.Streams()
	for _, s := range streams {
		s.Close()
	}

	// close underlying connection
	c.netConn.Close()
	return c.swarm.removeConn(c)
}

// ConnsWithGroup narrows down a set of connections to those in a given group.
func ConnsWithGroup(g Group, conns []*Conn) []*Conn {
	var out []*Conn
	for _, c := range conns {
		if c.InGroup(g) {
			out = append(out, c)
		}
	}
	return out
}

func ConnInConns(c1 *Conn, conns []*Conn) bool {
	for _, c2 := range conns {
		if c2 == c1 {
			return true
		}
	}
	return false
}

// ------------------------------------------------------------------
// All the connection setup logic here, in one place.
// these are mostly *Swarm methods, but i wanted a less-crowded place
// for them.
// ------------------------------------------------------------------

// addConn is the internal version of AddConn. we need the server bool
// as spdystream requires it.
func (s *Swarm) addConn(netConn net.Conn, server bool) (*Conn, error) {
	if netConn == nil {
		return nil, errors.New("nil conn")
	}

	// this function is so we can defer our lock, which needs to be
	// unlocked **before** the Handler is called (which needs to be
	// sequential). This was the simplest thing :)
	setupConn := func() (*Conn, error) {
		s.connLock.Lock()
		defer s.connLock.Unlock()

		// first, check if we already have it...
		for c := range s.conns {
			if c.netConn == netConn {
				return c, nil
			}
		}

		// create a new spdystream connection
		ssConn, err := ss.NewConnection(netConn, server)
		if err != nil {
			return nil, err
		}

		// add the connection
		c := newConn(netConn, ssConn, s)
		s.conns[c] = struct{}{}
		return c, nil
	}

	c, err := setupConn()
	if err != nil {
		return nil, err
	}

	s.ConnHandler()(c)

	// go listen for incoming streams on this connection
	go c.ssConn.Serve(func(ssS *ss.Stream) {
		// log.Printf("accepted stream %d from %s\n", ssS.Identifier(), netConn.RemoteAddr())
		ssS.SendReply(http.Header{}, false)
		stream := s.setupSSStream(ssS, c)
		s.StreamHandler()(stream) // call our handler
	})

	return c, nil
}

// createStream is the internal function that creates a new stream. assumes
// all validation has happened.
func (s *Swarm) createStream(c *Conn) (*Stream, error) {

	// Create a new ss.Stream
	ssStream, err := c.ssConn.CreateStream(http.Header{}, nil, false)
	if err != nil {
		return nil, err
	}

	// create a new stream
	return s.setupSSStream(ssStream, c), nil
}

// newStream is the internal function that creates a new stream. assumes
// all validation has happened.
func (s *Swarm) setupSSStream(ssS *ss.Stream, c *Conn) *Stream {
	// create a new *Stream
	stream := newStream(ssS, c)

	// add it to our streams maps

	// add it to our map
	s.streamLock.Lock()
	c.streamLock.Lock()
	s.streams[stream] = struct{}{}
	c.streams[stream] = struct{}{}
	s.streamLock.Unlock()
	c.streamLock.Unlock()
	return stream
}

func (s *Swarm) removeStream(stream *Stream) error {

	// remove from our maps
	s.streamLock.Lock()
	stream.conn.streamLock.Lock()
	delete(s.streams, stream)
	delete(stream.conn.streams, stream)
	s.streamLock.Unlock()
	stream.conn.streamLock.Unlock()

	return stream.ssStream.Close()
}

func (s *Swarm) removeConn(conn *Conn) error {
	// remove from our maps
	s.connLock.Lock()
	delete(s.conns, conn)
	s.connLock.Unlock()
	return nil
}
<|endoftext|>


<commit_before>package epp

import (
	"encoding/binary"
	"encoding/xml"
	"io"
	"net"
	"sync"
	"time"
)

// IgnoreEOF returns err unless err == io.EOF,
// in which case it returns nil.
func IgnoreEOF(err error) error {
	if err == io.EOF {
		return nil
	}
	return err
}

// Conn represents a single connection to an EPP server.
// Reads and writes are serialized, so it is safe for concurrent use.
type Conn struct {
	// Conn is the underlying net.Conn (usually a TLS connection).
	net.Conn

	// Timeout defines the timeout for network operations.
	// It must be set at initialization. Changing it after
	// a connection is already opened will have no effect.
	Timeout time.Duration

	// m protects Greeting and LoginResult.
	m sync.Mutex

	// Greeting holds the last received greeting message from the server,
	// indicating server name, status, data policy and capabilities.
	//
	// Deprecated: This field is written to upon opening a new EPP connection and should not be modified.
	Greeting

	// LoginResult holds the last received login response message's Result
	// from the server, in which some servers might include diagnostics such
	// as connection count limits.
	//
	// Deprecated: this field is written to by the Login method but otherwise is not used by this package.
	LoginResult Result

	// mWrite synchronizes connection writes.
	mWrite sync.Mutex

	responses chan *Response
	readErr   error

	done chan struct{}
}

// NewConn initializes an epp.Conn from a net.Conn and performs the EPP
// handshake. It reads and stores the initial EPP <greeting> message.
// https://tools.ietf.org/html/rfc5730#section-2.4
func NewConn(conn net.Conn) (*Conn, error) {
	return NewTimeoutConn(conn, 0)
}

// NewTimeoutConn initializes an epp.Conn like NewConn, limiting the duration of network
// operations on conn using Set(Read|Write)Deadline.
func NewTimeoutConn(conn net.Conn, timeout time.Duration) (*Conn, error) {
	c := &Conn{
		Conn:      conn,
		Timeout:   timeout,
		responses: make(chan *Response),
		done:      make(chan struct{}),
	}
	go c.readLoop()
	g, err := c.readGreeting()
	if err == nil {
		c.m.Lock()
		c.Greeting = g
		c.m.Unlock()
	}
	return c, err
}

// Close sends an EPP <logout> command and closes the connection c.
func (c *Conn) Close() error {
	select {
	case <-c.done:
		return net.ErrClosed
	default:
	}
	c.Logout()
	close(c.done)
	return c.Conn.Close()
}

// writeRequest writes a single EPP request (x) for writing on c.
// writeRequest can be called from multiple goroutines.
func (c *Conn) writeRequest(x []byte) error {
	c.mWrite.Lock()
	defer c.mWrite.Unlock()
	if c.Timeout > 0 {
		c.Conn.SetWriteDeadline(time.Now().Add(c.Timeout))
	}
	return writeDataUnit(c.Conn, x)
}

// readResponse dequeues and returns a EPP response from c.
// It returns an error if the EPP response contains an error Result.
// readResponse can be called from multiple goroutines.
func (c *Conn) readResponse() (*Response, error) {
	select {
	case res := <-c.responses:
		if res == nil {
			return res, c.readErr
		}
		if res.Result.IsError() {
			return nil, &res.Result
		}
		return res, nil
	case <-c.done:
		return nil, net.ErrClosed
	}
}

func (c *Conn) readLoop() {
	defer close(c.responses)
	timeout := c.Timeout
	r := &io.LimitedReader{R: c.Conn}
	for {
		if timeout > 0 {
			c.Conn.SetReadDeadline(time.Now().Add(timeout))
		}
		n, err := readDataUnitHeader(c.Conn)
		if err != nil {
			c.readErr = err
			return
		}
		r.N = int64(n)
		res := &Response{}
		err = IgnoreEOF(scanResponse.Scan(xml.NewDecoder(r), res))
		if err != nil {
			c.readErr = err
			return
		}
		c.responses <- res
	}
}

// writeDataUnit writes x to w.
// Bytes written are prefixed with 32-bit header specifying the total size
// of the data unit (message + 4 byte header), in network (big-endian) order.
// http://www.ietf.org/rfc/rfc4934.txt
func writeDataUnit(w io.Writer, x []byte) error {
	logXML("<-- WRITE DATA UNIT -->", x)
	s := uint32(4 + len(x))
	err := binary.Write(w, binary.BigEndian, s)
	if err != nil {
		return err
	}
	_, err = w.Write(x)
	return err
}

// readDataUnitHeader reads a single EPP data unit header from r, returning the payload size or an error.
// An EPP data unit is prefixed with 32-bit header specifying the total size
// of the data unit (message + 4 byte header), in network (big-endian) order.
// http://www.ietf.org/rfc/rfc4934.txt
func readDataUnitHeader(r io.Reader) (uint32, error) {
	var n uint32
	err := binary.Read(r, binary.BigEndian, &n)
	if err != nil {
		return 0, err
	}
	if n < 4 {
		return 0, io.ErrUnexpectedEOF
	}
	// https://tools.ietf.org/html/rfc5734#section-4
	return n - 4, err
}
<commit_msg>eliminate read goroutine; just use a mutex<commit_after>package epp

import (
	"encoding/binary"
	"encoding/xml"
	"io"
	"net"
	"sync"
	"time"
)

// IgnoreEOF returns err unless err == io.EOF,
// in which case it returns nil.
func IgnoreEOF(err error) error {
	if err == io.EOF {
		return nil
	}
	return err
}

// Conn represents a single connection to an EPP server.
// Reads and writes are serialized, so it is safe for concurrent use.
type Conn struct {
	// Conn is the underlying net.Conn (usually a TLS connection).
	net.Conn

	// Timeout defines the timeout for network operations.
	// It must be set at initialization. Changing it after
	// a connection is already opened will have no effect.
	Timeout time.Duration

	// m protects Greeting and LoginResult.
	m sync.Mutex

	// Greeting holds the last received greeting message from the server,
	// indicating server name, status, data policy and capabilities.
	//
	// Deprecated: This field is written to upon opening a new EPP connection and should not be modified.
	Greeting

	// LoginResult holds the last received login response message's Result
	// from the server, in which some servers might include diagnostics such
	// as connection count limits.
	//
	// Deprecated: this field is written to by the Login method but otherwise is not used by this package.
	LoginResult Result

	// mRead synchronizes connection reads.
	mRead sync.Mutex

	// mWrite synchronizes connection writes.
	mWrite sync.Mutex

	done chan struct{}
}

// NewConn initializes an epp.Conn from a net.Conn and performs the EPP
// handshake. It reads and stores the initial EPP <greeting> message.
// https://tools.ietf.org/html/rfc5730#section-2.4
func NewConn(conn net.Conn) (*Conn, error) {
	return NewTimeoutConn(conn, 0)
}

// NewTimeoutConn initializes an epp.Conn like NewConn, limiting the duration of network
// operations on conn using Set(Read|Write)Deadline.
func NewTimeoutConn(conn net.Conn, timeout time.Duration) (*Conn, error) {
	c := &Conn{
		Conn:    conn,
		Timeout: timeout,
		done:    make(chan struct{}),
	}
	g, err := c.readGreeting()
	if err == nil {
		c.m.Lock()
		c.Greeting = g
		c.m.Unlock()
	}
	return c, err
}

// Close sends an EPP <logout> command and closes the connection c.
func (c *Conn) Close() error {
	select {
	case <-c.done:
		return net.ErrClosed
	default:
	}
	c.Logout()
	close(c.done)
	return c.Conn.Close()
}

// writeRequest writes a single EPP request (x) for writing on c.
// writeRequest can be called from multiple goroutines.
func (c *Conn) writeRequest(x []byte) error {
	c.mWrite.Lock()
	defer c.mWrite.Unlock()
	if c.Timeout > 0 {
		c.Conn.SetWriteDeadline(time.Now().Add(c.Timeout))
	}
	return writeDataUnit(c.Conn, x)
}

// readResponse dequeues and returns a EPP response from c.
// It returns an error if the EPP response contains an error Result.
// readResponse can be called from multiple goroutines.
func (c *Conn) readResponse() (*Response, error) {
	c.mRead.Lock()
	defer c.mRead.Unlock()
	if c.Timeout > 0 {
		c.Conn.SetReadDeadline(time.Now().Add(c.Timeout))
	}
	n, err := readDataUnitHeader(c.Conn)
	if err != nil {
		return nil, err
	}
	r := io.LimitedReader{R: c.Conn, N: int64(n)}
	res := &Response{}
	err = IgnoreEOF(scanResponse.Scan(xml.NewDecoder(&r), res))
	return res, err
}

// writeDataUnit writes x to w.
// Bytes written are prefixed with 32-bit header specifying the total size
// of the data unit (message + 4 byte header), in network (big-endian) order.
// http://www.ietf.org/rfc/rfc4934.txt
func writeDataUnit(w io.Writer, x []byte) error {
	logXML("<-- WRITE DATA UNIT -->", x)
	s := uint32(4 + len(x))
	err := binary.Write(w, binary.BigEndian, s)
	if err != nil {
		return err
	}
	_, err = w.Write(x)
	return err
}

// readDataUnitHeader reads a single EPP data unit header from r, returning the payload size or an error.
// An EPP data unit is prefixed with 32-bit header specifying the total size
// of the data unit (message + 4 byte header), in network (big-endian) order.
// http://www.ietf.org/rfc/rfc4934.txt
func readDataUnitHeader(r io.Reader) (uint32, error) {
	var n uint32
	err := binary.Read(r, binary.BigEndian, &n)
	if err != nil {
		return 0, err
	}
	if n < 4 {
		return 0, io.ErrUnexpectedEOF
	}
	// https://tools.ietf.org/html/rfc5734#section-4
	return n - 4, err
}
<|endoftext|>


<commit_before>/*
Open Source Initiative OSI - The MIT License (MIT):Licensing

The MIT License (MIT)
Copyright (c) 2013 DutchCoders <http://github.com/dutchcoders/>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package clamd

import (
	"bufio"
	"fmt"
	"io"
	"net"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

const CHUNK_SIZE = 1024
const TCP_TIMEOUT = time.Second * 2

var resultRegex = regexp.MustCompile(
	`^(?P<path>[^:]+): ((?P<desc>[^:]+)(\((?P<virhash>([^:]+)):(?P<virsize>\d+)\))? )?(?P<status>FOUND|ERROR|OK)$`,
)

type CLAMDConn struct {
	net.Conn
}

func (conn *CLAMDConn) sendCommand(command string) error {
	commandBytes := []byte(fmt.Sprintf("n%s\n", command))

	_, err := conn.Write(commandBytes)
	return err
}

func (conn *CLAMDConn) sendEOF() error {
	_, err := conn.Write([]byte{0, 0, 0, 0})
	return err
}

func (conn *CLAMDConn) sendChunk(data []byte) error {
	var buf [4]byte
	lenData := len(data)
	buf[0] = byte(lenData >> 24)
	buf[1] = byte(lenData >> 16)
	buf[2] = byte(lenData >> 8)
	buf[3] = byte(lenData >> 0)

	a := buf

	b := make([]byte, len(a))
	for i := range a {
		b[i] = a[i]
	}

	conn.Write(b)

	_, err := conn.Write(data)
	return err
}

func (c *CLAMDConn) readResponse() (chan *ScanResult, *sync.WaitGroup, error) {
	var wg sync.WaitGroup

	wg.Add(1)
	reader := bufio.NewReader(c)
	ch := make(chan *ScanResult)

	go func() {
		defer func() {
			close(ch)
			wg.Done()
		}()

		for {
			line, err := reader.ReadString('\n')
			if err == io.EOF {
				return
			}

			if err != nil {
				return
			}

			line = strings.TrimRight(line, " \t\r\n")
			ch <- parseResult(line)
		}
	}()

	return ch, &wg, nil
}

func parseResult(line string) *ScanResult {
	res := &ScanResult{}
	res.Raw = line

	matches := resultRegex.FindStringSubmatch(line)
	if len(matches) == 0 {
		res.Status = RES_PARSE_ERROR
		return res
	}

	for i, name := range resultRegex.SubexpNames() {
		switch name {
		case "path":
			res.Path = matches[i]
		case "desc":
			res.Description = matches[i]
		case "virhash":
			res.Hash = matches[i]
		case "virsize":
			i, err := strconv.Atoi(matches[i])
			if err == nil {
				res.Size = i
			}
		case "status":
			res.Status = matches[i]
		}
	}

	return res
}

func newCLAMDTcpConn(address string) (*CLAMDConn, error) {
	conn, err := net.DialTimeout("tcp", address, TCP_TIMEOUT)

	if err != nil {
		if nerr, isOk := err.(net.Error); isOk && nerr.Timeout() {
			return nil, nerr
		}

		return nil, err
	}

	return &CLAMDConn{Conn: conn}, err
}

func newCLAMDUnixConn(address string) (*CLAMDConn, error) {
	conn, err := net.Dial("unix", address)
	if err != nil {
		return nil, err
	}

	return &CLAMDConn{Conn: conn}, err
}
<commit_msg>Improve error handling on parse error<commit_after>/*
Open Source Initiative OSI - The MIT License (MIT):Licensing

The MIT License (MIT)
Copyright (c) 2013 DutchCoders <http://github.com/dutchcoders/>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package clamd

import (
	"bufio"
	"fmt"
	"io"
	"net"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

const CHUNK_SIZE = 1024
const TCP_TIMEOUT = time.Second * 2

var resultRegex = regexp.MustCompile(
	`^(?P<path>[^:]+): ((?P<desc>[^:]+)(\((?P<virhash>([^:]+)):(?P<virsize>\d+)\))? )?(?P<status>FOUND|ERROR|OK)$`,
)

type CLAMDConn struct {
	net.Conn
}

func (conn *CLAMDConn) sendCommand(command string) error {
	commandBytes := []byte(fmt.Sprintf("n%s\n", command))

	_, err := conn.Write(commandBytes)
	return err
}

func (conn *CLAMDConn) sendEOF() error {
	_, err := conn.Write([]byte{0, 0, 0, 0})
	return err
}

func (conn *CLAMDConn) sendChunk(data []byte) error {
	var buf [4]byte
	lenData := len(data)
	buf[0] = byte(lenData >> 24)
	buf[1] = byte(lenData >> 16)
	buf[2] = byte(lenData >> 8)
	buf[3] = byte(lenData >> 0)

	a := buf

	b := make([]byte, len(a))
	for i := range a {
		b[i] = a[i]
	}

	conn.Write(b)

	_, err := conn.Write(data)
	return err
}

func (c *CLAMDConn) readResponse() (chan *ScanResult, *sync.WaitGroup, error) {
	var wg sync.WaitGroup

	wg.Add(1)
	reader := bufio.NewReader(c)
	ch := make(chan *ScanResult)

	go func() {
		defer func() {
			close(ch)
			wg.Done()
		}()

		for {
			line, err := reader.ReadString('\n')
			if err == io.EOF {
				return
			}

			if err != nil {
				return
			}

			line = strings.TrimRight(line, " \t\r\n")
			ch <- parseResult(line)
		}
	}()

	return ch, &wg, nil
}

func parseResult(line string) *ScanResult {
	res := &ScanResult{}
	res.Raw = line

	matches := resultRegex.FindStringSubmatch(line)
	if len(matches) == 0 {
		res.Description = "Regex had no matches"
		res.Status = RES_PARSE_ERROR
		return res
	}

	for i, name := range resultRegex.SubexpNames() {
		switch name {
		case "path":
			res.Path = matches[i]
		case "desc":
			res.Description = matches[i]
		case "virhash":
			res.Hash = matches[i]
		case "virsize":
			i, err := strconv.Atoi(matches[i])
			if err == nil {
				res.Size = i
			}
		case "status":
			switch matches[i] {
			case RES_OK:
			case RES_FOUND:
			case RES_ERROR:
				break
			default:
				res.Description = "Invalid status field: " + matches[i]
				res.Status = RES_PARSE_ERROR
				return res
			}
			res.Status = matches[i]
		}
	}

	return res
}

func newCLAMDTcpConn(address string) (*CLAMDConn, error) {
	conn, err := net.DialTimeout("tcp", address, TCP_TIMEOUT)

	if err != nil {
		if nerr, isOk := err.(net.Error); isOk && nerr.Timeout() {
			return nil, nerr
		}

		return nil, err
	}

	return &CLAMDConn{Conn: conn}, err
}

func newCLAMDUnixConn(address string) (*CLAMDConn, error) {
	conn, err := net.Dial("unix", address)
	if err != nil {
		return nil, err
	}

	return &CLAMDConn{Conn: conn}, err
}
<|endoftext|>


<commit_before>/*
	THIS IS NOT DONE AT ALL! USE AT YOUR OWN RISK!

	- it would be nice if cgo could grok several .go files,
	so far it can't; so all the C interface stuff has to be
	in one file; bummer that
*/

package sqlite3

/*
#include <stdlib.h>
#include "wrapper.h"
*/
import "C"
import "unsafe"

import "fmt"
import "os"
import "strconv"

/* these are not exported yet since I am not sure they are needed */
const (
	sqliteOk = iota; /* Successful result */
	sqliteError; /* SQL error or missing database */
	sqliteInternal; /* Internal logic error in SQLite */
	sqlitePerm; /* Access permission denied */
	sqliteAbort; /* Callback routine requested an abort */
	sqliteBusy; /* The database file is locked */
	sqliteLocked; /* A table in the database is locked */
	sqliteNomem; /* A malloc() failed */
	sqliteReadonly; /* Attempt to write a readonly database */
	sqliteInterrupt; /* Operation terminated by sqlite3_interrupt()*/
	sqliteIoerr; /* Some kind of disk I/O error occurred */
	sqliteCorrupt; /* The database disk image is malformed */
	sqlite_Notfound; /* NOT USED. Table or record not found */
	sqliteFull; /* Insertion failed because database is full */
	sqliteCantopen; /* Unable to open the database file */
	sqliteProtocol; /* NOT USED. Database lock protocol error */
	sqliteEmpty; /* Database is empty */
	sqliteSchema; /* The database schema changed */
	sqliteToobig; /* String or BLOB exceeds size limit */
	sqliteConstraint; /* Abort due to constraint violation */
	sqliteMismatch; /* Data type mismatch */
	sqliteMisuse; /* Library used incorrectly */
	sqliteNolfs; /* Uses OS features not supported on host */
	sqliteAuth; /* Authorization denied */
	sqliteFormat; /* Auxiliary database format error */
	sqliteRange; /* 2nd parameter to sqlite3_bind out of range */
	sqliteNotadb; /* File opened that is not a database file */
	sqliteRow = 100; /* sqlite3_step() has another row ready */
	sqliteDone = 101;  /* sqlite3_step() has finished executing */
)

type Connection struct {
	/* pointer to struct sqlite3 */
	handle C.wsq_db;
}

type Cursor struct {
	/* pointer to struct sqlite3_stmt */
	handle C.wsq_st;
	/* connection we were created on */
	connection *Connection;
	/* the last query yielded results */
	result bool;
}

func Version() (data map[string]string, error os.Error)
{
	data = make(map[string]string);

	cp := C.wsq_libversion();
	if (cp == nil) {
		error = &InterfaceError{"Version: couldn't get library version!"};
		return;
	}
	data["version"] = C.GoString(cp);
	// TODO: fake client and server keys?

	cp = C.wsq_sourceid();
	if (cp != nil) {
		data["sqlite3.sourceid"] = C.GoString(cp);
	}

	i := C.wsq_libversion_number();
	data["sqlite3.versionnumber"] = strconv.Itob(int(i), 10);

	return;
}

type Any interface{};
type ConnectionInfo map[string] Any;

func parseConnInfo(info ConnectionInfo) (name string, flags int, vfs *string, error os.Error)
{
	ok := false;
	any := Any(nil);

	any, ok = info["name"];
	if !ok {
		error = &InterfaceError{"Open: No \"name\" in arguments map."};
		return;
	}
	name, ok = any.(string);
	if !ok {
		error = &InterfaceError{"Open: \"name\" argument not a string."};
		return;
	}

	any, ok = info["sqlite.flags"];
	if ok {
		flags = any.(int);
	}

	any, ok = info["sqlite.vfs"];
	if ok {
		vfs = new(string);
		*vfs = any.(string);
	}

	return;
}

func Open(info ConnectionInfo) (conn *Connection, error os.Error)
{
	name, flags, vfs, error := parseConnInfo(info);
	if error != nil {
		return;
	}

	conn = new(Connection);

	rc := sqliteOk;
	p := C.CString(name);

	if vfs != nil {
		q := C.CString(*vfs);
		rc = int(C.wsq_open(p, &conn.handle, C.int(flags), q));
		C.free(unsafe.Pointer(q));
	}
	else {
		rc = int(C.wsq_open(p, &conn.handle, C.int(flags), nil));
	}

	C.free(unsafe.Pointer(p));
	if rc != sqliteOk {
		error = conn.error();
	}

	return;
}

func (self *Connection) error() (error os.Error) {
	e := new(DatabaseError);
	e.basic = int(C.wsq_errcode(self.handle));
	e.extended = int(C.wsq_extended_errcode(self.handle));
	e.message = C.GoString(C.wsq_errmsg(self.handle));
	return e;
}

func (self *Connection) Cursor() (cursor *Cursor, error os.Error) {
	cursor = new(Cursor);
	cursor.connection = self;
	return;
}

func (self *Connection) Close() (error os.Error) {
	rc := C.wsq_close(self.handle);
	if rc != sqliteOk {
		error = self.error();
	}
	return;
}

func (self *Cursor) Execute(query string, parameters ...) (error os.Error) {
	query = fmt.Sprintf(query, parameters);

	q := C.CString(query);

	rc := C.wsq_prepare(self.connection.handle, q, -1, &self.handle, nil);
	if rc != sqliteOk {
		error = self.connection.error();
		if self.handle != nil {
			// TODO: finalize
		}
		return;
	}

	rc = C.wsq_step(self.handle);
	switch rc {
		case sqliteDone:
			self.result = false;
			// TODO: finalize
		case sqliteRow:
			self.result = true;
			// TODO: obtain results somehow? or later call?
		default:
			error = self.connection.error();
			// TODO: finalize
			return;
	}

	C.free(unsafe.Pointer(q));
	return;
}

func (self *Cursor) FetchOne() (data []interface{}, error os.Error) {
	if !self.result {
		error = &InterfaceError{"FetchOne: No results to fetch!"};
		return;
	}

	nColumns := int(C.wsq_column_count(self.handle));
	if nColumns <= 0 {
		error = &InterfaceError{"FetchOne: No columns in result!"};
		return;
	}

	data = make([]interface{}, nColumns);
	for i := 0; i < nColumns; i++ {
		text := C.wsq_column_text(self.handle, C.int(i));
		data[i] = C.GoString(text);
	}

	rc := C.wsq_step(self.handle);
	switch rc {
		case sqliteDone:
			self.result = false;
			// TODO: finalize
		case sqliteRow:
			self.result = true;
		default:
			error = self.connection.error();
			// TODO: finalize
			return;
	}

	return;
}
func (self *Cursor) FetchRow() (data map[string]interface{}, error os.Error) {
	if !self.result {
		error = &InterfaceError{"FetchRow: No results to fetch!"};
		return;
	}

	nColumns := int(C.wsq_column_count(self.handle));
	if nColumns <= 0 {
		error = &InterfaceError{"FetchRow: No columns in result!"};
		return;
	}

	data = make(map[string]interface{}, nColumns);
	for i := 0; i < nColumns; i++ {
		text := C.wsq_column_text(self.handle, C.int(i));
		name := C.wsq_column_name(self.handle, C.int(i));
		data[C.GoString(name)] = C.GoString(text);
	}

	rc := C.wsq_step(self.handle);
	switch rc {
		case sqliteDone:
			self.result = false;
			// TODO: finalize
		case sqliteRow:
			self.result = true;
		default:
			error = self.connection.error();
			// TODO: finalize
			return;
	}

	return;
}

func (self *Cursor) Close() (error os.Error) {
	if self.handle != nil {
		rc := C.wsq_finalize(self.handle);
		if rc != sqliteOk {
			error = self.connection.error();
		}
	}
	return;
}
<commit_msg>Added flags for Open(). Added default busy timeout on all connections.<commit_after>/*
	THIS IS NOT DONE AT ALL! USE AT YOUR OWN RISK!

	- it would be nice if cgo could grok several .go files,
	so far it can't; so all the C interface stuff has to be
	in one file; bummer that
*/

package sqlite3

/*
#include <stdlib.h>
#include "wrapper.h"
*/
import "C"
import "unsafe"

import "fmt"
import "os"
import "strconv"

/* these are not exported yet since I am not sure they are needed */
const (
	sqliteOk = iota; /* Successful result */
	sqliteError; /* SQL error or missing database */
	sqliteInternal; /* Internal logic error in SQLite */
	sqlitePerm; /* Access permission denied */
	sqliteAbort; /* Callback routine requested an abort */
	sqliteBusy; /* The database file is locked */
	sqliteLocked; /* A table in the database is locked */
	sqliteNomem; /* A malloc() failed */
	sqliteReadonly; /* Attempt to write a readonly database */
	sqliteInterrupt; /* Operation terminated by sqlite3_interrupt()*/
	sqliteIoerr; /* Some kind of disk I/O error occurred */
	sqliteCorrupt; /* The database disk image is malformed */
	sqlite_Notfound; /* NOT USED. Table or record not found */
	sqliteFull; /* Insertion failed because database is full */
	sqliteCantopen; /* Unable to open the database file */
	sqliteProtocol; /* NOT USED. Database lock protocol error */
	sqliteEmpty; /* Database is empty */
	sqliteSchema; /* The database schema changed */
	sqliteToobig; /* String or BLOB exceeds size limit */
	sqliteConstraint; /* Abort due to constraint violation */
	sqliteMismatch; /* Data type mismatch */
	sqliteMisuse; /* Library used incorrectly */
	sqliteNolfs; /* Uses OS features not supported on host */
	sqliteAuth; /* Authorization denied */
	sqliteFormat; /* Auxiliary database format error */
	sqliteRange; /* 2nd parameter to sqlite3_bind out of range */
	sqliteNotadb; /* File opened that is not a database file */
	sqliteRow = 100; /* sqlite3_step() has another row ready */
	sqliteDone = 101;  /* sqlite3_step() has finished executing */
)

/*
	These constants can be or'd together and passed as the
	"sqlite3.flags" argument to Open(). Some of them only
	apply if "sqlite3.vfs" is also passed. See the SQLite
	documentation for details.
*/
const (
	OpenReadOnly = 0x00000001;
	OpenReadWrite = 0x00000002;
	OpenCreate = 0x00000004;
	OpenDeleteOnClose = 0x00000008;  /* VFS only */
	OpenExclusive = 0x00000010;  /* VFS only */
	OpenMainDb = 0x00000100;  /* VFS only */
	OpenTempDb = 0x00000200;  /* VFS only */
	OpenTransientDb = 0x00000400;  /* VFS only */
	OpenMainJournal = 0x00000800;  /* VFS only */
	OpenTempJournal = 0x00001000;  /* VFS only */
	OpenSubJournal = 0x00002000;  /* VFS only */
	OpenMasterJournal = 0x00004000;  /* VFS only */
	OpenNoMutex = 0x00008000;
	OpenFullMutex = 0x00010000;
	OpenSharedCache = 0x00020000;
	OpenPrivateCache = 0x00040000;
)

const defaultTimeoutMilliseconds = 16*1000;

type Connection struct {
	/* pointer to struct sqlite3 */
	handle C.wsq_db;
}

type Cursor struct {
	/* pointer to struct sqlite3_stmt */
	handle C.wsq_st;
	/* connection we were created on */
	connection *Connection;
	/* the last query yielded results */
	result bool;
}

func Version() (data map[string]string, error os.Error)
{
	data = make(map[string]string);

	cp := C.wsq_libversion();
	if (cp == nil) {
		error = &InterfaceError{"Version: couldn't get library version!"};
		return;
	}
	data["version"] = C.GoString(cp);
	// TODO: fake client and server keys?

	cp = C.wsq_sourceid();
	if (cp != nil) {
		data["sqlite3.sourceid"] = C.GoString(cp);
	}

	i := C.wsq_libversion_number();
	data["sqlite3.versionnumber"] = strconv.Itob(int(i), 10);

	return;
}

type Any interface{};
type ConnectionInfo map[string] Any;

func parseConnInfo(info ConnectionInfo) (name string, flags int, vfs *string, error os.Error)
{
	ok := false;
	any := Any(nil);

	any, ok = info["name"];
	if !ok {
		error = &InterfaceError{"Open: No \"name\" in arguments map."};
		return;
	}
	name, ok = any.(string);
	if !ok {
		error = &InterfaceError{"Open: \"name\" argument not a string."};
		return;
	}

	any, ok = info["sqlite.flags"];
	if ok {
		flags = any.(int);
	}

	any, ok = info["sqlite.vfs"];
	if ok {
		vfs = new(string);
		*vfs = any.(string);
	}

	return;
}

func Open(info ConnectionInfo) (conn *Connection, error os.Error)
{
	name, flags, vfs, error := parseConnInfo(info);
	if error != nil {
		return;
	}

	conn = new(Connection);

	rc := sqliteOk;
	p := C.CString(name);

	if vfs != nil {
		q := C.CString(*vfs);
		rc = int(C.wsq_open(p, &conn.handle, C.int(flags), q));
		C.free(unsafe.Pointer(q));
	}
	else {
		rc = int(C.wsq_open(p, &conn.handle, C.int(flags), nil));
	}

	C.free(unsafe.Pointer(p));
	if rc != sqliteOk {
		error = conn.error();
	}
	else {
		rc := C.wsq_busy_timeout(conn.handle, defaultTimeoutMilliseconds);
		if rc != sqliteOk {
			error = conn.error();
		}
	}

	return;
}

func (self *Connection) error() (error os.Error) {
	e := new(DatabaseError);
	e.basic = int(C.wsq_errcode(self.handle));
	e.extended = int(C.wsq_extended_errcode(self.handle));
	e.message = C.GoString(C.wsq_errmsg(self.handle));
	return e;
}

func (self *Connection) Cursor() (cursor *Cursor, error os.Error) {
	cursor = new(Cursor);
	cursor.connection = self;
	return;
}

func (self *Connection) Close() (error os.Error) {
	rc := C.wsq_close(self.handle);
	if rc != sqliteOk {
		error = self.error();
	}
	return;
}

func (self *Cursor) Execute(query string, parameters ...) (error os.Error) {
	query = fmt.Sprintf(query, parameters);

	q := C.CString(query);

	rc := C.wsq_prepare(self.connection.handle, q, -1, &self.handle, nil);
	if rc != sqliteOk {
		error = self.connection.error();
		if self.handle != nil {
			// TODO: finalize
		}
		return;
	}

	rc = C.wsq_step(self.handle);
	switch rc {
		case sqliteDone:
			self.result = false;
			// TODO: finalize
		case sqliteRow:
			self.result = true;
			// TODO: obtain results somehow? or later call?
		default:
			error = self.connection.error();
			// TODO: finalize
			return;
	}

	C.free(unsafe.Pointer(q));
	return;
}

func (self *Cursor) FetchOne() (data []interface{}, error os.Error) {
	if !self.result {
		error = &InterfaceError{"FetchOne: No results to fetch!"};
		return;
	}

	nColumns := int(C.wsq_column_count(self.handle));
	if nColumns <= 0 {
		error = &InterfaceError{"FetchOne: No columns in result!"};
		return;
	}

	data = make([]interface{}, nColumns);
	for i := 0; i < nColumns; i++ {
		text := C.wsq_column_text(self.handle, C.int(i));
		data[i] = C.GoString(text);
	}

	rc := C.wsq_step(self.handle);
	switch rc {
		case sqliteDone:
			self.result = false;
			// TODO: finalize
		case sqliteRow:
			self.result = true;
		default:
			error = self.connection.error();
			// TODO: finalize
			return;
	}

	return;
}
func (self *Cursor) FetchRow() (data map[string]interface{}, error os.Error) {
	if !self.result {
		error = &InterfaceError{"FetchRow: No results to fetch!"};
		return;
	}

	nColumns := int(C.wsq_column_count(self.handle));
	if nColumns <= 0 {
		error = &InterfaceError{"FetchRow: No columns in result!"};
		return;
	}

	data = make(map[string]interface{}, nColumns);
	for i := 0; i < nColumns; i++ {
		text := C.wsq_column_text(self.handle, C.int(i));
		name := C.wsq_column_name(self.handle, C.int(i));
		data[C.GoString(name)] = C.GoString(text);
	}

	rc := C.wsq_step(self.handle);
	switch rc {
		case sqliteDone:
			self.result = false;
			// TODO: finalize
		case sqliteRow:
			self.result = true;
		default:
			error = self.connection.error();
			// TODO: finalize
			return;
	}

	return;
}

func (self *Cursor) Close() (error os.Error) {
	if self.handle != nil {
		rc := C.wsq_finalize(self.handle);
		if rc != sqliteOk {
			error = self.connection.error();
		}
	}
	return;
}
<|endoftext|>


<commit_before>// This library implements a cron spec parser and runner.  See the README for
// more details.
package cron

import (
	"sort"
	"time"
)

// Cron keeps track of any number of entries, invoking the associated func as
// specified by the schedule. It may be started, stopped, and the entries may
// be inspected while running.
type Cron struct {
	entries  []*Entry
	stop     chan struct{}
	add      chan *Entry
	remove   chan *Entry
	snapshot chan []*Entry
	running  bool
}

// Job is an interface for submitted cron jobs.
type Job interface {
	Run()
}

// The Schedule describes a job's duty cycle.
type Schedule interface {
	// Return the next activation time, later than the given time.
	// Next is invoked initially, and then each time the job is run.
	Next(time.Time) time.Time
}

// Entry consists of a schedule and the func to execute on that schedule.
type Entry struct {
	// The schedule on which this job should be run.
	Schedule Schedule

	// The next time the job will run. This is the zero time if Cron has not been
	// started or this entry's schedule is unsatisfiable
	Next time.Time

	// The last time this job was run. This is the zero time if the job has never
	// been run.
	Prev time.Time

	// The Job to run.
	Job Job
}

// byTime is a wrapper for sorting the entry array by time
// (with zero time at the end).
type byTime []*Entry

func (s byTime) Len() int      { return len(s) }
func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byTime) Less(i, j int) bool {
	// Two zero times should return false.
	// Otherwise, zero is "greater" than any other time.
	// (To sort it at the end of the list.)
	if s[i].Next.IsZero() {
		return false
	}
	if s[j].Next.IsZero() {
		return true
	}
	return s[i].Next.Before(s[j].Next)
}

// New returns a new Cron job runner.
func New() *Cron {
	return &Cron{
		entries:  nil,
		add:      make(chan *Entry),
		remove:   make(chan *Entry),
		stop:     make(chan struct{}),
		snapshot: make(chan []*Entry),
		running:  false,
	}
}

// A wrapper that turns a func() into a cron.Job
type FuncJob func()

func (f FuncJob) Run() { f() }

// AddFunc adds a func to the Cron to be run on the given schedule.
func (c *Cron) AddFunc(spec string, cmd func()) error {
	return c.AddJob(spec, FuncJob(cmd))
}

// AddFunc adds a Job to the Cron to be run on the given schedule.
func (c *Cron) AddJob(spec string, cmd Job) error {
	schedule, err := Parse(spec)
	if err != nil {
		return err
	}
	c.Schedule(schedule, cmd)
	return nil
}

// Schedule adds a Job to the Cron to be run on the given schedule.
func (c *Cron) Schedule(schedule Schedule, cmd Job) *Entry {
	entry := &Entry{
		Schedule: schedule,
		Job:      cmd,
	}
	if !c.running {
		c.entries = append(c.entries, entry)
		return entry
	}

	c.add <- entry
	return entry
}

// Entries returns a snapshot of the cron entries.
func (c *Cron) Entries() []*Entry {
	if c.running {
		c.snapshot <- nil
		x := <-c.snapshot
		return x
	}
	return c.entrySnapshot()
}

// Start the cron scheduler in its own go-routine.
func (c *Cron) Start() {
	c.running = true
	go c.run()
}

// Run the scheduler.. this is private just due to the need to synchronize
// access to the 'running' state variable.
func (c *Cron) run() {
	// Figure out the next activation times for each entry.
	now := time.Now().Local()
	for _, entry := range c.entries {
		entry.Next = entry.Schedule.Next(now)
	}

	for {
		// Determine the next entry to run.
		sort.Sort(byTime(c.entries))

		var effective time.Time
		if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
			// If there are no entries yet, just sleep - it still handles new entries
			// and stop requests.
			effective = now.AddDate(10, 0, 0)
		} else {
			effective = c.entries[0].Next
		}

		select {
		case now = <-time.After(effective.Sub(now)):
			// Run every entry whose next time was this effective time.
			for _, e := range c.entries {
				if e.Next != effective {
					break
				}
				go e.Job.Run()
				e.Prev = e.Next
				e.Next = e.Schedule.Next(effective)
			}
			continue

		case newEntry := <-c.add:
			c.entries = append(c.entries, newEntry)
			newEntry.Next = newEntry.Schedule.Next(now)
		case ne := <-c.remove:
			i := -1
			for k, e := range c.entries {
				if e == ne {
					i = k
				}
			}
			if i > -1 {
				c.entries = append(c.entries[:i], c.entries[i+1:]...)
			}
		case <-c.snapshot:
			c.snapshot <- c.entrySnapshot()
		case <-c.stop:
			return
		}

		// 'now' should be updated after newEntry and snapshot cases.
		now = time.Now().Local()
	}
}

// Stop the cron scheduler.
func (c *Cron) Stop() {
	c.stop <- struct{}{}
	c.running = false
}

// Remove the entry from cron entries
func (c *Cron) Remove(e *Entry) {
	c.remove <- e
}

// entrySnapshot returns a copy of the current cron entry list.
func (c *Cron) entrySnapshot() []*Entry {
	entries := []*Entry{}
	for _, e := range c.entries {
		entries = append(entries, &Entry{
			Schedule: e.Schedule,
			Next:     e.Next,
			Prev:     e.Prev,
			Job:      e.Job,
		})
	}
	return entries
}
<commit_msg>fix bug<commit_after>// This library implements a cron spec parser and runner.  See the README for
// more details.
package cron

import (
	"sort"
	"time"
)

// Cron keeps track of any number of entries, invoking the associated func as
// specified by the schedule. It may be started, stopped, and the entries may
// be inspected while running.
type Cron struct {
	entries  []*Entry
	stop     chan struct{}
	add      chan *Entry
	remove   chan *Entry
	snapshot chan []*Entry
	running  bool
}

// Job is an interface for submitted cron jobs.
type Job interface {
	Run()
}

// The Schedule describes a job's duty cycle.
type Schedule interface {
	// Return the next activation time, later than the given time.
	// Next is invoked initially, and then each time the job is run.
	Next(time.Time) time.Time
}

// Entry consists of a schedule and the func to execute on that schedule.
type Entry struct {
	// The schedule on which this job should be run.
	Schedule Schedule

	// The next time the job will run. This is the zero time if Cron has not been
	// started or this entry's schedule is unsatisfiable
	Next time.Time

	// The last time this job was run. This is the zero time if the job has never
	// been run.
	Prev time.Time

	// The Job to run.
	Job Job
}

// byTime is a wrapper for sorting the entry array by time
// (with zero time at the end).
type byTime []*Entry

func (s byTime) Len() int      { return len(s) }
func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byTime) Less(i, j int) bool {
	// Two zero times should return false.
	// Otherwise, zero is "greater" than any other time.
	// (To sort it at the end of the list.)
	if s[i].Next.IsZero() {
		return false
	}
	if s[j].Next.IsZero() {
		return true
	}
	return s[i].Next.Before(s[j].Next)
}

// New returns a new Cron job runner.
func New() *Cron {
	return &Cron{
		entries:  nil,
		add:      make(chan *Entry),
		remove:   make(chan *Entry),
		stop:     make(chan struct{}),
		snapshot: make(chan []*Entry),
		running:  false,
	}
}

// A wrapper that turns a func() into a cron.Job
type FuncJob func()

func (f FuncJob) Run() { f() }

// AddFunc adds a func to the Cron to be run on the given schedule.
func (c *Cron) AddFunc(spec string, cmd func()) error {
	return c.AddJob(spec, FuncJob(cmd))
}

// AddFunc adds a Job to the Cron to be run on the given schedule.
func (c *Cron) AddJob(spec string, cmd Job) error {
	schedule, err := Parse(spec)
	if err != nil {
		return err
	}
	c.Schedule(schedule, cmd)
	return nil
}

// Schedule adds a Job to the Cron to be run on the given schedule.
func (c *Cron) Schedule(schedule Schedule, cmd Job) *Entry {
	entry := &Entry{
		Schedule: schedule,
		Job:      cmd,
	}
	if !c.running {
		c.entries = append(c.entries, entry)
		return entry
	}

	c.add <- entry
	return entry
}

// Entries returns a snapshot of the cron entries.
func (c *Cron) Entries() []*Entry {
	if c.running {
		c.snapshot <- nil
		x := <-c.snapshot
		return x
	}
	return c.entrySnapshot()
}

// Start the cron scheduler in its own go-routine.
func (c *Cron) Start() {
	c.running = true
	go c.run()
}

// Run the scheduler.. this is private just due to the need to synchronize
// access to the 'running' state variable.
func (c *Cron) run() {
	// Figure out the next activation times for each entry.
	now := time.Now().Local()
	for _, entry := range c.entries {
		entry.Next = entry.Schedule.Next(now)
	}

	for {
		// Determine the next entry to run.
		sort.Sort(byTime(c.entries))

		var effective time.Time
		if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
			// If there are no entries yet, just sleep - it still handles new entries
			// and stop requests.
			effective = now.AddDate(10, 0, 0)
		} else {
			effective = c.entries[0].Next
		}

		select {
		case now = <-time.After(effective.Sub(now)):
			// Run every entry whose next time was this effective time.
			for _, e := range c.entries {
				if e.Next != effective {
					break
				}
				go e.Job.Run()
				e.Prev = e.Next
				e.Next = e.Schedule.Next(effective)
			}
			continue

		case newEntry := <-c.add:
			c.entries = append(c.entries, newEntry)
			newEntry.Next = newEntry.Schedule.Next(time.Now().Local())
		case ne := <-c.remove:
			i := -1
			for k, e := range c.entries {
				if e == ne {
					i = k
				}
			}
			if i > -1 {
				c.entries = append(c.entries[:i], c.entries[i+1:]...)
			}
		case <-c.snapshot:
			c.snapshot <- c.entrySnapshot()
		case <-c.stop:
			return
		}

		// 'now' should be updated after newEntry and snapshot cases.
		now = time.Now().Local()
	}
}

// Stop the cron scheduler.
func (c *Cron) Stop() {
	c.stop <- struct{}{}
	c.running = false
}

// Remove the entry from cron entries
func (c *Cron) Remove(e *Entry) {
	c.remove <- e
}

// entrySnapshot returns a copy of the current cron entry list.
func (c *Cron) entrySnapshot() []*Entry {
	entries := []*Entry{}
	for _, e := range c.entries {
		entries = append(entries, &Entry{
			Schedule: e.Schedule,
			Next:     e.Next,
			Prev:     e.Prev,
			Job:      e.Job,
		})
	}
	return entries
}
<|endoftext|>