<commit_before>package gop

import (
	"strings"
	"sync"
	"time"

	"github.com/cactus/go-statsd-client/statsd"
)

type StatsdClient struct {
	sync.Mutex
	client      statsd.Statter
	hostPort    string
	prefix      string
	rate        float32
	app         *App
	lastAttempt time.Time
	retryDur    time.Duration
}

func (a *App) initStatsd() {
	a.configureStatsd(nil)
	a.Cfg.AddOnChangeCallback(a.configureStatsd)
}

func (a *App) configureStatsd(cfg *Config) {
	statsdHostport, _ := a.Cfg.Get("gop", "statsd_hostport", "localhost:8125")
	prefix := []string{}
	if p, ok := a.Cfg.Get("gop", "statsd_prefix", ""); ok {
		prefix = append(prefix, p)
	}
	hostname := strings.Replace(a.Hostname(), ".", "_", -1)
	prefix = append(prefix, a.ProjectName, a.AppName, hostname)
	statsdPrefix := strings.Join(prefix, ".")
	rate, _ := a.Cfg.GetFloat32("gop", "statsd_rate", 1.0)
	retryDur, _ := a.Cfg.GetDuration("gop", "statsd_rate", time.Minute*1)
	// TODO: Need to protect a.Stats from race
	a.Stats = StatsdClient{
		client:   nil,
		hostPort: statsdHostport,
		prefix:   statsdPrefix,
		rate:     rate,
		app:      a,
		retryDur: retryDur,
	}
	a.Stats.connect()
}

func (s *StatsdClient) connect() bool {
	s.Lock()
	defer s.Unlock()
	if s.client != nil {
		return true
	}
	now := time.Now()
	if now.Sub(s.lastAttempt) < s.retryDur {
		return false
	}
	var err error
	s.client, err = statsd.New(s.hostPort, s.prefix)
	s.lastAttempt = now
	if err != nil {
		s.app.Error("STATSD Failed to create client (stats will noop): " + err.Error())
		//client, err = statsd.NewNoopClient()
		return false
	}
	s.app.Infof("STATSD sending to [%s] with prefix [%s] at rate [%f]", s.hostPort, s.prefix, s.rate)
	return true
}

func (s *StatsdClient) Dec(stat string, value int64) {
	s.app.Fine("STATSD DEC %s %d", stat, value)
	if !s.connect() {
		return
	}
	_ = s.client.Dec(stat, value, s.rate)
}

func (s *StatsdClient) Gauge(stat string, value int64) {
	s.app.Fine("STATSD GAUGE %s %d", stat, value)
	if !s.connect() {
		return
	}
	_ = s.client.Gauge(stat, value, s.rate)
}

func (s *StatsdClient) GaugeDelta(stat string, value int64) {
	s.app.Fine("STATSD GAUGEDELTA %s %d", stat, value)
	if !s.connect() {
		return
	}
	_ = s.client.GaugeDelta(stat, value, s.rate)
}

func (s *StatsdClient) Inc(stat string, value int64) {
	s.app.Fine("STATSD INC %s %d", stat, value)
	if !s.connect() {
		return
	}
	_ = s.client.Inc(stat, value, s.rate)
}

func (s *StatsdClient) Timing(stat string, delta int64) {
	s.app.Fine("STATSD TIMING %s %d", stat, delta)
	if !s.connect() {
		return
	}
	_ = s.client.Timing(stat, delta, s.rate)
}

func (s *StatsdClient) TimingDuration(stat string, delta time.Duration) {
	s.app.Fine("STATSD TIMING %s %s", stat, delta)
	if !s.connect() {
		return
	}
	_ = s.client.TimingDuration(stat, delta, s.rate)
}

// TimingTrack records the time something took to run using a defer, very good
// for timing a function call, just add a the defer at the top like so:
//     func timeMe() {
//         defer app.Stats.TimingTrack("timeMe.run_time", time.Now())
//         // rest of code, can return anywhere and run time tracked
//     }
// This works because the time.Now() is run when defer line is reached, so gets
// the time timeMe statred
func (s *StatsdClient) TimingTrack(stat string, start time.Time) {
	elapsed := time.Since(start)
	s.app.Finef("STATSD TIMING %s %s", stat, elapsed)
	if !s.connect() {
		return
	}
	_ = s.client.TimingDuration(stat, elapsed, s.rate)
}
<commit_msg>stats: re-connect every minute<commit_after>package gop

import (
	"strings"
	"sync"
	"time"

	"github.com/cactus/go-statsd-client/statsd"
)

type StatsdClient struct {
	sync.Mutex
	client     statsd.Statter
	hostPort   string
	prefix     string
	rate       float32
	app        *App
	reconEvery time.Duration
	reconTimer *time.Timer
}

func (a *App) initStatsd() {
	a.configureStatsd(nil)
	a.Cfg.AddOnChangeCallback(a.configureStatsd)
}

func (a *App) configureStatsd(cfg *Config) {
	statsdHostport, _ := a.Cfg.Get("gop", "statsd_hostport", "localhost:8125")
	prefix := []string{}
	if p, ok := a.Cfg.Get("gop", "statsd_prefix", ""); ok {
		prefix = append(prefix, p)
	}
	hostname := strings.Replace(a.Hostname(), ".", "_", -1)
	prefix = append(prefix, a.ProjectName, a.AppName, hostname)
	statsdPrefix := strings.Join(prefix, ".")
	rate, _ := a.Cfg.GetFloat32("gop", "statsd_rate", 1.0)
	recon, _ := a.Cfg.GetDuration("gop", "statsd_reconnect_every", time.Minute*1)
	// TODO: Need to protect a.Stats from race
	a.Stats = StatsdClient{
		client:     nil,
		hostPort:   statsdHostport,
		prefix:     statsdPrefix,
		rate:       rate,
		app:        a,
		reconEvery: recon,
	}
	a.Infof("STATSD sending to [%s] with prefix [%s] at rate [%f]", a.Stats.hostPort, a.Stats.prefix, a.Stats.rate)
	a.Stats.connect()
}

// connect makes a 'connection' to the statsd host (doing a DNS lookup),
// setting the internal client. If that fails we set the internal client to
// noop. If reconEvery is set non 0, periodically re-connects on that duration.
func (s *StatsdClient) connect() bool {
	s.Lock()
	defer s.Unlock()
	if s.reconTimer != nil {
		s.reconTimer.Stop()
		s.reconTimer = nil
	}
	var err error
	s.client, err = statsd.New(s.hostPort, s.prefix)
	if err != nil {
		s.app.Error("STATSD Failed to create client (stats will noop): " + err.Error())
		s.client, err = statsd.NewNoopClient()
	} else {
		s.app.Finef("STATSD sending to [%s] with prefix [%s] at rate [%f]", s.hostPort, s.prefix, s.rate)
	}
	if s.reconEvery != 0 {
		s.reconTimer = time.AfterFunc(s.reconEvery, func() { s.connect() })
	}
	return true
}

func (s *StatsdClient) c() statsd.Statter {
	s.Lock()
	ptr := s.client
	s.Unlock()
	return ptr
}

func (s *StatsdClient) Dec(stat string, value int64) {
	s.app.Fine("STATSD DEC %s %d", stat, value)
	_ = s.c().Dec(stat, value, s.rate)
}

func (s *StatsdClient) Gauge(stat string, value int64) {
	s.app.Fine("STATSD GAUGE %s %d", stat, value)
	_ = s.c().Gauge(stat, value, s.rate)
}

func (s *StatsdClient) GaugeDelta(stat string, value int64) {
	s.app.Fine("STATSD GAUGEDELTA %s %d", stat, value)
	_ = s.c().GaugeDelta(stat, value, s.rate)
}

func (s *StatsdClient) Inc(stat string, value int64) {
	s.app.Fine("STATSD INC %s %d", stat, value)
	_ = s.c().Inc(stat, value, s.rate)
}

func (s *StatsdClient) Timing(stat string, delta int64) {
	s.app.Fine("STATSD TIMING %s %d", stat, delta)
	_ = s.c().Timing(stat, delta, s.rate)
}

func (s *StatsdClient) TimingDuration(stat string, delta time.Duration) {
	s.app.Fine("STATSD TIMING %s %s", stat, delta)
	_ = s.c().TimingDuration(stat, delta, s.rate)
}

// TimingTrack records the time something took to run using a defer, very good
// for timing a function call, just add a the defer at the top like so:
//     func timeMe() {
//         defer app.Stats.TimingTrack("timeMe.run_time", time.Now())
//         // rest of code, can return anywhere and run time tracked
//     }
// This works because the time.Now() is run when defer line is reached, so gets
// the time timeMe statred
func (s *StatsdClient) TimingTrack(stat string, start time.Time) {
	elapsed := time.Since(start)
	s.app.Finef("STATSD TIMING %s %s", stat, elapsed)
	_ = s.c().TimingDuration(stat, elapsed, s.rate)
}
<|endoftext|>


<commit_before>package statsd

import (
	"net"
	"os"
	"strconv"
	"time"
)

var Address = "localhost:8125"

var AlsoAppendHost = true

// Cache the conn for perf.
var conn net.Conn
var host string

func init() {
	var err error

	if host, err = os.Hostname(); err != nil {
		panic(err)
	}
}

func Timer() timer {
	return timer{time.Now()}
}

// Timer
type timer struct {
	start time.Time
}

func (t *timer) Reset() {
	t.start = time.Now()
}

func (t *timer) Send(names ...interface{}) (took time.Duration) {
	took = time.Since(t.start)

	if err := getConnection(); err != nil {
		return
	}

	value := ":" + strconv.FormatUint(uint64(took.Nanoseconds()/1e6), 10) + "|ms"

	for _, name := range names {
		conn.Write([]byte(name.(string) + value))

		// Send a host suffixed stat too.
		if AlsoAppendHost {
			conn.Write([]byte(name.(string) + "." + host + value))
		}
	}

	return
}

func Gauge(name string, value int64) {
	if err := getConnection(); err != nil {
		return
	}

	suffix := ":" + strconv.FormatInt(value, 10) + "|g"

	conn.Write([]byte(name + suffix))

	// Send a host suffixed stat too.
	if AlsoAppendHost {
		conn.Write([]byte(name + "." + host + suffix))
	}

	return
}

// Inc is a simple counter adding one to a given metric.
func Inc(name string) {
	if err := getConnection(); err != nil {
		return
	}

	conn.Write([]byte(name + ":1|c"))

	// Send a host suffixed stat too.
	if AlsoAppendHost {
		conn.Write([]byte(name + "." + host + ":1|c"))
	}

	return
}

func Time(name string, took time.Duration) {
	if err := getConnection(); err != nil {
		return
	}

	value := ":" + strconv.FormatUint(uint64(took.Nanoseconds()/1e6), 10) + "|ms"

	conn.Write([]byte(name + value))

	// Send a host suffixed stat too.
	if AlsoAppendHost {
		conn.Write([]byte(name + "." + host + value))
	}

	return
}

func getConnection() (err error) {
	// If we don't have a conn, make one.
	if conn == nil {
		if conn, err = net.Dial("udp", Address); err != nil {
			conn = nil
			return
		}
	}

	return
}
<commit_msg>statsd.go: fix go lint warnings<commit_after>package statsd

import (
	"net"
	"os"
	"strconv"
	"time"
)

// Address of the StatsD server.
var Address = "localhost:8125"

// AlsoAppendHost appends hostname along with any metric sent.
var AlsoAppendHost = true

// Cache the conn for perf.
var conn net.Conn
var host string

func init() {
	var err error

	if host, err = os.Hostname(); err != nil {
		panic(err)
	}
}

func Timer() timer {
	return timer{time.Now()}
}

// Timer
type timer struct {
	start time.Time
}

func (t *timer) Reset() {
	t.start = time.Now()
}

func (t *timer) Send(names ...interface{}) (took time.Duration) {
	took = time.Since(t.start)

	if err := getConnection(); err != nil {
		return
	}

	value := ":" + strconv.FormatUint(uint64(took.Nanoseconds()/1e6), 10) + "|ms"

	for _, name := range names {
		conn.Write([]byte(name.(string) + value))

		// Send a host suffixed stat too.
		if AlsoAppendHost {
			conn.Write([]byte(name.(string) + "." + host + value))
		}
	}

	return
}

// Gauge sets arbitrary numeric value for a given metric.
func Gauge(name string, value int64) {
	if err := getConnection(); err != nil {
		return
	}

	suffix := ":" + strconv.FormatInt(value, 10) + "|g"

	conn.Write([]byte(name + suffix))

	// Send a host suffixed stat too.
	if AlsoAppendHost {
		conn.Write([]byte(name + "." + host + suffix))
	}

	return
}

// Inc is a simple counter adding one to a given metric.
func Inc(name string) {
	if err := getConnection(); err != nil {
		return
	}

	conn.Write([]byte(name + ":1|c"))

	// Send a host suffixed stat too.
	if AlsoAppendHost {
		conn.Write([]byte(name + "." + host + ":1|c"))
	}

	return
}

// Time sends duration in ms for a given metric.
func Time(name string, took time.Duration) {
	if err := getConnection(); err != nil {
		return
	}

	value := ":" + strconv.FormatUint(uint64(took.Nanoseconds()/1e6), 10) + "|ms"

	conn.Write([]byte(name + value))

	// Send a host suffixed stat too.
	if AlsoAppendHost {
		conn.Write([]byte(name + "." + host + value))
	}

	return
}

func getConnection() (err error) {
	// If we don't have a conn, make one.
	if conn == nil {
		if conn, err = net.Dial("udp", Address); err != nil {
			conn = nil
			return
		}
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"sort"
	"strings"
	"time"
)

var statusTemplate *template.Template

var templateFns = template.FuncMap{
	"marshal": func(v status) template.JS {
		a, _ := json.Marshal(v)
		return template.JS(a)
	},
	"castToFloat32": func(v int) float32 {
		return float32(v)
	},
}

func init() {
	// This is chunked into the binary with go-bindata. See the Makefile for more
	// information.
	raw := string(MustAsset("status.tmpl"))
	statusTemplate = template.Must(template.New("status").Funcs(templateFns).Parse(raw))
}

type status struct {
	DBs map[string]dbStatus `json:"dbs"`
}

type dbStatus struct {
	Versions map[string]versionStatus `json:"versions,omitempty"`
}

type versionStatus struct {
	Path                      string  `json:"path"`
	NumPartitions             int     `json:"num_partitions"`
	UnderreplicatedPartitions int     `json:"underreplicated_partitions"`
	OverreplicatedPartitions  int     `json:"overreplicated_partitions"`
	MissingPartitions         int     `json:"missing_partitions"`
	TargetReplication         int     `json:"target_replication"`
	AverageReplication        float32 `json:"average_replication"`

	Nodes map[string]nodeVersionStatus `json:"nodes"`
}

type nodeVersionStatus struct {
	CreatedAt   time.Time    `json:"created_at"`
	AvailableAt time.Time    `json:"available_at,omitempty"`
	Current     bool         `json:"current"`
	State       versionState `json:"state"`
	Partitions  []int        `json:"partitions"`
}

type versionState string

const (
	versionAvailable versionState = "AVAILABLE"
	versionRemoving               = "REMOVING"
	versionBuilding               = "BUILDING"
	versionError                  = "ERROR"
)

func (s *sequins) serveHealth(w http.ResponseWriter, r *http.Request) {
	s.dbsLock.RLock()

	status := status{DBs: make(map[string]dbStatus)}
	for name, db := range s.dbs {
		status.DBs[name] = copyDBStatus(db.status())
	}

	s.dbsLock.RUnlock()

	allNodesAvailable := true
	statuses := make(map[string]map[string]versionState)

	// Iterate through all of the nodes of the local databases and ensure
	// that the versions they own are all marked as available. A single
	// version being in an unvailable state results in an error response.
	for dbKey, dbValue := range status.DBs {
		for versionKey, versionValue := range dbValue.Versions {
			for _, nodeValue := range versionValue.Nodes {
				_, ok := statuses[dbKey]
				if !ok {
					statuses[dbKey] = make(map[string]versionState)
				}

				statuses[dbKey][versionKey] = nodeValue.State
				if nodeValue.State != versionAvailable {
					allNodesAvailable = false
				}
			}
		}
	}

	jsonBytes, err := json.Marshal(statuses)
	if err != nil {
		log.Printf("Error encoding response to JSON: %v", jsonBytes)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	if allNodesAvailable {
		w.WriteHeader(http.StatusOK)
	} else {
		w.WriteHeader(http.StatusNotFound)
	}
	w.Write(jsonBytes)
}

func (s *sequins) serveStatus(w http.ResponseWriter, r *http.Request) {
	s.dbsLock.RLock()

	status := status{DBs: make(map[string]dbStatus)}
	for name, db := range s.dbs {
		status.DBs[name] = copyDBStatus(db.status())
	}

	s.dbsLock.RUnlock()

	// By default, serve our peers' statuses merged with ours. We take
	// extra care not to mutate local status structs.
	if r.URL.Query().Get("proxy") == "" && s.peers != nil {
		for _, p := range s.peers.Get() {
			peerStatus, err := s.getPeerStatus(p, "")
			if err != nil {
				log.Printf("Error fetching status from peer %s: %s", p, err)
				continue
			}

			merged := peerStatus
			for db := range status.DBs {
				if _, ok := merged.DBs[db]; ok {
					merged.DBs[db] = mergeDBStatus(merged.DBs[db], status.DBs[db])
				} else {
					merged.DBs[db] = status.DBs[db]
				}
			}

			status = merged
		}

		for _, db := range status.DBs {
			for versionName, version := range db.Versions {
				db.Versions[versionName] = calculateReplicationStats(version)
			}
		}
	}

	if acceptsJSON(r) {
		jsonBytes, err := json.Marshal(status)
		if err != nil {
			log.Println("Error serving status:", err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		w.Header()["Content-Type"] = []string{"application/json"}
		w.Write(jsonBytes)
	} else {
		err := statusTemplate.Execute(w, status)
		if err != nil {
			log.Println("Error rendering status:", err)
		}
	}
}

func (db *db) serveStatus(w http.ResponseWriter, r *http.Request) {
	s := db.status()

	// By default, serve our peers' statuses merged with ours.
	if r.URL.Query().Get("proxy") == "" && db.sequins.peers != nil {
		for _, p := range db.sequins.peers.Get() {
			peerStatus, err := db.sequins.getPeerStatus(p, db.name)
			if err != nil {
				log.Printf("Error fetching status from peer %s: %s", p, err)
				continue
			}

			peerDBStatus := peerStatus.DBs[db.name]
			s = mergeDBStatus(peerDBStatus, s)
		}

		for versionName, version := range s.Versions {
			s.Versions[versionName] = calculateReplicationStats(version)
		}
	}

	if acceptsJSON(r) {
		jsonBytes, err := json.Marshal(s)
		if err != nil {
			log.Println("Error serving status:", err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		w.Header()["Content-Type"] = []string{"application/json"}
		w.Write(jsonBytes)
	} else {
		status := status{DBs: make(map[string]dbStatus)}
		status.DBs[db.name] = s
		err := statusTemplate.Execute(w, status)
		if err != nil {
			log.Println("Error rendering status:", err)
		}
	}
}

// getPeerStatus fetches a peer's status for the given db. If db is empty, it
// returns the status for all dbs.
func (s *sequins) getPeerStatus(peer string, db string) (status, error) {
	url := fmt.Sprintf("http://%s/%s?proxy=status", peer, db)
	if !strings.HasSuffix(url, "/") {
		url += "/"
	}

	status := status{}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Set("Accept", "application/json")
	if err != nil {
		return status, err
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return status, err
	}

	defer resp.Body.Close()
	decoder := json.NewDecoder(resp.Body)
	if db == "" {
		err = decoder.Decode(&status)
	} else {
		s := dbStatus{}
		err = decoder.Decode(&s)
		if err != nil {
			return status, err
		}

		status.DBs = map[string]dbStatus{db: s}
	}

	return status, err
}

// mergeDBStatus merges two dbStatus objects, mutating only the
// left one.
func mergeDBStatus(left, right dbStatus) dbStatus {
	for v, vst := range right.Versions {
		if _, ok := left.Versions[v]; !ok {
			left.Versions[v] = versionStatus{
				Nodes:             make(map[string]nodeVersionStatus),
				Path:              vst.Path,
				NumPartitions:     vst.NumPartitions,
				TargetReplication: vst.TargetReplication,
			}
		}

		for hostname, node := range right.Versions[v].Nodes {
			left.Versions[v].Nodes[hostname] = node
		}
	}

	return left
}

// copyDBStatus does a deep copy of a dbStatus object and returns it.
func copyDBStatus(status dbStatus) dbStatus {
	fresh := dbStatus{Versions: make(map[string]versionStatus)}
	return mergeDBStatus(fresh, status)
}

func calculateReplicationStats(vst versionStatus) versionStatus {
	replication := make(map[int]int)
	for _, node := range vst.Nodes {
		if node.State == versionAvailable || node.State == versionRemoving {
			for _, p := range node.Partitions {
				replication[p] += 1
			}
		}
	}

	total := 0
	for p := 0; p < vst.NumPartitions; p++ {
		r := replication[p]
		total += r
		if r == 0 {
			vst.MissingPartitions += 1
		} else if r < vst.TargetReplication {
			vst.UnderreplicatedPartitions += 1
		} else if r > vst.TargetReplication {
			vst.OverreplicatedPartitions += 1
		}
	}

	if vst.NumPartitions == 0 {
		vst.AverageReplication = 0
	} else {
		vst.AverageReplication = float32(total) / float32(vst.NumPartitions)
	}

	return vst
}

func acceptsJSON(r *http.Request) bool {
	for _, accept := range r.Header["Accept"] {
		if accept == "application/json" {
			return true
		}
	}

	return false
}

func (db *db) status() dbStatus {
	status := dbStatus{Versions: make(map[string]versionStatus)}
	for _, vs := range db.mux.getAll() {
		status.Versions[vs.name] = vs.status()
	}

	hostname := "localhost"
	if db.sequins.peers != nil {
		hostname = db.sequins.address
	}

	current := db.mux.getCurrent()
	db.mux.release(current)
	for name := range status.Versions {
		st := status.Versions[name].Nodes[hostname]
		st.Current = (current != nil && name == current.name)

		status.Versions[name].Nodes[hostname] = st
	}

	return status
}

func (vs *version) status() versionStatus {
	vs.stateLock.Lock()
	defer vs.stateLock.Unlock()

	st := versionStatus{
		Path:          vs.sequins.backend.DisplayPath(vs.db.name, vs.name),
		NumPartitions: vs.numPartitions,
		Nodes:         make(map[string]nodeVersionStatus),
	}

	partitions := make([]int, 0, len(vs.partitions.SelectedLocal()))
	for p := range vs.partitions.SelectedLocal() {
		partitions = append(partitions, p)
	}

	sort.Ints(partitions)
	nodeStatus := nodeVersionStatus{
		CreatedAt:  vs.created.UTC().Truncate(time.Second),
		State:      vs.state,
		Partitions: partitions,
	}

	if !vs.available.IsZero() {
		nodeStatus.AvailableAt = vs.available.UTC().Truncate(time.Second)
	}

	hostname := "localhost"
	if vs.sequins.peers != nil {
		hostname = vs.sequins.address
	}

	st.Nodes[hostname] = nodeStatus
	return st
}

func (vs *version) setState(state versionState) {
	vs.stateLock.Lock()
	defer vs.stateLock.Unlock()

	if vs.state != versionError {
		vs.state = state
		if state == versionAvailable {
			vs.available = time.Now()
			if vs.stats != nil {
				tags := []string{fmt.Sprintf("sequins_db:%s", vs.db.name)}
				span := vs.available.Sub(vs.created)
				vs.stats.Timing("db_creation_time", span, tags, 1)

			}

		}
	}
}
<commit_msg>Formatting<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"sort"
	"strings"
	"time"
)

var statusTemplate *template.Template

var templateFns = template.FuncMap{
	"marshal": func(v status) template.JS {
		a, _ := json.Marshal(v)
		return template.JS(a)
	},
	"castToFloat32": func(v int) float32 {
		return float32(v)
	},
}

func init() {
	// This is chunked into the binary with go-bindata. See the Makefile for more
	// information.
	raw := string(MustAsset("status.tmpl"))
	statusTemplate = template.Must(template.New("status").Funcs(templateFns).Parse(raw))
}

type status struct {
	DBs map[string]dbStatus `json:"dbs"`
}

type dbStatus struct {
	Versions map[string]versionStatus `json:"versions,omitempty"`
}

type versionStatus struct {
	Path                      string  `json:"path"`
	NumPartitions             int     `json:"num_partitions"`
	UnderreplicatedPartitions int     `json:"underreplicated_partitions"`
	OverreplicatedPartitions  int     `json:"overreplicated_partitions"`
	MissingPartitions         int     `json:"missing_partitions"`
	TargetReplication         int     `json:"target_replication"`
	AverageReplication        float32 `json:"average_replication"`

	Nodes map[string]nodeVersionStatus `json:"nodes"`
}

type nodeVersionStatus struct {
	CreatedAt   time.Time    `json:"created_at"`
	AvailableAt time.Time    `json:"available_at,omitempty"`
	Current     bool         `json:"current"`
	State       versionState `json:"state"`
	Partitions  []int        `json:"partitions"`
}

type versionState string

const (
	versionAvailable versionState = "AVAILABLE"
	versionRemoving               = "REMOVING"
	versionBuilding               = "BUILDING"
	versionError                  = "ERROR"
)

func (s *sequins) serveHealth(w http.ResponseWriter, r *http.Request) {
	s.dbsLock.RLock()

	status := status{DBs: make(map[string]dbStatus)}
	for name, db := range s.dbs {
		status.DBs[name] = copyDBStatus(db.status())
	}

	s.dbsLock.RUnlock()

	allNodesAvailable := true
	statuses := make(map[string]map[string]versionState)

	// Iterate through all of the nodes of the local databases and ensure
	// that the versions they own are all marked as available. A single
	// version being in an unvailable state results in an error response.
	for dbKey, dbValue := range status.DBs {
		for versionKey, versionValue := range dbValue.Versions {
			for _, nodeValue := range versionValue.Nodes {
				_, ok := statuses[dbKey]
				if !ok {
					statuses[dbKey] = make(map[string]versionState)
				}

				statuses[dbKey][versionKey] = nodeValue.State
				if nodeValue.State != versionAvailable {
					allNodesAvailable = false
				}
			}
		}
	}

	jsonBytes, err := json.Marshal(statuses)
	if err != nil {
		log.Printf("Error encoding response to JSON: %v", jsonBytes)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	if allNodesAvailable {
		w.WriteHeader(http.StatusOK)
	} else {
		w.WriteHeader(http.StatusNotFound)
	}
	w.Write(jsonBytes)
}

func (s *sequins) serveStatus(w http.ResponseWriter, r *http.Request) {
	s.dbsLock.RLock()

	status := status{DBs: make(map[string]dbStatus)}
	for name, db := range s.dbs {
		status.DBs[name] = copyDBStatus(db.status())
	}

	s.dbsLock.RUnlock()

	// By default, serve our peers' statuses merged with ours. We take
	// extra care not to mutate local status structs.
	if r.URL.Query().Get("proxy") == "" && s.peers != nil {
		for _, p := range s.peers.Get() {
			peerStatus, err := s.getPeerStatus(p, "")
			if err != nil {
				log.Printf("Error fetching status from peer %s: %s", p, err)
				continue
			}

			merged := peerStatus
			for db := range status.DBs {
				if _, ok := merged.DBs[db]; ok {
					merged.DBs[db] = mergeDBStatus(merged.DBs[db], status.DBs[db])
				} else {
					merged.DBs[db] = status.DBs[db]
				}
			}

			status = merged
		}

		for _, db := range status.DBs {
			for versionName, version := range db.Versions {
				db.Versions[versionName] = calculateReplicationStats(version)
			}
		}
	}

	if acceptsJSON(r) {
		jsonBytes, err := json.Marshal(status)
		if err != nil {
			log.Println("Error serving status:", err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		w.Header()["Content-Type"] = []string{"application/json"}
		w.Write(jsonBytes)
	} else {
		err := statusTemplate.Execute(w, status)
		if err != nil {
			log.Println("Error rendering status:", err)
		}
	}
}

func (db *db) serveStatus(w http.ResponseWriter, r *http.Request) {
	s := db.status()

	// By default, serve our peers' statuses merged with ours.
	if r.URL.Query().Get("proxy") == "" && db.sequins.peers != nil {
		for _, p := range db.sequins.peers.Get() {
			peerStatus, err := db.sequins.getPeerStatus(p, db.name)
			if err != nil {
				log.Printf("Error fetching status from peer %s: %s", p, err)
				continue
			}

			peerDBStatus := peerStatus.DBs[db.name]
			s = mergeDBStatus(peerDBStatus, s)
		}

		for versionName, version := range s.Versions {
			s.Versions[versionName] = calculateReplicationStats(version)
		}
	}

	if acceptsJSON(r) {
		jsonBytes, err := json.Marshal(s)
		if err != nil {
			log.Println("Error serving status:", err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		w.Header()["Content-Type"] = []string{"application/json"}
		w.Write(jsonBytes)
	} else {
		status := status{DBs: make(map[string]dbStatus)}
		status.DBs[db.name] = s
		err := statusTemplate.Execute(w, status)
		if err != nil {
			log.Println("Error rendering status:", err)
		}
	}
}

// getPeerStatus fetches a peer's status for the given db. If db is empty, it
// returns the status for all dbs.
func (s *sequins) getPeerStatus(peer string, db string) (status, error) {
	url := fmt.Sprintf("http://%s/%s?proxy=status", peer, db)
	if !strings.HasSuffix(url, "/") {
		url += "/"
	}

	status := status{}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Set("Accept", "application/json")
	if err != nil {
		return status, err
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return status, err
	}

	defer resp.Body.Close()
	decoder := json.NewDecoder(resp.Body)
	if db == "" {
		err = decoder.Decode(&status)
	} else {
		s := dbStatus{}
		err = decoder.Decode(&s)
		if err != nil {
			return status, err
		}

		status.DBs = map[string]dbStatus{db: s}
	}

	return status, err
}

// mergeDBStatus merges two dbStatus objects, mutating only the
// left one.
func mergeDBStatus(left, right dbStatus) dbStatus {
	for v, vst := range right.Versions {
		if _, ok := left.Versions[v]; !ok {
			left.Versions[v] = versionStatus{
				Nodes:             make(map[string]nodeVersionStatus),
				Path:              vst.Path,
				NumPartitions:     vst.NumPartitions,
				TargetReplication: vst.TargetReplication,
			}
		}

		for hostname, node := range right.Versions[v].Nodes {
			left.Versions[v].Nodes[hostname] = node
		}
	}

	return left
}

// copyDBStatus does a deep copy of a dbStatus object and returns it.
func copyDBStatus(status dbStatus) dbStatus {
	fresh := dbStatus{Versions: make(map[string]versionStatus)}
	return mergeDBStatus(fresh, status)
}

func calculateReplicationStats(vst versionStatus) versionStatus {
	replication := make(map[int]int)
	for _, node := range vst.Nodes {
		if node.State == versionAvailable || node.State == versionRemoving {
			for _, p := range node.Partitions {
				replication[p] += 1
			}
		}
	}

	total := 0
	for p := 0; p < vst.NumPartitions; p++ {
		r := replication[p]
		total += r
		if r == 0 {
			vst.MissingPartitions += 1
		} else if r < vst.TargetReplication {
			vst.UnderreplicatedPartitions += 1
		} else if r > vst.TargetReplication {
			vst.OverreplicatedPartitions += 1
		}
	}

	if vst.NumPartitions == 0 {
		vst.AverageReplication = 0
	} else {
		vst.AverageReplication = float32(total) / float32(vst.NumPartitions)
	}

	return vst
}

func acceptsJSON(r *http.Request) bool {
	for _, accept := range r.Header["Accept"] {
		if accept == "application/json" {
			return true
		}
	}

	return false
}

func (db *db) status() dbStatus {
	status := dbStatus{Versions: make(map[string]versionStatus)}
	for _, vs := range db.mux.getAll() {
		status.Versions[vs.name] = vs.status()
	}

	hostname := "localhost"
	if db.sequins.peers != nil {
		hostname = db.sequins.address
	}

	current := db.mux.getCurrent()
	db.mux.release(current)
	for name := range status.Versions {
		st := status.Versions[name].Nodes[hostname]
		st.Current = (current != nil && name == current.name)

		status.Versions[name].Nodes[hostname] = st
	}

	return status
}

func (vs *version) status() versionStatus {
	vs.stateLock.Lock()
	defer vs.stateLock.Unlock()

	st := versionStatus{
		Path:          vs.sequins.backend.DisplayPath(vs.db.name, vs.name),
		NumPartitions: vs.numPartitions,
		Nodes:         make(map[string]nodeVersionStatus),
	}

	partitions := make([]int, 0, len(vs.partitions.SelectedLocal()))
	for p := range vs.partitions.SelectedLocal() {
		partitions = append(partitions, p)
	}

	sort.Ints(partitions)
	nodeStatus := nodeVersionStatus{
		CreatedAt:  vs.created.UTC().Truncate(time.Second),
		State:      vs.state,
		Partitions: partitions,
	}

	if !vs.available.IsZero() {
		nodeStatus.AvailableAt = vs.available.UTC().Truncate(time.Second)
	}

	hostname := "localhost"
	if vs.sequins.peers != nil {
		hostname = vs.sequins.address
	}

	st.Nodes[hostname] = nodeStatus
	return st
}

func (vs *version) setState(state versionState) {
	vs.stateLock.Lock()
	defer vs.stateLock.Unlock()

	if vs.state != versionError {
		vs.state = state
		if state == versionAvailable {
			vs.available = time.Now()

			if vs.stats != nil {
				tags := []string{fmt.Sprintf("sequins_db:%s", vs.db.name)}
				span := vs.available.Sub(vs.created)
				vs.stats.Timing("db_creation_time", span, tags, 1)
			}
		}
	}
}
<|endoftext|>


<commit_before>package git

/*
#include <git2.h>
#include <git2/errors.h>
*/
import "C"

import (
	"runtime"
)

type Status int

const (
	StatusCurrent         Status = C.GIT_STATUS_CURRENT
	StatusIndexNew               = C.GIT_STATUS_INDEX_NEW
	StatusIndexModified          = C.GIT_STATUS_INDEX_MODIFIED
	StatusIndexDeleted           = C.GIT_STATUS_INDEX_DELETED
	StatusIndexRenamed           = C.GIT_STATUS_INDEX_RENAMED
	StatusIndexTypeChange        = C.GIT_STATUS_INDEX_TYPECHANGE
	StatusWtNew                  = C.GIT_STATUS_WT_NEW
	StatusWtModified             = C.GIT_STATUS_WT_NEW
	StatusWtDeleted              = C.GIT_STATUS_WT_DELETED
	StatusWtTypeChange           = C.GIT_STATUS_WT_TYPECHANGE
	StatusWtRenamed              = C.GIT_STATUS_WT_RENAMED
	StatusIgnored                = C.GIT_STATUS_IGNORED
)

type StatusList struct {
	ptr *C.git_status_list
}

type StatusEntry struct {
	Status         Status
	HeadToIndex    DiffDelta
	IndexToWorkdir DiffDelta
}

func newStatusListFromC(ptr *C.git_status_list) *StatusList {
	if ptr == nil {
		return nil
	}

	statusList := &StatusList{
		ptr: ptr,
	}

	runtime.SetFinalizer(statusList, (*StatusList).Free)
	return statusList
}

func (statusList *StatusList) Free() error {
	if statusList.ptr == nil {
		return ErrInvalid
	}
	runtime.SetFinalizer(statusList, nil)
	C.git_status_list_free(statusList.ptr)
	statusList.ptr = nil
	return nil
}

func statusEntryFromC(statusEntry *C.git_status_entry) StatusEntry {
	return StatusEntry {
		Status:         Status(statusEntry.status),
		HeadToIndex:    diffDeltaFromC(statusEntry.head_to_index),
		IndexToWorkdir: diffDeltaFromC(statusEntry.index_to_workdir),
	}
}
<commit_msg>add ByIndex, reorder to match other files<commit_after>package git

/*
#include <git2.h>
#include <git2/errors.h>
*/
import "C"

import (
	"runtime"
)

type Status int

const (
	StatusCurrent         Status = C.GIT_STATUS_CURRENT
	StatusIndexNew               = C.GIT_STATUS_INDEX_NEW
	StatusIndexModified          = C.GIT_STATUS_INDEX_MODIFIED
	StatusIndexDeleted           = C.GIT_STATUS_INDEX_DELETED
	StatusIndexRenamed           = C.GIT_STATUS_INDEX_RENAMED
	StatusIndexTypeChange        = C.GIT_STATUS_INDEX_TYPECHANGE
	StatusWtNew                  = C.GIT_STATUS_WT_NEW
	StatusWtModified             = C.GIT_STATUS_WT_NEW
	StatusWtDeleted              = C.GIT_STATUS_WT_DELETED
	StatusWtTypeChange           = C.GIT_STATUS_WT_TYPECHANGE
	StatusWtRenamed              = C.GIT_STATUS_WT_RENAMED
	StatusIgnored                = C.GIT_STATUS_IGNORED
)

type StatusEntry struct {
	Status         Status
	HeadToIndex    DiffDelta
	IndexToWorkdir DiffDelta
}

func statusEntryFromC(statusEntry *C.git_status_entry) StatusEntry {
	return StatusEntry {
		Status:         Status(statusEntry.status),
		HeadToIndex:    diffDeltaFromC(statusEntry.head_to_index),
		IndexToWorkdir: diffDeltaFromC(statusEntry.index_to_workdir),
	}
}

type StatusList struct {
	ptr *C.git_status_list
}

func newStatusListFromC(ptr *C.git_status_list) *StatusList {
	if ptr == nil {
		return nil
	}

	statusList := &StatusList{
		ptr: ptr,
	}

	runtime.SetFinalizer(statusList, (*StatusList).Free)
	return statusList
}

func (statusList *StatusList) Free() error {
	if statusList.ptr == nil {
		return ErrInvalid
	}
	runtime.SetFinalizer(statusList, nil)
	C.git_status_list_free(statusList.ptr)
	statusList.ptr = nil
	return nil
}

func (statusList *StatusList) ByIndex(index int) (StatusEntry, error) {
	if statusList.ptr == nil {
		return StatusEntry{}, ErrInvalid
	}
	ptr := C.git_status_byindex(statusList.ptr, C.size_t(index))
	return statusEntryFromC(ptr), nil
}
<|endoftext|>


<commit_before>package encrypted

import (
	"io/ioutil"
	"log"
	"os"

	"github.com/tinzenite/shared"
)

/*
handleLockMessage handles the logic upon receiving a LockMessage. Notably this
includes allowing or disallowing a lock for a specific time frame.
*/
func (c *chaninterface) handleLockMessage(address string, lm *shared.LockMessage) {
	switch lm.Action {
	case shared.LoRequest:
		if c.enc.isLockedAddress(address) {
			// we catch this to avoid having peers trying to sync multiple times at the same time
			log.Println("Relock tried for same address, ignoring!")
			return
		}
		if c.enc.setLock(address) {
			// if successful notify peer of success
			accept := shared.CreateLockMessage(shared.LoAccept)
			c.enc.channel.Send(address, accept.JSON())
		}
		// if not successful send release to signify that peer has no lock
		deny := shared.CreateLockMessage(shared.LoRelease)
		c.enc.channel.Send(address, deny.JSON())
		return
	case shared.LoRelease:
		if c.enc.isLockedAddress(address) {
			c.enc.ClearLock()
			// TODO notify of clear?
			return
		}
		log.Println("handleLockMessage: WARNING: received release request from invalid peer!", address[:8])
	default:
		log.Println("handleLockMessage: Invalid action received!")
	}
}

/*
handleRequestMessage handles the logic upon receiving a RequestMessage. NOTE:
will only be actually handled if Encrypted is currently locked.
*/
func (c *chaninterface) handleRequestMessage(address string, rm *shared.RequestMessage) {
	var data []byte           // data to send
	var identification string // identification for writing temp file
	var err error
	// check file type and fetch data accordingly
	switch rm.ObjType {
	case shared.OtObject:
		// TODO differentiate ORGDIR from path? how? we don't have it... :( FIXME: add more special ID____ things
		// fetch data for normal objects from storage
		data, err = c.enc.storage.Retrieve(rm.Identification)
		identification = rm.Identification
	case shared.OtModel:
		// model is read from specially named file
		data, err = ioutil.ReadFile(c.enc.RootPath + "/" + shared.IDMODEL)
		identification = shared.IDMODEL
	case shared.OtPeer:
		data, err = ioutil.ReadFile(c.enc.RootPath + "/" + shared.ORGDIR + "/" + shared.PEERSDIR + "/" + rm.Identification)
		identification = rm.Identification
	case shared.OtAuth:
		data, err = ioutil.ReadFile(c.enc.RootPath + "/" + shared.ORGDIR + "/" + shared.AUTHJSON)
		identification = rm.Identification
	default:
		log.Println("handleRequestMessage: Invalid ObjType requested!", rm.ObjType.String())
		return
	}
	// if error return
	if err != nil {
		// print error only if not model (because missing model signals that this peer is empty)
		if rm.ObjType != shared.OtModel {
			log.Println("handleRequestMessage: retrieval of", rm.ObjType, "failed:", err)
		}
		// notify sender that it don't exist in any case
		nm := shared.CreateNotifyMessage(shared.NoMissing, identification)
		c.enc.channel.Send(address, nm.JSON())
		return
	}
	// path for temp file
	filePath := c.enc.RootPath + "/" + shared.SENDINGDIR + "/" + c.buildKey(address, identification)
	// write data to temp sending file
	err = ioutil.WriteFile(filePath, data, shared.FILEPERMISSIONMODE)
	if err != nil {
		log.Println("handleRequestMessage: failed to write data to SEDIR:", err)
		return
	}
	// send file
	err = c.enc.channel.SendFile(address, filePath, rm.Identification, func(success bool) {
		// FIXME file is removed... :P When everything works remove file no matter what
		// if NOT success, log and keep file for debugging
		if !success {
			log.Println("handleRequestMessage: Failed to send file on request!", filePath)
			return
		}
		// remove file
		err := os.Remove(filePath)
		if err != nil {
			log.Println("handleRequestMessage: failed to remove temp file:", err)
			return
		}
	})
	// if error log
	if err != nil {
		log.Println("handleRequestMessage: SendFile returned error:", err)
	}
}

/*
handlePushMessage handles the logic upon receiving a PushMessage.
*/
func (c *chaninterface) handlePushMessage(address string, pm *shared.PushMessage) {
	// note that file transfer is allowed for when file is received
	key := c.buildKey(address, pm.Identification)
	// if we reach this, allow and store push message too
	c.mutex.Lock()
	c.allowedTransfers[key] = *pm
	c.mutex.Unlock()
	// notify that we have received the push message
	rm := shared.CreateRequestMessage(pm.ObjType, pm.Identification)
	c.enc.channel.Send(address, rm.JSON())
}

/*
handleNotifyMessage handles the logic upon receiving a NotifyMessage.
*/
func (c *chaninterface) handleNotifyMessage(address string, nm *shared.NotifyMessage) {
	switch nm.Notify {
	case shared.NoRemoved:
		// TODO notify message must ALSO differentiate types... for now just remove from storage
		err := c.enc.storage.Remove(nm.Identification)
		if err != nil {
			log.Println("handleNotifyMessage: failed to remove from storage:", err)
		}
	default:
		log.Println("handleNotifyMessage: unknown notify type:", nm.Notify)
	}
}

/*
buildKey is a helper function that builds the key used to identify transfers.
*/
func (c *chaninterface) buildKey(address, identification string) string {
	return address + ":" + identification
}
<commit_msg>adapted to channel changes<commit_after>package encrypted

import (
	"io/ioutil"
	"log"
	"os"

	"github.com/tinzenite/channel"
	"github.com/tinzenite/shared"
)

/*
handleLockMessage handles the logic upon receiving a LockMessage. Notably this
includes allowing or disallowing a lock for a specific time frame.
*/
func (c *chaninterface) handleLockMessage(address string, lm *shared.LockMessage) {
	switch lm.Action {
	case shared.LoRequest:
		if c.enc.isLockedAddress(address) {
			// we catch this to avoid having peers trying to sync multiple times at the same time
			log.Println("Relock tried for same address, ignoring!")
			return
		}
		if c.enc.setLock(address) {
			// if successful notify peer of success
			accept := shared.CreateLockMessage(shared.LoAccept)
			c.enc.channel.Send(address, accept.JSON())
		}
		// if not successful send release to signify that peer has no lock
		deny := shared.CreateLockMessage(shared.LoRelease)
		c.enc.channel.Send(address, deny.JSON())
		return
	case shared.LoRelease:
		if c.enc.isLockedAddress(address) {
			c.enc.ClearLock()
			// TODO notify of clear?
			return
		}
		log.Println("handleLockMessage: WARNING: received release request from invalid peer!", address[:8])
	default:
		log.Println("handleLockMessage: Invalid action received!")
	}
}

/*
handleRequestMessage handles the logic upon receiving a RequestMessage. NOTE:
will only be actually handled if Encrypted is currently locked.
*/
func (c *chaninterface) handleRequestMessage(address string, rm *shared.RequestMessage) {
	var data []byte           // data to send
	var identification string // identification for writing temp file
	var err error
	// check file type and fetch data accordingly
	switch rm.ObjType {
	case shared.OtObject:
		// TODO differentiate ORGDIR from path? how? we don't have it... :( FIXME: add more special ID____ things
		// fetch data for normal objects from storage
		data, err = c.enc.storage.Retrieve(rm.Identification)
		identification = rm.Identification
	case shared.OtModel:
		// model is read from specially named file
		data, err = ioutil.ReadFile(c.enc.RootPath + "/" + shared.IDMODEL)
		identification = shared.IDMODEL
	case shared.OtPeer:
		data, err = ioutil.ReadFile(c.enc.RootPath + "/" + shared.ORGDIR + "/" + shared.PEERSDIR + "/" + rm.Identification)
		identification = rm.Identification
	case shared.OtAuth:
		data, err = ioutil.ReadFile(c.enc.RootPath + "/" + shared.ORGDIR + "/" + shared.AUTHJSON)
		identification = rm.Identification
	default:
		log.Println("handleRequestMessage: Invalid ObjType requested!", rm.ObjType.String())
		return
	}
	// if error return
	if err != nil {
		// print error only if not model (because missing model signals that this peer is empty)
		if rm.ObjType != shared.OtModel {
			log.Println("handleRequestMessage: retrieval of", rm.ObjType, "failed:", err)
		}
		// notify sender that it don't exist in any case
		nm := shared.CreateNotifyMessage(shared.NoMissing, identification)
		c.enc.channel.Send(address, nm.JSON())
		return
	}
	// path for temp file
	filePath := c.enc.RootPath + "/" + shared.SENDINGDIR + "/" + c.buildKey(address, identification)
	// write data to temp sending file
	err = ioutil.WriteFile(filePath, data, shared.FILEPERMISSIONMODE)
	if err != nil {
		log.Println("handleRequestMessage: failed to write data to SEDIR:", err)
		return
	}
	// function for when done with transfer
	onComplete := func(status channel.State) {
		// if NOT success, log and keep file for debugging
		if status != channel.StSuccess {
			log.Println("handleRequestMessage: Failed to send file on request!", filePath)
			return
		}
		// remove file
		err := os.Remove(filePath)
		if err != nil {
			log.Println("handleRequestMessage: failed to remove temp file:", err)
			return
		}
	}
	// send file
	err = c.enc.channel.SendFile(address, filePath, rm.Identification, onComplete)
	// if error log
	if err != nil {
		log.Println("handleRequestMessage: SendFile returned error:", err)
	}
}

/*
handlePushMessage handles the logic upon receiving a PushMessage.
*/
func (c *chaninterface) handlePushMessage(address string, pm *shared.PushMessage) {
	// note that file transfer is allowed for when file is received
	key := c.buildKey(address, pm.Identification)
	// if we reach this, allow and store push message too
	c.mutex.Lock()
	c.allowedTransfers[key] = *pm
	c.mutex.Unlock()
	// notify that we have received the push message
	rm := shared.CreateRequestMessage(pm.ObjType, pm.Identification)
	c.enc.channel.Send(address, rm.JSON())
}

/*
handleNotifyMessage handles the logic upon receiving a NotifyMessage.
*/
func (c *chaninterface) handleNotifyMessage(address string, nm *shared.NotifyMessage) {
	switch nm.Notify {
	case shared.NoRemoved:
		// TODO notify message must ALSO differentiate types... for now just remove from storage
		err := c.enc.storage.Remove(nm.Identification)
		if err != nil {
			log.Println("handleNotifyMessage: failed to remove from storage:", err)
		}
	default:
		log.Println("handleNotifyMessage: unknown notify type:", nm.Notify)
	}
}

/*
buildKey is a helper function that builds the key used to identify transfers.
*/
func (c *chaninterface) buildKey(address, identification string) string {
	return address + ":" + identification
}
<|endoftext|>


<commit_before>package uasurfer

import (
	"regexp"
	"strconv"
	"strings"
)

var (
	iosVersion           = regexp.MustCompile("(cpu|iphone) os \\d+")
	osxVersionUnderscore = regexp.MustCompile("os x 10_\\d+")
	osxVersionDot        = regexp.MustCompile("os x 10\\.\\d+")
	wpVerLong            = regexp.MustCompile("windows\\sphone\\sos\\s\\d+")
	wpVerShort           = regexp.MustCompile("windows\\sphone\\s\\d+")
	//kindleTest           = regexp.MustCompile("\\sKF[A-Z]{2,4}\\s")
	androidVersion = regexp.MustCompile("android \\d+")
)

// OS type returns a lowercase name (string) of the operating system, along with its major version (int).
// To allow easy of use with math operators, the version numbers for Mac and Win may be slightly unexpected.
// Here are some examples:
//
// 	For Windows XP (Windows NT 5.1), "windows" is the platform, "xp" is the name, and 5 the version.
// 	For OS X 10.5.1, "mac" is the platform, "os x" the name, and 5 the version.
// 	For Android 5.1, "linux" is the platform, "android" is the name, and 5 the version.
//	For iOS 5.1, "iphone" or "ipad" is the platform, "ios" is the name, and 5 the version.
type OS struct {
	Name    OSName
	Version int
}

// Retrieve the espoused platform and OS from the User-Agent string
func (b *BrowserProfile) evalSystem(ua string) (Platform, OSName, int) {
	var (
		depth             int
		i                 int
		platformUASection []byte //represents portion of ua string that contains platform information
	)

	// more efficient method of getting the spec string than using native regexp package (TODO: need stronger profiling verification)
	for ; i < len(ua); i++ {
		if ua[i] == '(' {
			depth++
		} else if ua[i] == ')' {
			depth--
			// don't be too hungry for parentheticals, the first group should be enough
			if depth == 0 {
				break
			}
		}

		if depth >= 1 {
			platformUASection = append(platformUASection, ua[i])
		}
	}

	agentPlatform := string(platformUASection)
	specs := strings.Split(agentPlatform, ";")
	specs[0] = strings.TrimPrefix(specs[0], "(")

	//strict OS & version identification
	switch specs[0] {
	case "android":
		return evalLinux(ua, agentPlatform)
	case "bb10", "playbook":
		return PlatformBlackberry, OSBlackberry, 0
	case "x11", "linux":
		return evalLinux(ua, agentPlatform)
	case "ipad", "iphone":
		return evaliOS(specs[0], agentPlatform)
	case "macintosh":
		return evalMacintosh(ua)
	}

	// Blackberry
	if strings.Contains(ua, "blackberry") || strings.Contains(ua, "playbook") {
		return PlatformBlackberry, OSBlackberry, 0
	}

	// Windows Phone
	if strings.Contains(agentPlatform, "windows phone ") {
		return evalWindowsPhone(agentPlatform)
	}

	// Windows, Xbox
	if strings.Contains(ua, "windows ") {
		return evalWindows(ua)
	}

	// Kindle
	if strings.Contains(ua, "kindle/") {
		return PlatformKindle, OSKindle, 0
	}

	// Linux (broader attempt)
	if strings.Contains(ua, "linux") {
		return evalLinux(ua, agentPlatform)
	}

	// WebOS (non-linux flagged)
	if strings.Contains(ua, "webos") || strings.Contains(ua, "hpwos") {
		return PlatformLinux, OSWebOS, 0
	}

	// Nintendo
	if strings.Contains(ua, "nintendo") {
		return PlatformNintendo, OSNintendo, 0
	}

	// Playstation
	if strings.Contains(ua, "playstation") || strings.Contains(ua, "vita") || strings.Contains(ua, "psp") {
		return PlatformPlaystation, OSPlaystation, 0
	}

	return PlatformUnknown, OSUnknown, 0 //default

}

// evalLinux returns the `Platform`, `OSName` and `int` of UAs with
// 'linux' listed as their platform. The Platform returned is not
// necessarily PlatformLinux as it may be PlatformKindle, for example.
func evalLinux(ua string, agentPlatform string) (Platform, OSName, int) {

	// Kindle Fire
	if strings.Contains(ua, "kindle") {
		return PlatformKindle, OSKindle, 0
	}

	// Android, Kindle Fire
	if strings.Contains(ua, "android") {

		// Android
		if androidVersion.MatchString(agentPlatform) {
			v := strings.TrimPrefix(androidVersion.FindString(agentPlatform), "android ")
			i := strToInt(v)

			return PlatformLinux, OSAndroid, i
		}

		return PlatformLinux, OSAndroid, 0 //default
	}

	// ChromeOS
	if strings.Contains(ua, "cros") {
		return PlatformLinux, OSChromeOS, 0
	}

	// WebOS
	if strings.Contains(ua, "webos") || strings.Contains(ua, "hpwos") {
		return PlatformLinux, OSWebOS, 0
	}

	// Linux, "Linux-like"
	if strings.Contains(ua, "x11") || strings.Contains(ua, "bsd") || strings.Contains(ua, "suse") {
		return PlatformLinux, OSLinux, 0
	}

	return PlatformLinux, OSUnknown, 0 //default

}

// evaliOS returns the `Platform`, `OSName` and `int` of UAs with
// 'ipad' or 'iphone' listed as their platform.
func evaliOS(uaPlatform string, agentPlatform string) (Platform, OSName, int) {

	// iPhone
	if uaPlatform == "iphone" {
		return PlatformiPhone, OSiOS, getiOSVersion(agentPlatform)
	}

	// iPad
	if uaPlatform == "ipad" {
		return PlatformiPad, OSiOS, getiOSVersion(agentPlatform)
	}

	return PlatformiPad, OSUnknown, 0 //default
}

func evalWindowsPhone(agentPlatform string) (Platform, OSName, int) {

	if strings.Contains(agentPlatform, "windows phone os ") {
		v := strings.TrimPrefix(wpVerLong.FindString(agentPlatform), "windows phone os ")
		i := strToInt(v)

		return PlatformWindowsPhone, OSWindowsPhone, i
	}

	if strings.Contains(agentPlatform, "windows phone ") {
		v := strings.TrimPrefix(wpVerShort.FindString(agentPlatform), "windows phone ")
		i := strToInt(v)

		return PlatformWindowsPhone, OSWindowsPhone, i
	}

	return PlatformWindowsPhone, OSUnknown, 0 //default
}

func evalWindows(ua string) (Platform, OSName, int) {

	//Xbox -- it reads just like Windows
	if strings.Contains(ua, "xbox") {
		return PlatformXbox, OSXbox, 6
	}

	// Windows 10
	if strings.Contains(ua, "windows nt 10") {
		return PlatformWindows, OSWindows10, 10
	}

	// Windows 8
	if strings.Contains(ua, "windows nt 6.2") || strings.Contains(ua, "windows nt 6.3") {
		return PlatformWindows, OSWindows8, 6
	}

	// Windows 7
	if strings.Contains(ua, "windows nt 6.1") {
		return PlatformWindows, OSWindows7, 6
	}

	// Windows Vista
	if strings.Contains(ua, "windows nt 6.0") {
		return PlatformWindows, OSWindowsVista, 6
	}
	// Windows XP
	if strings.Contains(ua, "windows nt 5.1") || strings.Contains(ua, "windows nt 5.2") || strings.Contains(ua, "windows xp") {
		return PlatformWindows, OSWindowsXP, 5
	}

	// Windows 2000
	if strings.Contains(ua, "windows nt 5.0") {
		return PlatformWindows, OSWindows2000, 5
	}

	return PlatformWindows, OSUnknown, 0 //default

}

func evalMacintosh(uaPlatformGroup string) (Platform, OSName, int) {

	if strings.Contains(uaPlatformGroup, "os x 10_") {
		if osxVersionUnderscore.MatchString(uaPlatformGroup) {
			v := strings.TrimPrefix(osxVersionUnderscore.FindString(uaPlatformGroup), "os x 10_")
			i := strToInt(v)

			return PlatformMac, OSMacOSX, i
		}
	}

	if strings.Contains(uaPlatformGroup, "os x 10.") {
		if osxVersionDot.MatchString(uaPlatformGroup) {
			v := strings.TrimPrefix(osxVersionDot.FindString(uaPlatformGroup), "os x 10.")
			i := strToInt(v)

			return PlatformMac, OSMacOSX, i
		}
	}

	return PlatformMac, OSUnknown, 0 //default

}

// getiOSVersion accepts the platform portion of a UA string and returns
// an `int` of the major version of iOS, or `0` (unknown) on error.
func getiOSVersion(uaPlatformGroup string) int {
	v := strings.Split(iosVersion.FindString(uaPlatformGroup), " ")[2]
	i := strToInt(v)

	return i
}

// strToInt simply accepts a string and returns an `int`,
// with '0' being default.
func strToInt(str string) int {
	i, _ := strconv.Atoi(str)

	// if !i {
	// 	i = 0
	// }

	return i
}
<commit_msg>removed struct usage<commit_after>package uasurfer

import (
	"regexp"
	"strconv"
	"strings"
)

var (
	iosVersion           = regexp.MustCompile("(cpu|iphone) os \\d+")
	osxVersionUnderscore = regexp.MustCompile("os x 10_\\d+")
	osxVersionDot        = regexp.MustCompile("os x 10\\.\\d+")
	wpVerLong            = regexp.MustCompile("windows\\sphone\\sos\\s\\d+")
	wpVerShort           = regexp.MustCompile("windows\\sphone\\s\\d+")
	//kindleTest           = regexp.MustCompile("\\sKF[A-Z]{2,4}\\s")
	androidVersion = regexp.MustCompile("android \\d+")
)

// OS type returns a lowercase name (string) of the operating system, along with its major version (int).
// To allow easy of use with math operators, the version numbers for Mac and Win may be slightly unexpected.
// Here are some examples:
//
// 	For Windows XP (Windows NT 5.1), "windows" is the platform, "xp" is the name, and 5 the version.
// 	For OS X 10.5.1, "mac" is the platform, "os x" the name, and 5 the version.
// 	For Android 5.1, "linux" is the platform, "android" is the name, and 5 the version.
//	For iOS 5.1, "iphone" or "ipad" is the platform, "ios" is the name, and 5 the version.
// type OS struct {
// 	Name    OSName
// 	Version int
// }

// Retrieve the espoused platform and OS from the User-Agent string
func evalSystem(ua string) (Platform, OSName, int) {
	var (
		depth             int
		i                 int
		platformUASection []byte //represents portion of ua string that contains platform information
	)

	// more efficient method of getting the spec string than using native regexp package (TODO: need stronger profiling verification)
	for ; i < len(ua); i++ {
		if ua[i] == '(' {
			depth++
		} else if ua[i] == ')' {
			depth--
			// don't be too hungry for parentheticals, the first group should be enough
			if depth == 0 {
				break
			}
		}

		if depth >= 1 {
			platformUASection = append(platformUASection, ua[i])
		}
	}

	agentPlatform := string(platformUASection)
	specs := strings.Split(agentPlatform, ";")
	specs[0] = strings.TrimPrefix(specs[0], "(")

	//strict OS & version identification
	switch specs[0] {
	case "android":
		return evalLinux(ua, agentPlatform)
	case "bb10", "playbook":
		return PlatformBlackberry, OSBlackberry, 0
	case "x11", "linux":
		return evalLinux(ua, agentPlatform)
	case "ipad", "iphone":
		return evaliOS(specs[0], agentPlatform)
	case "macintosh":
		return evalMacintosh(ua)
	}

	// Blackberry
	if strings.Contains(ua, "blackberry") || strings.Contains(ua, "playbook") {
		return PlatformBlackberry, OSBlackberry, 0
	}

	// Windows Phone
	if strings.Contains(agentPlatform, "windows phone ") {
		return evalWindowsPhone(agentPlatform)
	}

	// Windows, Xbox
	if strings.Contains(ua, "windows ") {
		return evalWindows(ua)
	}

	// Kindle
	if strings.Contains(ua, "kindle/") {
		return PlatformKindle, OSKindle, 0
	}

	// Linux (broader attempt)
	if strings.Contains(ua, "linux") {
		return evalLinux(ua, agentPlatform)
	}

	// WebOS (non-linux flagged)
	if strings.Contains(ua, "webos") || strings.Contains(ua, "hpwos") {
		return PlatformLinux, OSWebOS, 0
	}

	// Nintendo
	if strings.Contains(ua, "nintendo") {
		return PlatformNintendo, OSNintendo, 0
	}

	// Playstation
	if strings.Contains(ua, "playstation") || strings.Contains(ua, "vita") || strings.Contains(ua, "psp") {
		return PlatformPlaystation, OSPlaystation, 0
	}

	return PlatformUnknown, OSUnknown, 0 //default

}

// evalLinux returns the `Platform`, `OSName` and `int` of UAs with
// 'linux' listed as their platform. The Platform returned is not
// necessarily PlatformLinux as it may be PlatformKindle, for example.
func evalLinux(ua string, agentPlatform string) (Platform, OSName, int) {

	// Kindle Fire
	if strings.Contains(ua, "kindle") {
		return PlatformKindle, OSKindle, 0
	}

	// Android, Kindle Fire
	if strings.Contains(ua, "android") {

		// Android
		if androidVersion.MatchString(agentPlatform) {
			v := strings.TrimPrefix(androidVersion.FindString(agentPlatform), "android ")
			i := strToInt(v)

			return PlatformLinux, OSAndroid, i
		}

		return PlatformLinux, OSAndroid, 0 //default
	}

	// ChromeOS
	if strings.Contains(ua, "cros") {
		return PlatformLinux, OSChromeOS, 0
	}

	// WebOS
	if strings.Contains(ua, "webos") || strings.Contains(ua, "hpwos") {
		return PlatformLinux, OSWebOS, 0
	}

	// Linux, "Linux-like"
	if strings.Contains(ua, "x11") || strings.Contains(ua, "bsd") || strings.Contains(ua, "suse") {
		return PlatformLinux, OSLinux, 0
	}

	return PlatformLinux, OSUnknown, 0 //default

}

// evaliOS returns the `Platform`, `OSName` and `int` of UAs with
// 'ipad' or 'iphone' listed as their platform.
func evaliOS(uaPlatform string, agentPlatform string) (Platform, OSName, int) {

	// iPhone
	if uaPlatform == "iphone" {
		return PlatformiPhone, OSiOS, getiOSVersion(agentPlatform)
	}

	// iPad
	if uaPlatform == "ipad" {
		return PlatformiPad, OSiOS, getiOSVersion(agentPlatform)
	}

	return PlatformiPad, OSUnknown, 0 //default
}

func evalWindowsPhone(agentPlatform string) (Platform, OSName, int) {

	if strings.Contains(agentPlatform, "windows phone os ") {
		v := strings.TrimPrefix(wpVerLong.FindString(agentPlatform), "windows phone os ")
		i := strToInt(v)

		return PlatformWindowsPhone, OSWindowsPhone, i
	}

	if strings.Contains(agentPlatform, "windows phone ") {
		v := strings.TrimPrefix(wpVerShort.FindString(agentPlatform), "windows phone ")
		i := strToInt(v)

		return PlatformWindowsPhone, OSWindowsPhone, i
	}

	return PlatformWindowsPhone, OSUnknown, 0 //default
}

func evalWindows(ua string) (Platform, OSName, int) {

	//Xbox -- it reads just like Windows
	if strings.Contains(ua, "xbox") {
		return PlatformXbox, OSXbox, 6
	}

	// Windows 10
	if strings.Contains(ua, "windows nt 10") {
		return PlatformWindows, OSWindows10, 10
	}

	// Windows 8
	if strings.Contains(ua, "windows nt 6.2") || strings.Contains(ua, "windows nt 6.3") {
		return PlatformWindows, OSWindows8, 6
	}

	// Windows 7
	if strings.Contains(ua, "windows nt 6.1") {
		return PlatformWindows, OSWindows7, 6
	}

	// Windows Vista
	if strings.Contains(ua, "windows nt 6.0") {
		return PlatformWindows, OSWindowsVista, 6
	}
	// Windows XP
	if strings.Contains(ua, "windows nt 5.1") || strings.Contains(ua, "windows nt 5.2") || strings.Contains(ua, "windows xp") {
		return PlatformWindows, OSWindowsXP, 5
	}

	// Windows 2000
	if strings.Contains(ua, "windows nt 5.0") {
		return PlatformWindows, OSWindows2000, 5
	}

	return PlatformWindows, OSUnknown, 0 //default

}

func evalMacintosh(uaPlatformGroup string) (Platform, OSName, int) {

	if strings.Contains(uaPlatformGroup, "os x 10_") {
		if osxVersionUnderscore.MatchString(uaPlatformGroup) {
			v := strings.TrimPrefix(osxVersionUnderscore.FindString(uaPlatformGroup), "os x 10_")
			i := strToInt(v)

			return PlatformMac, OSMacOSX, i
		}
	}

	if strings.Contains(uaPlatformGroup, "os x 10.") {
		if osxVersionDot.MatchString(uaPlatformGroup) {
			v := strings.TrimPrefix(osxVersionDot.FindString(uaPlatformGroup), "os x 10.")
			i := strToInt(v)

			return PlatformMac, OSMacOSX, i
		}
	}

	return PlatformMac, OSUnknown, 0 //default

}

// getiOSVersion accepts the platform portion of a UA string and returns
// an `int` of the major version of iOS, or `0` (unknown) on error.
func getiOSVersion(uaPlatformGroup string) int {
	v := strings.Split(iosVersion.FindString(uaPlatformGroup), " ")[2]
	i := strToInt(v)

	return i
}

// strToInt simply accepts a string and returns an `int`,
// with '0' being default.
func strToInt(str string) int {
	i, _ := strconv.Atoi(str)

	// if !i {
	// 	i = 0
	// }

	return i
}
<|endoftext|>


<commit_before>/*
Package stream provides filters that can be chained together in a manner
similar to Unix pipelines.  A simple example that prints all go files
under the current directory:

	stream.Run(
		stream.Find("."),
		stream.Grep(`\.go$`),
		stream.WriteLines(os.Stdout),
	)

Run is passed a sequence of filters that are chained together
(stream.Find, stream.Grep, stream.WriteLines are filters). The empty
input is passed to the first filter. The output of one filter is fed
as input to the next filter.

stream.Run is just one way to execute filters.  Others are stream.Contents
(returns the output of the last filter as a []string), and
stream.ForEach (executes a supplied function for every output item).

Error handling

Filter execution can result in errors.  These are returned from stream
functions normally.  For example, the following program will panic.
	err := stream.Run(
		stream.Items("hello", "world"),
		stream.Grep("["), // Invalid regular expression
		stream.WriteLines(os.Stdout),
	)
	if err != nil {
		panic(err)
	}

User defined filters

Each filter takes as input a sequence of strings (read from a channel)
and produces as output a sequence of strings (written to a channel).
The stream package provides a bunch of useful filters.  Applications can
define their own filters easily. For example, here is a filter that
repeats every input n times:

	func Repeat(n int) stream.FilterFunc {
		return func(arg stream.Arg) error {
			for s := range arg.In {
				for i := 0; i < n; i++ {
					arg.Out <- s
				}
			}
			return nil
		}
	}

	stream.Run(
		stream.Items("hello", "world"),
		Repeat(2),
		stream.WriteLines(os.Stdout),
	)

The output will be:

	hello
	hello
	world
	world

Note that Repeat returns a FilterFunc, a function type that implements the
Filter interface. This is a common implementation pattern: many simple filters
can be expressed as a single function of type FilterFunc.

Parameterized Filters

FilterFunc is an appropriate type to use for most filters like Repeat
above.  However for some filters, dynamic customization is
appropriate.  Such filters provide their own implementation of the
Filter interface with extra methods. For example, stream.Sort provides
extra methods that can be used to control how items are sorted:

	stream.Run(
		stream.Command("ls", "-l"),
		stream.Sort().Num(5),  // Sort numerically by size (column 5)
		stream.WriteLines(os.Stdout),
	)

*/
package stream

import (
	"fmt"
	"sync"
)

// filterErrors records errors accumulated during the execution of a filter.
type filterErrors struct {
	mu  sync.Mutex
	err error
}

func (e *filterErrors) record(err error) {
	if err != nil {
		e.mu.Lock()
		if e.err == nil {
			e.err = err
		}
		e.mu.Unlock()
	}
}

func (e *filterErrors) getError() error {
	e.mu.Lock()
	defer e.mu.Unlock()
	return e.err
}

// Arg contains the data passed to Filter.Run. Arg.In is a channel that
// produces the input to the filter, and Arg.Out is a channel that
// receives the output from the filter.
type Arg struct {
	In    <-chan string
	Out   chan<- string
	dummy bool // To allow later expansion
}

// The Filter interface represents a process that takes as input a
// sequence of strings from a channel and produces a sequence on
// another channel.
type Filter interface {
	// RunFilter reads a sequence of items from Arg.In and produces a
	// sequence of items on Arg.Out.  RunFilter returns nil on success,
	// an error otherwise.  RunFilter must *not* close the Arg.Out
	// channel.
	RunFilter(Arg) error
}

// FilterFunc is an adapter type that allows the use of ordinary
// functions as Filters.  If f is a function with the appropriate
// signature, FilterFunc(f) is a Filter that calls f.
type FilterFunc func(Arg) error

func (f FilterFunc) RunFilter(arg Arg) error { return f(arg) }

const channelBuffer = 1000

// Sequence returns a filter that is the concatenation of all filter arguments.
// The output of a filter is fed as input to the next filter.
func Sequence(filters ...Filter) Filter {
	if len(filters) == 1 {
		return filters[0]
	}
	return FilterFunc(func(arg Arg) error {
		e := &filterErrors{}
		in := arg.In
		for _, f := range filters {
			c := make(chan string, channelBuffer)
			go runFilter(f, Arg{In: in, Out: c}, e)
			in = c
		}
		for s := range in {
			arg.Out <- s
		}
		return e.getError()
	})
}

// Run executes the sequence of filters and discards all output.
// It returns either nil, an error if any filter reported an error.
func Run(filters ...Filter) error {
	return ForEach(Sequence(filters...), func(s string) {})
}

// ForEach calls fn(s) for every item s in the output of filter and
// returns either nil, or any error reported by the execution of the filter.
func ForEach(filter Filter, fn func(s string)) error {
	in := make(chan string, 0)
	close(in)
	out := make(chan string, channelBuffer)
	e := &filterErrors{}
	go runFilter(filter, Arg{In: in, Out: out}, e)
	for s := range out {
		fn(s)
	}
	return e.getError()
}

// Contents returns a slice that contains all items that are
// the output of filters.
func Contents(filters ...Filter) ([]string, error) {
	var result []string
	err := ForEach(Sequence(filters...), func(s string) {
		result = append(result, s)
	})
	if err != nil {
		result = nil // Discard results on error
	}
	return result, err
}

func runFilter(f Filter, arg Arg, e *filterErrors) {
	e.record(f.RunFilter(arg))
	close(arg.Out)
	for _ = range arg.In { // Discard all unhandled input
	}
}

// Items emits items.
func Items(items ...string) Filter {
	return FilterFunc(func(arg Arg) error {
		for _, s := range items {
			arg.Out <- s
		}
		return nil
	})
}

// Numbers emits the integers x..y
func Numbers(x, y int) Filter {
	return FilterFunc(func(arg Arg) error {
		for i := x; i <= y; i++ {
			arg.Out <- fmt.Sprint(i)
		}
		return nil
	})
}

// Map calls fn(x) for every item x and yields the outputs of the fn calls.
func Map(fn func(string) string) Filter {
	return FilterFunc(func(arg Arg) error {
		for s := range arg.In {
			arg.Out <- fn(s)
		}
		return nil
	})
}

// If emits every input x for which fn(x) is true.
func If(fn func(string) bool) Filter {
	return FilterFunc(func(arg Arg) error {
		for s := range arg.In {
			if fn(s) {
				arg.Out <- s
			}
		}
		return nil
	})
}

// Uniq squashes adjacent identical items in arg.In into a single output.
func Uniq() Filter {
	return FilterFunc(func(arg Arg) error {
		first := true
		last := ""
		for s := range arg.In {
			if first || last != s {
				arg.Out <- s
			}
			last = s
			first = false
		}
		return nil
	})
}

// UniqWithCount squashes adjacent identical items in arg.In into a single
// output prefixed with the count of identical items.
func UniqWithCount() Filter {
	return FilterFunc(func(arg Arg) error {
		current := ""
		count := 0
		for s := range arg.In {
			if s != current {
				if count > 0 {
					arg.Out <- fmt.Sprintf("%d %s", count, current)
				}
				count = 0
				current = s
			}
			count++
		}
		if count > 0 {
			arg.Out <- fmt.Sprintf("%d %s", count, current)
		}
		return nil
	})
}

// Reverse yields items in the reverse of the order it received them.
func Reverse() Filter {
	return FilterFunc(func(arg Arg) error {
		var data []string
		for s := range arg.In {
			data = append(data, s)
		}
		for i := len(data) - 1; i >= 0; i-- {
			arg.Out <- data[i]
		}
		return nil
	})
}

// First yields the first n items that it receives.
func First(n int) Filter {
	return FilterFunc(func(arg Arg) error {
		emitted := 0
		for s := range arg.In {
			if emitted < n {
				arg.Out <- s
				emitted++
			}
		}
		return nil
	})
}

// DropFirst yields all items except for the first n items that it receives.
func DropFirst(n int) Filter {
	return FilterFunc(func(arg Arg) error {
		emitted := 0
		for s := range arg.In {
			if emitted >= n {
				arg.Out <- s
			}
			emitted++
		}
		return nil
	})
}

// Last yields the last n items that it receives.
func Last(n int) Filter {
	return FilterFunc(func(arg Arg) error {
		var buf []string
		for s := range arg.In {
			buf = append(buf, s)
			if len(buf) > n {
				buf = buf[1:]
			}
		}
		for _, s := range buf {
			arg.Out <- s
		}
		return nil
	})
}

// DropLast yields all items except for the last n items that it receives.
func DropLast(n int) Filter {
	return FilterFunc(func(arg Arg) error {
		var buf []string
		for s := range arg.In {
			buf = append(buf, s)
			if len(buf) > n {
				arg.Out <- buf[0]
				buf = buf[1:]
			}
		}
		return nil
	})
}

// NumberLines prefixes its item with its index in the input sequence
// (starting at 1).
func NumberLines() Filter {
	return FilterFunc(func(arg Arg) error {
		line := 1
		for s := range arg.In {
			arg.Out <- fmt.Sprintf("%5d %s", line, s)
			line++
		}
		return nil
	})
}

// Slice emits s[startOffset:endOffset] for each input item s.  Note
// that Slice follows Go conventions, and unlike the "cut" utility,
// offsets are numbered starting at zero, and the end offset is not
// included in the output.
func Slice(startOffset, endOffset int) Filter {
	return FilterFunc(func(arg Arg) error {
		for s := range arg.In {
			if len(s) > endOffset {
				s = s[:endOffset]
			}
			if len(s) < startOffset {
				s = ""
			} else {
				s = s[startOffset:]
			}
			arg.Out <- s
		}
		return nil
	})
}

// Columns splits each item into columns and yields the concatenation
// of the columns numbers passed as arguments.  Columns are numbered
// starting at 1.  If a column number is bigger than the number of columns
// in an item, it is skipped.
func Columns(columns ...int) Filter {
	return FilterFunc(func(arg Arg) error {
		for _, c := range columns {
			if c <= 0 {
				return fmt.Errorf("stream.Columns: invalid column number %d", c)
			}
		}
		for s := range arg.In {
			result := ""
			for _, col := range columns {
				if _, c := column(s, col); c != "" {
					if result != "" {
						result = result + " "
					}
					result = result + c
				}
			}
			arg.Out <- result
		}
		return nil
	})
}
<commit_msg>added acknowledment<commit_after>/*
Package stream provides filters that can be chained together in a manner
similar to Unix pipelines.  A simple example that prints all go files
under the current directory:

	stream.Run(
		stream.Find("."),
		stream.Grep(`\.go$`),
		stream.WriteLines(os.Stdout),
	)

Run is passed a sequence of filters that are chained together
(stream.Find, stream.Grep, stream.WriteLines are filters). The empty
input is passed to the first filter. The output of one filter is fed
as input to the next filter.

stream.Run is just one way to execute filters.  Others are stream.Contents
(returns the output of the last filter as a []string), and
stream.ForEach (executes a supplied function for every output item).

Error handling

Filter execution can result in errors.  These are returned from stream
functions normally.  For example, the following program will panic.
	err := stream.Run(
		stream.Items("hello", "world"),
		stream.Grep("["), // Invalid regular expression
		stream.WriteLines(os.Stdout),
	)
	if err != nil {
		panic(err)
	}

User defined filters

Each filter takes as input a sequence of strings (read from a channel)
and produces as output a sequence of strings (written to a channel).
The stream package provides a bunch of useful filters.  Applications can
define their own filters easily. For example, here is a filter that
repeats every input n times:

	func Repeat(n int) stream.FilterFunc {
		return func(arg stream.Arg) error {
			for s := range arg.In {
				for i := 0; i < n; i++ {
					arg.Out <- s
				}
			}
			return nil
		}
	}

	stream.Run(
		stream.Items("hello", "world"),
		Repeat(2),
		stream.WriteLines(os.Stdout),
	)

The output will be:

	hello
	hello
	world
	world

Note that Repeat returns a FilterFunc, a function type that implements the
Filter interface. This is a common implementation pattern: many simple filters
can be expressed as a single function of type FilterFunc.

Parameterized Filters

FilterFunc is an appropriate type to use for most filters like Repeat
above.  However for some filters, dynamic customization is
appropriate.  Such filters provide their own implementation of the
Filter interface with extra methods. For example, stream.Sort provides
extra methods that can be used to control how items are sorted:

	stream.Run(
		stream.Command("ls", "-l"),
		stream.Sort().Num(5),  // Sort numerically by size (column 5)
		stream.WriteLines(os.Stdout),
	)

Acknowledgments

The interface of this package is inspired by and quite similar to
the http://labix.org/pipe package. Users may wish to consider that package
in case it fits their needs better.
*/
package stream

import (
	"fmt"
	"sync"
)

// filterErrors records errors accumulated during the execution of a filter.
type filterErrors struct {
	mu  sync.Mutex
	err error
}

func (e *filterErrors) record(err error) {
	if err != nil {
		e.mu.Lock()
		if e.err == nil {
			e.err = err
		}
		e.mu.Unlock()
	}
}

func (e *filterErrors) getError() error {
	e.mu.Lock()
	defer e.mu.Unlock()
	return e.err
}

// Arg contains the data passed to Filter.Run. Arg.In is a channel that
// produces the input to the filter, and Arg.Out is a channel that
// receives the output from the filter.
type Arg struct {
	In    <-chan string
	Out   chan<- string
	dummy bool // To allow later expansion
}

// The Filter interface represents a process that takes as input a
// sequence of strings from a channel and produces a sequence on
// another channel.
type Filter interface {
	// RunFilter reads a sequence of items from Arg.In and produces a
	// sequence of items on Arg.Out.  RunFilter returns nil on success,
	// an error otherwise.  RunFilter must *not* close the Arg.Out
	// channel.
	RunFilter(Arg) error
}

// FilterFunc is an adapter type that allows the use of ordinary
// functions as Filters.  If f is a function with the appropriate
// signature, FilterFunc(f) is a Filter that calls f.
type FilterFunc func(Arg) error

func (f FilterFunc) RunFilter(arg Arg) error { return f(arg) }

const channelBuffer = 1000

// Sequence returns a filter that is the concatenation of all filter arguments.
// The output of a filter is fed as input to the next filter.
func Sequence(filters ...Filter) Filter {
	if len(filters) == 1 {
		return filters[0]
	}
	return FilterFunc(func(arg Arg) error {
		e := &filterErrors{}
		in := arg.In
		for _, f := range filters {
			c := make(chan string, channelBuffer)
			go runFilter(f, Arg{In: in, Out: c}, e)
			in = c
		}
		for s := range in {
			arg.Out <- s
		}
		return e.getError()
	})
}

// Run executes the sequence of filters and discards all output.
// It returns either nil, an error if any filter reported an error.
func Run(filters ...Filter) error {
	return ForEach(Sequence(filters...), func(s string) {})
}

// ForEach calls fn(s) for every item s in the output of filter and
// returns either nil, or any error reported by the execution of the filter.
func ForEach(filter Filter, fn func(s string)) error {
	in := make(chan string, 0)
	close(in)
	out := make(chan string, channelBuffer)
	e := &filterErrors{}
	go runFilter(filter, Arg{In: in, Out: out}, e)
	for s := range out {
		fn(s)
	}
	return e.getError()
}

// Contents returns a slice that contains all items that are
// the output of filters.
func Contents(filters ...Filter) ([]string, error) {
	var result []string
	err := ForEach(Sequence(filters...), func(s string) {
		result = append(result, s)
	})
	if err != nil {
		result = nil // Discard results on error
	}
	return result, err
}

func runFilter(f Filter, arg Arg, e *filterErrors) {
	e.record(f.RunFilter(arg))
	close(arg.Out)
	for _ = range arg.In { // Discard all unhandled input
	}
}

// Items emits items.
func Items(items ...string) Filter {
	return FilterFunc(func(arg Arg) error {
		for _, s := range items {
			arg.Out <- s
		}
		return nil
	})
}

// Numbers emits the integers x..y
func Numbers(x, y int) Filter {
	return FilterFunc(func(arg Arg) error {
		for i := x; i <= y; i++ {
			arg.Out <- fmt.Sprint(i)
		}
		return nil
	})
}

// Map calls fn(x) for every item x and yields the outputs of the fn calls.
func Map(fn func(string) string) Filter {
	return FilterFunc(func(arg Arg) error {
		for s := range arg.In {
			arg.Out <- fn(s)
		}
		return nil
	})
}

// If emits every input x for which fn(x) is true.
func If(fn func(string) bool) Filter {
	return FilterFunc(func(arg Arg) error {
		for s := range arg.In {
			if fn(s) {
				arg.Out <- s
			}
		}
		return nil
	})
}

// Uniq squashes adjacent identical items in arg.In into a single output.
func Uniq() Filter {
	return FilterFunc(func(arg Arg) error {
		first := true
		last := ""
		for s := range arg.In {
			if first || last != s {
				arg.Out <- s
			}
			last = s
			first = false
		}
		return nil
	})
}

// UniqWithCount squashes adjacent identical items in arg.In into a single
// output prefixed with the count of identical items.
func UniqWithCount() Filter {
	return FilterFunc(func(arg Arg) error {
		current := ""
		count := 0
		for s := range arg.In {
			if s != current {
				if count > 0 {
					arg.Out <- fmt.Sprintf("%d %s", count, current)
				}
				count = 0
				current = s
			}
			count++
		}
		if count > 0 {
			arg.Out <- fmt.Sprintf("%d %s", count, current)
		}
		return nil
	})
}

// Reverse yields items in the reverse of the order it received them.
func Reverse() Filter {
	return FilterFunc(func(arg Arg) error {
		var data []string
		for s := range arg.In {
			data = append(data, s)
		}
		for i := len(data) - 1; i >= 0; i-- {
			arg.Out <- data[i]
		}
		return nil
	})
}

// First yields the first n items that it receives.
func First(n int) Filter {
	return FilterFunc(func(arg Arg) error {
		emitted := 0
		for s := range arg.In {
			if emitted < n {
				arg.Out <- s
				emitted++
			}
		}
		return nil
	})
}

// DropFirst yields all items except for the first n items that it receives.
func DropFirst(n int) Filter {
	return FilterFunc(func(arg Arg) error {
		emitted := 0
		for s := range arg.In {
			if emitted >= n {
				arg.Out <- s
			}
			emitted++
		}
		return nil
	})
}

// Last yields the last n items that it receives.
func Last(n int) Filter {
	return FilterFunc(func(arg Arg) error {
		var buf []string
		for s := range arg.In {
			buf = append(buf, s)
			if len(buf) > n {
				buf = buf[1:]
			}
		}
		for _, s := range buf {
			arg.Out <- s
		}
		return nil
	})
}

// DropLast yields all items except for the last n items that it receives.
func DropLast(n int) Filter {
	return FilterFunc(func(arg Arg) error {
		var buf []string
		for s := range arg.In {
			buf = append(buf, s)
			if len(buf) > n {
				arg.Out <- buf[0]
				buf = buf[1:]
			}
		}
		return nil
	})
}

// NumberLines prefixes its item with its index in the input sequence
// (starting at 1).
func NumberLines() Filter {
	return FilterFunc(func(arg Arg) error {
		line := 1
		for s := range arg.In {
			arg.Out <- fmt.Sprintf("%5d %s", line, s)
			line++
		}
		return nil
	})
}

// Slice emits s[startOffset:endOffset] for each input item s.  Note
// that Slice follows Go conventions, and unlike the "cut" utility,
// offsets are numbered starting at zero, and the end offset is not
// included in the output.
func Slice(startOffset, endOffset int) Filter {
	return FilterFunc(func(arg Arg) error {
		for s := range arg.In {
			if len(s) > endOffset {
				s = s[:endOffset]
			}
			if len(s) < startOffset {
				s = ""
			} else {
				s = s[startOffset:]
			}
			arg.Out <- s
		}
		return nil
	})
}

// Columns splits each item into columns and yields the concatenation
// of the columns numbers passed as arguments.  Columns are numbered
// starting at 1.  If a column number is bigger than the number of columns
// in an item, it is skipped.
func Columns(columns ...int) Filter {
	return FilterFunc(func(arg Arg) error {
		for _, c := range columns {
			if c <= 0 {
				return fmt.Errorf("stream.Columns: invalid column number %d", c)
			}
		}
		for s := range arg.In {
			result := ""
			for _, col := range columns {
				if _, c := column(s, col); c != "" {
					if result != "" {
						result = result + " "
					}
					result = result + c
				}
			}
			arg.Out <- result
		}
		return nil
	})
}
<|endoftext|>


<commit_before>package rpc

import (
	"errors"
	"fmt"
	"io"
	"launchpad.net/juju-core/log"
	"sync"
)

var ErrShutdown = errors.New("connection is shut down")

// A ClientCodec implements writing of RPC requests and reading of RPC
// responses for the client side of an RPC session.  The client calls
// WriteRequest to write a request to the connection and calls
// ReadResponseHeader and ReadResponseBody in pairs to read responses.
// The client calls Close when finished with the connection.
// The params argument to WriteRequest will always be of struct
// type; the result argument to ReadResponseBody will always be
// a non-nil pointer to a struct.
type ClientCodec interface {
	WriteRequest(req *Request, params interface{}) error
	ReadResponseHeader(resp *Response) error
	ReadResponseBody(result interface{}) error
	Close() error
}

// Client represents an RPC Client.  There may be multiple outstanding
// Calls associated with a single Client, and a Client may be used by
// multiple goroutines simultaneously.
type Client struct {
	sending sync.Mutex
	codec   ClientCodec
	request Request

	mutex    sync.Mutex // protects the following fields
	reqId    uint64
	pending  map[uint64]*Call
	closing  bool
	shutdown bool
}

// NewClient returns a new Client to handle requests to the set of
// services at the other end of the connection.  The given codec is used
// to encode requests and decode responses.
func NewClientWithCodec(codec ClientCodec) *Client {
	client := &Client{
		codec:   codec,
		pending: make(map[uint64]*Call),
	}
	go client.input()
	return client
}

// Call represents an active RPC.
type Call struct {
	Type     string
	Id       string
	Request  string
	Params   interface{}
	Response interface{}
	Error    error
	Done     chan *Call
}

// ServerError represents an error returned from an RPC server.
type ServerError struct {
	Message string
	Code    string
}

func (e *ServerError) Error() string {
	return "server error: " + e.Message
}

func (e *ServerError) ErrorCode() string {
	return e.Code
}

func (client *Client) Close() error {
	client.mutex.Lock()
	if client.shutdown || client.closing {
		client.mutex.Unlock()
		return ErrShutdown
	}
	client.closing = true
	client.mutex.Unlock()
	return client.codec.Close()
}

func (client *Client) send(call *Call) {
	client.sending.Lock()
	defer client.sending.Unlock()

	// Register this call.
	client.mutex.Lock()
	if client.shutdown || client.closing {
		call.Error = ErrShutdown
		client.mutex.Unlock()
		call.done()
		return
	}

	client.reqId++
	reqId := client.reqId
	client.pending[reqId] = call
	client.mutex.Unlock()

	// Encode and send the request.
	client.request = Request{
		RequestId: reqId,
		Type:      call.Type,
		Id:        call.Id,
		Request:   call.Request,
	}
	params := call.Params
	if params == nil {
		params = struct{}{}
	}
	if err := client.codec.WriteRequest(&client.request, params); err != nil {
		client.mutex.Lock()
		call = client.pending[reqId]
		delete(client.pending, reqId)
		client.mutex.Unlock()
		if call != nil {
			call.Error = err
			call.done()
		}
	}
}

func (client *Client) readBody(resp interface{}) error {
	if resp == nil {
		resp = &struct{}{}
	}
	err := client.codec.ReadResponseBody(resp)
	if err != nil {
		err = fmt.Errorf("error reading body: %v", err)
	}
	return err
}

func (client *Client) input() {
	var err error
	var response Response
	for err == nil {
		response = Response{}
		err = client.codec.ReadResponseHeader(&response)
		if err != nil {
			break
		}
		reqId := response.RequestId
		client.mutex.Lock()
		call := client.pending[reqId]
		delete(client.pending, reqId)
		client.mutex.Unlock()

		switch {
		case call == nil:
			// We've got no pending call. That usually means that
			// WriteRequest partially failed, and call was already
			// removed; response is a server telling us about an
			// error reading request body. We should still attempt
			// to read error body, but there's no one to give it to.
			err = client.readBody(nil)
		case response.Error != "":
			// We've got an error response. Give this to the request;
			// any subsequent requests will get the ReadResponseBody
			// error if there is one.
			call.Error = &ServerError{
				Message: response.Error,
				Code:    response.ErrorCode,
			}
			err = client.readBody(nil)
			call.done()
		default:
			err = client.readBody(call.Response)
			call.done()
		}
	}
	// Terminate pending calls.
	client.sending.Lock()
	client.mutex.Lock()
	client.shutdown = true
	closing := client.closing
	if err == io.EOF {
		if closing {
			err = ErrShutdown
		} else {
			err = io.ErrUnexpectedEOF
		}
	}
	for _, call := range client.pending {
		call.Error = err
		call.done()
	}
	client.pending = nil
	client.mutex.Unlock()
	client.sending.Unlock()
	if err != io.EOF && !closing {
		log.Printf("rpc: client protocol error: %v", err)
	}
}

func (call *Call) done() {
	select {
	case call.Done <- call:
		// ok
	default:
		// We don't want to block here.  It is the caller's responsibility to make
		// sure the channel has enough buffer space. See comment in Go().
		log.Printf("rpc: discarding Call reply due to insufficient Done chan capacity")
	}
}

// Call invokes the named action on the object of the given type with
// the given id.  The returned values will be stored in response, which
// should be a pointer.  If the action fails remotely, the returned
// error will be of type ServerError.
// The params value may be nil if no parameters are provided;
// the response value may be nil to indicate that any result
// should be discarded.
func (c *Client) Call(objType, id, action string, params, response interface{}) error {
	call := <-c.Go(objType, id, action, params, response, make(chan *Call, 1)).Done
	return call.Error
}

// Go invokes the request asynchronously.  It returns the Call structure representing
// the invocation.  The done channel will signal when the call is complete by returning
// the same Call object.  If done is nil, Go will allocate a new channel.
// If non-nil, done must be buffered or Go will deliberately panic.
func (c *Client) Go(objType, id, request string, args, response interface{}, done chan *Call) *Call {
	if done == nil {
		done = make(chan *Call, 10) // buffered.
	} else {
		// If caller passes done != nil, it must arrange that
		// done has enough buffer for the number of simultaneous
		// RPCs that will be using that channel.  If the channel
		// is totally unbuffered, it's best not to run at all.
		if cap(done) == 0 {
			panic("launchpad.net/juju-core/rpc: done channel is unbuffered")
		}
	}
	call := &Call{
		Type:     objType,
		Id:       id,
		Request:  request,
		Params:   args,
		Response: response,
		Done:     done,
	}
	c.send(call)
	return call
}
<commit_msg>rpc: remove blank line<commit_after>package rpc

import (
	"errors"
	"fmt"
	"io"
	"launchpad.net/juju-core/log"
	"sync"
)

var ErrShutdown = errors.New("connection is shut down")

// A ClientCodec implements writing of RPC requests and reading of RPC
// responses for the client side of an RPC session.  The client calls
// WriteRequest to write a request to the connection and calls
// ReadResponseHeader and ReadResponseBody in pairs to read responses.
// The client calls Close when finished with the connection.
// The params argument to WriteRequest will always be of struct
// type; the result argument to ReadResponseBody will always be
// a non-nil pointer to a struct.
type ClientCodec interface {
	WriteRequest(req *Request, params interface{}) error
	ReadResponseHeader(resp *Response) error
	ReadResponseBody(result interface{}) error
	Close() error
}

// Client represents an RPC Client.  There may be multiple outstanding
// Calls associated with a single Client, and a Client may be used by
// multiple goroutines simultaneously.
type Client struct {
	sending sync.Mutex
	codec   ClientCodec
	request Request

	mutex    sync.Mutex // protects the following fields
	reqId    uint64
	pending  map[uint64]*Call
	closing  bool
	shutdown bool
}

// NewClient returns a new Client to handle requests to the set of
// services at the other end of the connection.  The given codec is used
// to encode requests and decode responses.
func NewClientWithCodec(codec ClientCodec) *Client {
	client := &Client{
		codec:   codec,
		pending: make(map[uint64]*Call),
	}
	go client.input()
	return client
}

// Call represents an active RPC.
type Call struct {
	Type     string
	Id       string
	Request  string
	Params   interface{}
	Response interface{}
	Error    error
	Done     chan *Call
}

// ServerError represents an error returned from an RPC server.
type ServerError struct {
	Message string
	Code    string
}

func (e *ServerError) Error() string {
	return "server error: " + e.Message
}

func (e *ServerError) ErrorCode() string {
	return e.Code
}

func (client *Client) Close() error {
	client.mutex.Lock()
	if client.shutdown || client.closing {
		client.mutex.Unlock()
		return ErrShutdown
	}
	client.closing = true
	client.mutex.Unlock()
	return client.codec.Close()
}

func (client *Client) send(call *Call) {
	client.sending.Lock()
	defer client.sending.Unlock()

	// Register this call.
	client.mutex.Lock()
	if client.shutdown || client.closing {
		call.Error = ErrShutdown
		client.mutex.Unlock()
		call.done()
		return
	}
	client.reqId++
	reqId := client.reqId
	client.pending[reqId] = call
	client.mutex.Unlock()

	// Encode and send the request.
	client.request = Request{
		RequestId: reqId,
		Type:      call.Type,
		Id:        call.Id,
		Request:   call.Request,
	}
	params := call.Params
	if params == nil {
		params = struct{}{}
	}
	if err := client.codec.WriteRequest(&client.request, params); err != nil {
		client.mutex.Lock()
		call = client.pending[reqId]
		delete(client.pending, reqId)
		client.mutex.Unlock()
		if call != nil {
			call.Error = err
			call.done()
		}
	}
}

func (client *Client) readBody(resp interface{}) error {
	if resp == nil {
		resp = &struct{}{}
	}
	err := client.codec.ReadResponseBody(resp)
	if err != nil {
		err = fmt.Errorf("error reading body: %v", err)
	}
	return err
}

func (client *Client) input() {
	var err error
	var response Response
	for err == nil {
		response = Response{}
		err = client.codec.ReadResponseHeader(&response)
		if err != nil {
			break
		}
		reqId := response.RequestId
		client.mutex.Lock()
		call := client.pending[reqId]
		delete(client.pending, reqId)
		client.mutex.Unlock()

		switch {
		case call == nil:
			// We've got no pending call. That usually means that
			// WriteRequest partially failed, and call was already
			// removed; response is a server telling us about an
			// error reading request body. We should still attempt
			// to read error body, but there's no one to give it to.
			err = client.readBody(nil)
		case response.Error != "":
			// We've got an error response. Give this to the request;
			// any subsequent requests will get the ReadResponseBody
			// error if there is one.
			call.Error = &ServerError{
				Message: response.Error,
				Code:    response.ErrorCode,
			}
			err = client.readBody(nil)
			call.done()
		default:
			err = client.readBody(call.Response)
			call.done()
		}
	}
	// Terminate pending calls.
	client.sending.Lock()
	client.mutex.Lock()
	client.shutdown = true
	closing := client.closing
	if err == io.EOF {
		if closing {
			err = ErrShutdown
		} else {
			err = io.ErrUnexpectedEOF
		}
	}
	for _, call := range client.pending {
		call.Error = err
		call.done()
	}
	client.pending = nil
	client.mutex.Unlock()
	client.sending.Unlock()
	if err != io.EOF && !closing {
		log.Printf("rpc: client protocol error: %v", err)
	}
}

func (call *Call) done() {
	select {
	case call.Done <- call:
		// ok
	default:
		// We don't want to block here.  It is the caller's responsibility to make
		// sure the channel has enough buffer space. See comment in Go().
		log.Printf("rpc: discarding Call reply due to insufficient Done chan capacity")
	}
}

// Call invokes the named action on the object of the given type with
// the given id.  The returned values will be stored in response, which
// should be a pointer.  If the action fails remotely, the returned
// error will be of type ServerError.
// The params value may be nil if no parameters are provided;
// the response value may be nil to indicate that any result
// should be discarded.
func (c *Client) Call(objType, id, action string, params, response interface{}) error {
	call := <-c.Go(objType, id, action, params, response, make(chan *Call, 1)).Done
	return call.Error
}

// Go invokes the request asynchronously.  It returns the Call structure representing
// the invocation.  The done channel will signal when the call is complete by returning
// the same Call object.  If done is nil, Go will allocate a new channel.
// If non-nil, done must be buffered or Go will deliberately panic.
func (c *Client) Go(objType, id, request string, args, response interface{}, done chan *Call) *Call {
	if done == nil {
		done = make(chan *Call, 10) // buffered.
	} else {
		// If caller passes done != nil, it must arrange that
		// done has enough buffer for the number of simultaneous
		// RPCs that will be using that channel.  If the channel
		// is totally unbuffered, it's best not to run at all.
		if cap(done) == 0 {
			panic("launchpad.net/juju-core/rpc: done channel is unbuffered")
		}
	}
	call := &Call{
		Type:     objType,
		Id:       id,
		Request:  request,
		Params:   args,
		Response: response,
		Done:     done,
	}
	c.send(call)
	return call
}
<|endoftext|>