<commit_before>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net"
	"os"
	"os/exec"
	"reflect"
	"runtime"
	"sync"
	"text/template"
	"time"

	"github.com/armon/consul-api"
)

const (
	// failSleep controls how long to sleep on a failure
	failSleep = 5 * time.Second

	// maxFailures controls the maximum number of failures
	// before we limit the sleep value
	maxFailures = 5

	// waitTime is used to control how long we do a blocking
	// query for
	waitTime = 60 * time.Second
)

type backendData struct {
	sync.Mutex

	// Client is a shared Consul client
	Client *consulapi.Client

	// Servers maps each watch path to a list of entries
	Servers map[*WatchPath][]*consulapi.ServiceEntry

	// Backends maps a backend to a list of watch paths used
	// to build up the server list
	Backends map[string][]*WatchPath

	// ChangeCh is used to inform of an update
	ChangeCh chan struct{}

	// StopCh is used to trigger a stop
	StopCh chan struct{}
}

// watch is used to start a long running watcher to handle updates.
// Returns a stopCh, and a finishCh.
func watch(conf *Config) (chan struct{}, chan struct{}) {
	stopCh := make(chan struct{})
	finishCh := make(chan struct{})
	go runWatch(conf, stopCh, finishCh)
	return stopCh, finishCh
}

// runWatch is a long running routine that watches with a
// given configuration
func runWatch(conf *Config, stopCh, doneCh chan struct{}) {
	defer close(doneCh)

	// Create the consul client
	consulConf := consulapi.DefaultConfig()
	if conf.Address != "" {
		consulConf.Address = conf.Address
	}

	// Attempt to contact the agent
	client, err := consulapi.NewClient(consulConf)
	if err != nil {
		log.Printf("[ERR] Failed to initialize consul client: %v", err)
		return
	}
	if _, err := client.Agent().NodeName(); err != nil {
		log.Printf("[ERR] Failed to contact consul agent: %v", err)
		return
	}

	// Create a backend store
	data := &backendData{
		Client:   client,
		Servers:  make(map[*WatchPath][]*consulapi.ServiceEntry),
		Backends: make(map[string][]*WatchPath),
		ChangeCh: make(chan struct{}, 1),
		StopCh:   stopCh,
	}

	// Start the watches
	data.Lock()
	for idx, watch := range conf.watches {
		data.Backends[watch.Backend] = append(data.Backends[watch.Backend], watch)
		go runSingleWatch(conf, data, idx, watch)
	}
	data.Unlock()

	// Monitor for changes or stop
	for {
		select {
		case <-data.ChangeCh:
			if maybeRefresh(conf, data) {
				return
			}

		case <-stopCh:
			return
		}
	}
}

// maybeRefresh is used to handle a potential config update
func maybeRefresh(conf *Config, data *backendData) (exit bool) {
	// Ignore initial updates until all the data is ready
	if !allWatchesReturned(conf, data) {
		return
	}

	// Merge the data for each backend
	backendServers := aggregateServers(data)

	// Build the output template
	output, err := buildTemplate(conf, backendServers)
	if err != nil {
		log.Printf("[ERR] %v", err)
		return true
	}

	// Check for a dry run
	if conf.DryRun {
		fmt.Printf("%s\n", output)
		return true
	}

	// Write out the configuration
	if err := ioutil.WriteFile(conf.Path, output, 0660); err != nil {
		log.Printf("[ERR] Failed to write config file: %v", err)
		return true
	}
	log.Printf("[INFO] Updated configuration file at %s", conf.Path)

	// Invoke the reload hook
	if err := reload(conf); err != nil {
		log.Printf("[ERR] Failed to reload: %v", err)
	} else {
		log.Printf("[INFO] Completed reload")
	}
	return
}

// allWatchesReturned checks if all the watches have some
// data registered. Prevents early template generation.
func allWatchesReturned(conf *Config, data *backendData) bool {
	data.Lock()
	defer data.Unlock()
	return len(data.Servers) >= len(conf.watches)
}

// aggregateServers merges the watches belonging to each
// backend together to prepare for template generation
func aggregateServers(data *backendData) map[string][]*consulapi.ServiceEntry {
	backendServers := make(map[string][]*consulapi.ServiceEntry)
	data.Lock()
	defer data.Unlock()
	for backend, watches := range data.Backends {
		var all []*consulapi.ServiceEntry
		for _, watch := range watches {
			entries := data.Servers[watch]
			all = append(all, entries...)
		}
		backendServers[backend] = all
	}
	return backendServers
}

// buildTemplate is used to build the output template
// from the configuration and server list
func buildTemplate(conf *Config,
	servers map[string][]*consulapi.ServiceEntry) ([]byte, error) {
	// Format the output
	outVars := formatOutput(servers)

	// Read the template
	raw, err := ioutil.ReadFile(conf.Template)
	if err != nil {
		return nil, fmt.Errorf("Failed to read template: %v", err)
	}

	// Create the template
	templ, err := template.New("output").Parse(string(raw))
	if err != nil {
		return nil, fmt.Errorf("Failed to parse the template: %v", err)
	}

	// Generate the output
	var output bytes.Buffer
	if err := templ.Execute(&output, outVars); err != nil {
		return nil, fmt.Errorf("Failed to generate the template: %v", err)
	}
	return output.Bytes(), nil
}

// runSingleWatch is used to query a single watch path for changes
func runSingleWatch(conf *Config, data *backendData, idx int, watch *WatchPath) {
	health := data.Client.Health()
	opts := &consulapi.QueryOptions{
		WaitTime: waitTime,
	}
	if watch.Datacenter != "" {
		opts.Datacenter = watch.Datacenter
	}

	failures := 0
	for {
		if shouldStop(data.StopCh) {
			return
		}
		entries, qm, err := health.Service(watch.Service, watch.Tag, true, opts)
		if err != nil {
			log.Printf("[ERR] Failed to fetch service nodes: %v", err)
		}

		// Patch the entries as necessary
		for _, entry := range entries {
			// Modify the node name to prefix with the watch ID. This
			// prevents a name conflict on duplicate names
			entry.Node.Node = fmt.Sprintf("%d_%s", idx, entry.Node.Node)

			// Patch the port if provided
			if watch.Port != 0 {
				entry.Service.Port = watch.Port
			}
		}

		// Update the entries. If this is the first read, do it on error
		data.Lock()
		old, ok := data.Servers[watch]
		if !ok || (err == nil && !reflect.DeepEqual(old, entries)) {
			data.Servers[watch] = entries
			asyncNotify(data.ChangeCh)
			if !conf.DryRun {
				log.Printf("[DEBUG] Updated nodes for %v", watch.Spec)
			}
		}
		data.Unlock()

		// Stop immediately on a dry run
		if conf.DryRun {
			return
		}

		// Check for an error
		if err != nil {
			failures = min(failures+1, maxFailures)
			time.Sleep(backoff(failSleep, failures))
		} else {
			failures = 0
			opts.WaitIndex = qm.LastIndex
		}
	}
}

// reload is used to invoke the reload command
func reload(conf *Config) error {
	// Determine the shell invocation based on OS
	var shell, flag string
	if runtime.GOOS == "windows" {
		shell = "cmd"
		flag = "/C"
	} else {
		shell = "/bin/sh"
		flag = "-c"
	}

	// Create and invoke the command
	cmd := exec.Command(shell, flag, conf.ReloadCommand)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

// shouldStop checks for a closed control channel
func shouldStop(ch chan struct{}) bool {
	select {
	case <-ch:
		return true
	default:
		return false
	}
}

// asyncNotify is used to notify a channel
func asyncNotify(ch chan struct{}) {
	select {
	case ch <- struct{}{}:
	default:
	}
}

// min returns the min of two ints
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

// backoff is used to compute an exponential backoff
func backoff(interval time.Duration, times int) time.Duration {
	times--
	return interval * time.Duration(math.Pow(2, float64(times)))
}

// ServerEntry is the full data structure exposed to
// the template for each server
type ServerEntry struct {
	ID      string
	Service string
	Tags    []string
	Port    int
	IP      net.IP
	Node    string
}

// String is the default text representation of a server
func (se *ServerEntry) String() string {
	name := fmt.Sprintf("%s_%s", se.Node, se.ID)
	addr := &net.TCPAddr{IP: se.IP, Port: se.Port}
	return fmt.Sprintf("server %s %s", name, addr)
}

// formatOutput converts the service entries into a format
// suitable for templating into the HAProxy file
func formatOutput(inp map[string][]*consulapi.ServiceEntry) map[string][]*ServerEntry {
	out := make(map[string][]*ServerEntry)
	for backend, entries := range inp {
		servers := make([]*ServerEntry, len(entries))
		for idx, entry := range entries {
			servers[idx] = &ServerEntry{
				ID:      entry.Service.ID,
				Service: entry.Service.Service,
				Tags:    entry.Service.Tags,
				Port:    entry.Service.Port,
				IP:      net.ParseIP(entry.Node.Address),
				Node:    entry.Node.Node,
			}
		}
		out[backend] = servers
	}
	return out
}
<commit_msg>Ignore the output of a check for purposes of change detection<commit_after>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net"
	"os"
	"os/exec"
	"reflect"
	"runtime"
	"sync"
	"text/template"
	"time"

	"github.com/armon/consul-api"
)

const (
	// failSleep controls how long to sleep on a failure
	failSleep = 5 * time.Second

	// maxFailures controls the maximum number of failures
	// before we limit the sleep value
	maxFailures = 5

	// waitTime is used to control how long we do a blocking
	// query for
	waitTime = 60 * time.Second
)

type backendData struct {
	sync.Mutex

	// Client is a shared Consul client
	Client *consulapi.Client

	// Servers maps each watch path to a list of entries
	Servers map[*WatchPath][]*consulapi.ServiceEntry

	// Backends maps a backend to a list of watch paths used
	// to build up the server list
	Backends map[string][]*WatchPath

	// ChangeCh is used to inform of an update
	ChangeCh chan struct{}

	// StopCh is used to trigger a stop
	StopCh chan struct{}
}

// watch is used to start a long running watcher to handle updates.
// Returns a stopCh, and a finishCh.
func watch(conf *Config) (chan struct{}, chan struct{}) {
	stopCh := make(chan struct{})
	finishCh := make(chan struct{})
	go runWatch(conf, stopCh, finishCh)
	return stopCh, finishCh
}

// runWatch is a long running routine that watches with a
// given configuration
func runWatch(conf *Config, stopCh, doneCh chan struct{}) {
	defer close(doneCh)

	// Create the consul client
	consulConf := consulapi.DefaultConfig()
	if conf.Address != "" {
		consulConf.Address = conf.Address
	}

	// Attempt to contact the agent
	client, err := consulapi.NewClient(consulConf)
	if err != nil {
		log.Printf("[ERR] Failed to initialize consul client: %v", err)
		return
	}
	if _, err := client.Agent().NodeName(); err != nil {
		log.Printf("[ERR] Failed to contact consul agent: %v", err)
		return
	}

	// Create a backend store
	data := &backendData{
		Client:   client,
		Servers:  make(map[*WatchPath][]*consulapi.ServiceEntry),
		Backends: make(map[string][]*WatchPath),
		ChangeCh: make(chan struct{}, 1),
		StopCh:   stopCh,
	}

	// Start the watches
	data.Lock()
	for idx, watch := range conf.watches {
		data.Backends[watch.Backend] = append(data.Backends[watch.Backend], watch)
		go runSingleWatch(conf, data, idx, watch)
	}
	data.Unlock()

	// Monitor for changes or stop
	for {
		select {
		case <-data.ChangeCh:
			if maybeRefresh(conf, data) {
				return
			}

		case <-stopCh:
			return
		}
	}
}

// maybeRefresh is used to handle a potential config update
func maybeRefresh(conf *Config, data *backendData) (exit bool) {
	// Ignore initial updates until all the data is ready
	if !allWatchesReturned(conf, data) {
		return
	}

	// Merge the data for each backend
	backendServers := aggregateServers(data)

	// Build the output template
	output, err := buildTemplate(conf, backendServers)
	if err != nil {
		log.Printf("[ERR] %v", err)
		return true
	}

	// Check for a dry run
	if conf.DryRun {
		fmt.Printf("%s\n", output)
		return true
	}

	// Write out the configuration
	if err := ioutil.WriteFile(conf.Path, output, 0660); err != nil {
		log.Printf("[ERR] Failed to write config file: %v", err)
		return true
	}
	log.Printf("[INFO] Updated configuration file at %s", conf.Path)

	// Invoke the reload hook
	if err := reload(conf); err != nil {
		log.Printf("[ERR] Failed to reload: %v", err)
	} else {
		log.Printf("[INFO] Completed reload")
	}
	return
}

// allWatchesReturned checks if all the watches have some
// data registered. Prevents early template generation.
func allWatchesReturned(conf *Config, data *backendData) bool {
	data.Lock()
	defer data.Unlock()
	return len(data.Servers) >= len(conf.watches)
}

// aggregateServers merges the watches belonging to each
// backend together to prepare for template generation
func aggregateServers(data *backendData) map[string][]*consulapi.ServiceEntry {
	backendServers := make(map[string][]*consulapi.ServiceEntry)
	data.Lock()
	defer data.Unlock()
	for backend, watches := range data.Backends {
		var all []*consulapi.ServiceEntry
		for _, watch := range watches {
			entries := data.Servers[watch]
			all = append(all, entries...)
		}
		backendServers[backend] = all
	}
	return backendServers
}

// buildTemplate is used to build the output template
// from the configuration and server list
func buildTemplate(conf *Config,
	servers map[string][]*consulapi.ServiceEntry) ([]byte, error) {
	// Format the output
	outVars := formatOutput(servers)

	// Read the template
	raw, err := ioutil.ReadFile(conf.Template)
	if err != nil {
		return nil, fmt.Errorf("Failed to read template: %v", err)
	}

	// Create the template
	templ, err := template.New("output").Parse(string(raw))
	if err != nil {
		return nil, fmt.Errorf("Failed to parse the template: %v", err)
	}

	// Generate the output
	var output bytes.Buffer
	if err := templ.Execute(&output, outVars); err != nil {
		return nil, fmt.Errorf("Failed to generate the template: %v", err)
	}
	return output.Bytes(), nil
}

// runSingleWatch is used to query a single watch path for changes
func runSingleWatch(conf *Config, data *backendData, idx int, watch *WatchPath) {
	health := data.Client.Health()
	opts := &consulapi.QueryOptions{
		WaitTime: waitTime,
	}
	if watch.Datacenter != "" {
		opts.Datacenter = watch.Datacenter
	}

	failures := 0
	for {
		if shouldStop(data.StopCh) {
			return
		}
		entries, qm, err := health.Service(watch.Service, watch.Tag, true, opts)
		if err != nil {
			log.Printf("[ERR] Failed to fetch service nodes: %v", err)
		}

		// Patch the entries as necessary
		for _, entry := range entries {
			// Modify the node name to prefix with the watch ID. This
			// prevents a name conflict on duplicate names
			entry.Node.Node = fmt.Sprintf("%d_%s", idx, entry.Node.Node)

			// Patch the port if provided
			if watch.Port != 0 {
				entry.Service.Port = watch.Port
			}

			// Clear the health output to prevent reloading due to changes
			// in output text since we don't care.
			for _, c := range entry.Checks {
				c.Notes = ""
				c.Output = ""
			}
		}

		// Update the entries. If this is the first read, do it on error
		data.Lock()
		old, ok := data.Servers[watch]
		if !ok || (err == nil && !reflect.DeepEqual(old, entries)) {
			data.Servers[watch] = entries
			asyncNotify(data.ChangeCh)
			if !conf.DryRun {
				log.Printf("[DEBUG] Updated nodes for %v", watch.Spec)
			}
		}
		data.Unlock()

		// Stop immediately on a dry run
		if conf.DryRun {
			return
		}

		// Check for an error
		if err != nil {
			failures = min(failures+1, maxFailures)
			time.Sleep(backoff(failSleep, failures))
		} else {
			failures = 0
			opts.WaitIndex = qm.LastIndex
		}
	}
}

// reload is used to invoke the reload command
func reload(conf *Config) error {
	// Determine the shell invocation based on OS
	var shell, flag string
	if runtime.GOOS == "windows" {
		shell = "cmd"
		flag = "/C"
	} else {
		shell = "/bin/sh"
		flag = "-c"
	}

	// Create and invoke the command
	cmd := exec.Command(shell, flag, conf.ReloadCommand)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

// shouldStop checks for a closed control channel
func shouldStop(ch chan struct{}) bool {
	select {
	case <-ch:
		return true
	default:
		return false
	}
}

// asyncNotify is used to notify a channel
func asyncNotify(ch chan struct{}) {
	select {
	case ch <- struct{}{}:
	default:
	}
}

// min returns the min of two ints
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

// backoff is used to compute an exponential backoff
func backoff(interval time.Duration, times int) time.Duration {
	times--
	return interval * time.Duration(math.Pow(2, float64(times)))
}

// ServerEntry is the full data structure exposed to
// the template for each server
type ServerEntry struct {
	ID      string
	Service string
	Tags    []string
	Port    int
	IP      net.IP
	Node    string
}

// String is the default text representation of a server
func (se *ServerEntry) String() string {
	name := fmt.Sprintf("%s_%s", se.Node, se.ID)
	addr := &net.TCPAddr{IP: se.IP, Port: se.Port}
	return fmt.Sprintf("server %s %s", name, addr)
}

// formatOutput converts the service entries into a format
// suitable for templating into the HAProxy file
func formatOutput(inp map[string][]*consulapi.ServiceEntry) map[string][]*ServerEntry {
	out := make(map[string][]*ServerEntry)
	for backend, entries := range inp {
		servers := make([]*ServerEntry, len(entries))
		for idx, entry := range entries {
			servers[idx] = &ServerEntry{
				ID:      entry.Service.ID,
				Service: entry.Service.Service,
				Tags:    entry.Service.Tags,
				Port:    entry.Service.Port,
				IP:      net.ParseIP(entry.Node.Address),
				Node:    entry.Node.Node,
			}
		}
		out[backend] = servers
	}
	return out
}
<|endoftext|>


<commit_before>package whogo

import (
	"bufio"
	"bytes"
	"github.com/ziutek/telnet"
	"io/ioutil"
	"time"
)

// Feed this function with data from Whois() function and it will tell whether the domain is available.
func Available(data []byte) bool {
	available := [34]string{"No Data Found", "NOT FOUND", "Domain Status: Available", "not registred,", "No match", "This query returned 0 objects", "Domain Not Found", "nothing found", "No records matching", "Status: AVAILABLE", "does not exist in database", "Status: Not Registered", "No match for", "Object does not exist", "We do not have an entry in our database matching your query", "no existe", "no matching record", "No domain records were found to match", "No entries found", "Status: free", "No entries found for the selected sourc", "not found...", "The domain has not been registered", "Not Registered", "No data was found", "This domain is available for registration", "Nothing found for this query", "No such domain", "No Objects Found", "Object_Not_Found", "No information available", "Domain is not registered", "domain name not known", "not found in database"}
	for _, v := range available {
		if v == string(data) {
			return true
		}
	}
	return false
}

func solve(domain string, timeout time.Duration) (string, error) {
	conn, err := dial("whois.iana.org", timeout)
	if err != nil {
		return "", err
	}
	err = send(conn, domain, timeout)
	if err != nil {
		return "", err
	}
	scanner := bufio.NewScanner(conn)
	for scanner.Scan() {
		if bytes.Contains(scanner.Bytes(), []byte("refer")) {
			refer := bytes.Split(scanner.Bytes(), []byte(":"))[1]
			return string(bytes.TrimSpace(refer)), nil
		}
	}
	if err := scanner.Err(); err != nil {
		return "", err
	}
	return "", nil
}

func dial(host string, timeout time.Duration) (*telnet.Conn, error) {
	conn, err := telnet.DialTimeout("tcp", host+":43", timeout)
	if err != nil {
		return conn, err
	}
	conn.SetUnixWriteMode(true)
	return conn, nil
}

func send(conn *telnet.Conn, s string, timeout time.Duration) error {
	conn.SetWriteDeadline(time.Now().Add(timeout))
	buf := make([]byte, len(s)+1)
	copy(buf, s)
	buf[len(s)] = '\n'
	_, err := conn.Write(buf)
	if err != nil {
		return err
	}
	return nil
}

// Takes in domain as a string without the any prefix. Example: google.com
// Timeout defines timeout which is set at every write and read request.
// Returns byte array of the WHOIS query.
func Whois(domain string, timeout time.Duration) ([]byte, error) {
	refer, err := solve(domain, timeout)
	if err != nil {
		return nil, err
	}
	conn, err := dial(refer, timeout)
	if err != nil {
		return nil, err
	}
	err = send(conn, domain, timeout)
	if err != nil {
		return nil, err
	}
	whois, err := ioutil.ReadAll(conn)
	if err != nil {
		return nil, err
	}
	//TODO: automatically search for the exact domain when whois returns
	//more than one record.
	// if bytes.Contains(whois, []byte("To single out one record")) {
	// 	fmt.Println("Contains more than one record.")
	// 	err = Send(conn, domain, timeout)
	// 	if err != nil {
	// 		return nil, err
	// 	}
	// 	whois, err = ioutil.ReadAll(conn)
	// 	if err != nil {
	// 		return nil, err
	// 	}
	// }
	conn.Close()
	return bytes.TrimSpace(whois), nil
}

//TODO: parse whois records and return Go struct
// func Records(status []byte) {
// 	lines := bytes.Split(status, []byte("\n"))
// 	for _, line := range lines {
// 		fmt.Println(string(line))
// 	}
// }
<commit_msg>add Records function<commit_after>package whogo

import (
	"bufio"
	"bytes"
	"github.com/9uuso/go-jaro-winkler-distance"
	"github.com/ziutek/telnet"
	"io/ioutil"
	"strings"
	"time"
)

type record struct {
	Nameservers []string
	Status      []string
	Created     string
	Updated     string
	Expiration  string
	Referral    string
}

// Available tells whether a domain is available according to it's WHOIS query.
// The parameter should be the result from Whois function.
func Available(data []byte) bool {
	available := [34]string{"No Data Found", "NOT FOUND", "Domain Status: Available", "not registred,", "No match", "This query returned 0 objects", "Domain Not Found", "nothing found", "No records matching", "Status: AVAILABLE", "does not exist in database", "Status: Not Registered", "No match for", "Object does not exist", "We do not have an entry in our database matching your query", "no existe", "no matching record", "No domain records were found to match", "No entries found", "Status: free", "No entries found for the selected sourc", "not found...", "The domain has not been registered", "Not Registered", "No data was found", "This domain is available for registration", "Nothing found for this query", "No such domain", "No Objects Found", "Object_Not_Found", "No information available", "Domain is not registered", "domain name not known", "not found in database"}
	for _, v := range available {
		if v == string(data) {
			return true
		}
	}
	return false
}

func solve(domain string, timeout time.Duration) (string, error) {
	conn, err := dial("whois.iana.org", timeout)
	if err != nil {
		return "", err
	}
	err = send(conn, domain, timeout)
	if err != nil {
		return "", err
	}
	scanner := bufio.NewScanner(conn)
	for scanner.Scan() {
		if bytes.Contains(scanner.Bytes(), []byte("refer")) {
			refer := bytes.Split(scanner.Bytes(), []byte(":"))[1]
			return string(bytes.TrimSpace(refer)), nil
		}
	}
	if err := scanner.Err(); err != nil {
		return "", err
	}
	return "", nil
}

func dial(host string, timeout time.Duration) (*telnet.Conn, error) {
	conn, err := telnet.DialTimeout("tcp", host+":43", timeout)
	if err != nil {
		return conn, err
	}
	conn.SetUnixWriteMode(true)
	return conn, nil
}

func send(conn *telnet.Conn, s string, timeout time.Duration) error {
	conn.SetWriteDeadline(time.Now().Add(timeout))
	buf := make([]byte, len(s)+1)
	copy(buf, s)
	buf[len(s)] = '\n'
	_, err := conn.Write(buf)
	if err != nil {
		return err
	}
	return nil
}

// Whois returns WHOIS query of a domain in format such as google.com
// Timeout defines timeout which is set at every write and read request.
func Whois(domain string, timeout time.Duration) ([]byte, error) {
	refer, err := solve(domain, timeout)
	if err != nil {
		return nil, err
	}
	conn, err := dial(refer, timeout)
	if err != nil {
		return nil, err
	}
	err = send(conn, domain, timeout)
	if err != nil {
		return nil, err
	}
	whois, err := ioutil.ReadAll(conn)
	if err != nil {
		return nil, err
	}
	//TODO: automatically search for the exact domain when whois returns
	//more than one record.
	// if bytes.Contains(whois, []byte("To single out one record")) {
	// 	fmt.Println("Contains more than one record.")
	// 	err = Send(conn, domain, timeout)
	// 	if err != nil {
	// 		return nil, err
	// 	}
	// 	whois, err = ioutil.ReadAll(conn)
	// 	if err != nil {
	// 		return nil, err
	// 	}
	// }
	conn.Close()
	return bytes.TrimSpace(whois), nil
}

func find(query map[string]string, s ...string) string {

	var max = float64(0)
	var res string

	if len(s) == 1 {
		for whoisIndex, whoisValue := range query {
			score := jwd.Calculate(whoisIndex, s[0])
			if score > max && score > 0.7 {
				max = score
				res = whoisValue
			}
		}
	}

	return res
}

// Records uses Jaro-Winkler distance to parse WHOIS queries.
// Other than .com domains may not be supported.
func Records(data []byte) record {
	lines := bytes.Split(data, []byte("\n"))
	query := make(map[string]string)
	var record record
	for _, line := range lines {
		if jwd.Calculate(strings.Split(string(line), ":")[0], "Referral") > 0.7 && bytes.Contains(line, []byte(":")) {
			record.Referral = strings.TrimSpace(strings.Split(string(line), ": ")[1])
		}
		if len(line) > 0 && bytes.Contains(line, []byte(":")) && len(bytes.TrimSpace(bytes.Split(line, []byte(":"))[1])) > 0 {
			this := string(line)
			if len(query[strings.TrimSpace(strings.Split(this, ":")[0])]) != 0 {
				n := query[strings.TrimSpace(strings.Split(this, ":")[0])]
				query[strings.TrimSpace(strings.Split(this, ":")[0])] = n + "," + strings.TrimSpace(strings.Split(this, ":")[1])
			} else {
				query[strings.TrimSpace(strings.Split(this, ":")[0])] = strings.TrimSpace(strings.Split(this, ":")[1])
			}
		}
	}
	record.Updated = find(query, "Updated")
	record.Created = find(query, "Created")
	record.Nameservers = strings.Split(find(query, "Nameservers"), ",")
	record.Status = strings.Split(find(query, "Status"), ",")
	record.Expiration = find(query, "Expiration")
	return record
}
<|endoftext|>


<commit_before>package narcissus

import (
	"fmt"
	"reflect"
	"strings"
)

// Write writes a structure pointer to the Augeas tree
func (n *Narcissus) Write(val interface{}) error {
	ref, err := structRef(val)
	if err != nil {
		return fmt.Errorf("invalid interface: %v", err)
	}

	err = n.Autoload(ref)
	if err != nil {
		return fmt.Errorf("failed to autoload file: %v", err)
	}

	path, err := getPath(ref)
	if err != nil {
		return fmt.Errorf("undefined path: %v", err)
	}

	err = n.writeStruct(ref, path)
	if err != nil {
		return fmt.Errorf("failed to write interface to tree: %v", err)
	}

	err = n.Augeas.Save()
	if err != nil {
		return fmt.Errorf("failed to save Augeas tree: %v", err)
	}

	return nil
}

func (n *Narcissus) writeStruct(ref reflect.Value, path string) error {
	refType := ref.Type()
	for i := 0; i < refType.NumField(); i++ {
		if refType.Field(i).Name == "augeasPath" {
			// Ignore the special `augeasPath` field
			continue
		}
		err := n.writeField(ref.Field(i), refType.Field(i), path)
		if err != nil {
			return fmt.Errorf("failed to write field %s to path %s: %v", refType.Field(i).Name, path, err)
		}
	}

	return nil
}

func (n *Narcissus) writeField(field reflect.Value, fieldType reflect.StructField, path string) error {
	fieldPath := fmt.Sprintf("%s/%s", path, fieldType.Tag.Get("path"))
	if field.Kind() == reflect.Slice {
		return n.writeSliceField(field, fieldType, path, fieldPath)
	} else if field.Kind() == reflect.Map {
		return n.writeMapField(field, fieldType, path, fieldPath)
	}
	return n.writeSimpleField(field, fieldPath, fieldType.Tag)
}

func (n *Narcissus) writeSimpleField(field reflect.Value, fieldPath string, tag reflect.StructTag) error {
	aug := n.Augeas
	// There might be a better way to convert, but that does it
	value := fmt.Sprintf("%v", field.Interface())

	if tag.Get("value-from") == "label" {
		return nil
	}

	// FIXME: use omitempty for that
	if value == "" {
		return nil
	}

	err := aug.Set(fieldPath, value)
	return err
}

func (n *Narcissus) writeSliceField(field reflect.Value, fieldType reflect.StructField, path, fieldPath string) error {
	for i := 0; i < field.Len(); i++ {
		value := field.Index(i)
		var p string
		if fieldType.Tag.Get("type") == "seq" {
			p = fmt.Sprintf("%s/%v", path, i+1)
		} else {
			p = fmt.Sprintf("%s[%v]", fieldPath, i+1)
		}
		// Create base node
		err := n.Augeas.Clear(p)
		if err != nil {
			return fmt.Errorf("failed to create base path for slice: %v", err)
		}
		if value.Kind() == reflect.Struct {
			err := n.writeStruct(value, p)
			if err != nil {
				return fmt.Errorf("failed to write slice struct value: %v", err)
			}
		} else {
			err := n.writeSimpleField(value, p, fieldType.Tag)
			if err != nil {
				return fmt.Errorf("failed to write slice value: %v", err)
			}
		}
	}

	return nil
}

func (n *Narcissus) writeMapField(field reflect.Value, fieldType reflect.StructField, path, fieldPath string) (err error) {
	keys := field.MapKeys()
	var purgeConditions []string
	for _, k := range keys {
		value := field.MapIndex(k)
		var p string
		if strings.HasSuffix(fieldPath, "/*") {
			// TrimSuffix? ouch!
			p = fmt.Sprintf("%s/%s", strings.TrimSuffix(fieldPath, "/*"), k)
			purgeConditions = append(purgeConditions, fmt.Sprintf("label() != '%s'", k))
		} else {
			p = fmt.Sprintf("%s[.='%s']", fieldPath, k)
			purgeConditions = append(purgeConditions, fmt.Sprintf(". != '%s'", k))
		}
		if value.Kind() == reflect.Struct {
			if fieldType.Tag.Get("key") != "label" {
				err = n.writeSimpleField(k, p, fieldType.Tag)
				if err != nil {
					return fmt.Errorf("failed to write map key: %v", err)
				}
			}
			err = n.writeStruct(value, p)
			if err != nil {
				return fmt.Errorf("failed to write map struct value: %v", err)
			}
		} else {
			err := n.writeSimpleField(value, p, fieldType.Tag)
			if err != nil {
				return fmt.Errorf("failed to write map value: %v", err)
			}
		}
	}

	// Purge absent keys
	purge := fieldType.Tag.Get("purge")
	if purge == "true" {
		purgePath := fieldPath + "[" + strings.Join(purgeConditions, " and ") + "]"
		n.Augeas.Remove(purgePath)
	}

	return nil
}
<commit_msg>Ignore all augeas* fields in write<commit_after>package narcissus

import (
	"fmt"
	"reflect"
	"strings"
)

// Write writes a structure pointer to the Augeas tree
func (n *Narcissus) Write(val interface{}) error {
	ref, err := structRef(val)
	if err != nil {
		return fmt.Errorf("invalid interface: %v", err)
	}

	err = n.Autoload(ref)
	if err != nil {
		return fmt.Errorf("failed to autoload file: %v", err)
	}

	path, err := getPath(ref)
	if err != nil {
		return fmt.Errorf("undefined path: %v", err)
	}

	err = n.writeStruct(ref, path)
	if err != nil {
		return fmt.Errorf("failed to write interface to tree: %v", err)
	}

	err = n.Augeas.Save()
	if err != nil {
		return fmt.Errorf("failed to save Augeas tree: %v", err)
	}

	return nil
}

func (n *Narcissus) writeStruct(ref reflect.Value, path string) error {
	refType := ref.Type()
	for i := 0; i < refType.NumField(); i++ {
		if strings.HasPrefix(refType.Field(i).Name, "augeas") {
			// Ignore the special `augeas*` fields
			continue
		}
		err := n.writeField(ref.Field(i), refType.Field(i), path)
		if err != nil {
			return fmt.Errorf("failed to write field %s to path %s: %v", refType.Field(i).Name, path, err)
		}
	}

	return nil
}

func (n *Narcissus) writeField(field reflect.Value, fieldType reflect.StructField, path string) error {
	fieldPath := fmt.Sprintf("%s/%s", path, fieldType.Tag.Get("path"))
	if field.Kind() == reflect.Slice {
		return n.writeSliceField(field, fieldType, path, fieldPath)
	} else if field.Kind() == reflect.Map {
		return n.writeMapField(field, fieldType, path, fieldPath)
	}
	return n.writeSimpleField(field, fieldPath, fieldType.Tag)
}

func (n *Narcissus) writeSimpleField(field reflect.Value, fieldPath string, tag reflect.StructTag) error {
	aug := n.Augeas
	// There might be a better way to convert, but that does it
	value := fmt.Sprintf("%v", field.Interface())

	if tag.Get("value-from") == "label" {
		return nil
	}

	// FIXME: use omitempty for that
	if value == "" {
		return nil
	}

	err := aug.Set(fieldPath, value)
	return err
}

func (n *Narcissus) writeSliceField(field reflect.Value, fieldType reflect.StructField, path, fieldPath string) error {
	for i := 0; i < field.Len(); i++ {
		value := field.Index(i)
		var p string
		if fieldType.Tag.Get("type") == "seq" {
			p = fmt.Sprintf("%s/%v", path, i+1)
		} else {
			p = fmt.Sprintf("%s[%v]", fieldPath, i+1)
		}
		// Create base node
		err := n.Augeas.Clear(p)
		if err != nil {
			return fmt.Errorf("failed to create base path for slice: %v", err)
		}
		if value.Kind() == reflect.Struct {
			err := n.writeStruct(value, p)
			if err != nil {
				return fmt.Errorf("failed to write slice struct value: %v", err)
			}
		} else {
			err := n.writeSimpleField(value, p, fieldType.Tag)
			if err != nil {
				return fmt.Errorf("failed to write slice value: %v", err)
			}
		}
	}

	return nil
}

func (n *Narcissus) writeMapField(field reflect.Value, fieldType reflect.StructField, path, fieldPath string) (err error) {
	keys := field.MapKeys()
	var purgeConditions []string
	for _, k := range keys {
		value := field.MapIndex(k)
		var p string
		if strings.HasSuffix(fieldPath, "/*") {
			// TrimSuffix? ouch!
			p = fmt.Sprintf("%s/%s", strings.TrimSuffix(fieldPath, "/*"), k)
			purgeConditions = append(purgeConditions, fmt.Sprintf("label() != '%s'", k))
		} else {
			p = fmt.Sprintf("%s[.='%s']", fieldPath, k)
			purgeConditions = append(purgeConditions, fmt.Sprintf(". != '%s'", k))
		}
		if value.Kind() == reflect.Struct {
			if fieldType.Tag.Get("key") != "label" {
				err = n.writeSimpleField(k, p, fieldType.Tag)
				if err != nil {
					return fmt.Errorf("failed to write map key: %v", err)
				}
			}
			err = n.writeStruct(value, p)
			if err != nil {
				return fmt.Errorf("failed to write map struct value: %v", err)
			}
		} else {
			err := n.writeSimpleField(value, p, fieldType.Tag)
			if err != nil {
				return fmt.Errorf("failed to write map value: %v", err)
			}
		}
	}

	// Purge absent keys
	purge := fieldType.Tag.Get("purge")
	if purge == "true" {
		purgePath := fieldPath + "[" + strings.Join(purgeConditions, " and ") + "]"
		n.Augeas.Remove(purgePath)
	}

	return nil
}
<|endoftext|>


<commit_before>// This is Free Software covered by the terms of the MIT license.
// See LICENSE file for details.
// Copyright 2017 by Intevation GmbH
package main

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
)

type xhtml struct {
	out bytes.Buffer
}

func (x *xhtml) tag(name string) func(*node) error {
	tag := "<" + name + "/>"
	return func(*node) error {
		x.out.WriteString(tag)
		return nil
	}
}

func (x *xhtml) open(name string) func(*node) error {
	tag := "<" + name + ">"
	return func(*node) error {
		x.out.WriteString(tag)
		return nil
	}
}

func (x *xhtml) close(name string) func(*node) error {
	tag := "</" + name + ">"
	return func(*node) error {
		x.out.WriteString(tag)
		return nil
	}
}

func (x *xhtml) element(name string) *visitor {
	return &visitor{x.open(name), x.close(name)}
}

func (x *xhtml) heading(level int) *visitor {
	tag := fmt.Sprintf("h%d", level)
	return &visitor{x.open(tag), x.close(tag)}
}

func (x *xhtml) text(n *node) error {
	enc := xml.NewEncoder(&x.out)
	txt := n.value.(string)
	enc.EncodeToken(xml.CharData(txt))
	enc.Flush()
	return nil
}

func (x *xhtml) noWikiInline(n *node) error {
	x.out.WriteString("<tt>")
	x.text(n)
	x.out.WriteString("</tt>")
	return nil
}

func (x *xhtml) noWiki(n *node) error {
	x.out.WriteString("
")
	x.noWikiInline(n)
	x.out.WriteString("
\n")
	return nil
}

func (x *xhtml) link(n *node) error {
	href := n.value.(string)
	enc := xml.NewEncoder(&x.out)
	enc.EncodeToken(xml.StartElement{
		Name: xml.Name{Local: "a"},
		Attr: []xml.Attr{{xml.Name{Local: "href"}, href}},
	})
	enc.Flush()
	return nil
}

func (x *xhtml) image(n *node) error {
	img := n.value.(*image)
	enc := xml.NewEncoder(&x.out)
	attr := []xml.Attr{{xml.Name{Local: "src"}, img.src}}
	if img.alt != "" {
		attr = append(attr, xml.Attr{xml.Name{Local: "src"}, img.alt})
	}
	enc.EncodeToken(xml.StartElement{
		Name: xml.Name{Local: "img"},
		Attr: attr,
	})
	enc.Flush()
	return nil
}

func exportXHTML(doc *document, out io.Writer) error {

	var x xhtml

	x.out.WriteString(xml.Header)
	x.out.WriteString("\n\n")

	err := doc.traverse(map[nodeType]*visitor{
		orderedListNode:     x.element("ol"),
		unorderedListNode:   x.element("ul"),
		listItemNode:        x.element("li"),
		textNode:            &visitor{enter: x.text},
		boldNode:            x.element("strong"),
		italicsNode:         x.element("i"),
		underlinedNode:      x.element("em"),
		strikeNode:          x.element("del"),
		superscriptNode:     x.element("sup"),
		subscriptNode:       x.element("sub"),
		tableNode:           x.element("table"),
		tableRowNode:        x.element("tr"),
		tableCellNode:       x.element("td"),
		tableHeaderRowNode:  x.element("th"),
		tableHeaderCellNode: x.element("td"),
		heading1Node:        x.heading(1),
		heading2Node:        x.heading(2),
		heading3Node:        x.heading(3),
		heading4Node:        x.heading(4),
		heading5Node:        x.heading(5),
		heading6Node:        x.heading(6),
		paragraphNode:       x.element("p"),
		lineBreakNode:       &visitor{enter: x.tag("br")},
		escapeNode:          &visitor{enter: x.text},
		noWikiNode:          &visitor{enter: x.noWiki},
		noWikiInlineNode:    &visitor{enter: x.noWikiInline},
		// placeholderNode not supported, yet.
		imageNode:          &visitor{enter: x.image, leave: x.close("img")},
		linkNode:           &visitor{enter: x.link, leave: x.close("a")},
		horizontalLineNode: &visitor{enter: x.tag("hr")},
	})
	if err != nil {
		x.out.WriteString("\n\n\n")
		_, err = x.out.WriteTo(out)
	}
	return err
}
<commit_msg>Use bufio.Writer instead of bytes.Buffer to write XHTML to. Should reduce alloc and copying around for larger documents. Also be more keen about errors while writing.<commit_after>// This is Free Software covered by the terms of the MIT license.
// See LICENSE file for details.
// Copyright 2017 by Intevation GmbH
package main

import (
	"bufio"
	"encoding/xml"
	"fmt"
	"io"
)

type xhtml struct {
	out *bufio.Writer
}

func (x *xhtml) writeString(s string) error {
	_, err := x.out.WriteString(s)
	return err
}

func (x *xhtml) tag(name string) func(*node) error {
	tag := "<" + name + "/>"
	return func(*node) error { return x.writeString(tag) }
}

func (x *xhtml) open(name string) func(*node) error {
	tag := "<" + name + ">"
	return func(*node) error { return x.writeString(tag) }
}

func (x *xhtml) close(name string) func(*node) error {
	tag := "</" + name + ">"
	return func(*node) error { return x.writeString(tag) }
}

func (x *xhtml) element(name string) *visitor {
	return &visitor{x.open(name), x.close(name)}
}

func (x *xhtml) heading(level int) *visitor {
	tag := fmt.Sprintf("h%d", level)
	return &visitor{x.open(tag), x.close(tag)}
}

func (x *xhtml) text(n *node) error {
	enc := xml.NewEncoder(x.out)
	txt := n.value.(string)
	if err := enc.EncodeToken(xml.CharData(txt)); err != nil {
		return err
	}
	return enc.Flush()
}

func (x *xhtml) noWikiInline(n *node) error {
	x.writeString("<tt>")
	x.text(n)
	return x.writeString("</tt>")
}

func (x *xhtml) noWiki(n *node) error {
	x.writeString("
")
	x.noWikiInline(n)
	return x.writeString("
\n")
}

func (x *xhtml) link(n *node) error {
	href := n.value.(string)
	enc := xml.NewEncoder(x.out)
	if err := enc.EncodeToken(xml.StartElement{
		Name: xml.Name{Local: "a"},
		Attr: []xml.Attr{{xml.Name{Local: "href"}, href}},
	}); err != nil {
		return err
	}
	return enc.Flush()
}

func (x *xhtml) image(n *node) error {
	img := n.value.(*image)
	enc := xml.NewEncoder(x.out)
	attr := []xml.Attr{{xml.Name{Local: "src"}, img.src}}
	if img.alt != "" {
		attr = append(attr, xml.Attr{xml.Name{Local: "src"}, img.alt})
	}
	if err := enc.EncodeToken(xml.StartElement{
		Name: xml.Name{Local: "img"},
		Attr: attr,
	}); err != nil {
		return err
	}
	return enc.Flush()
}

func exportXHTML(doc *document, out io.Writer) error {

	x := xhtml{out: bufio.NewWriter(out)}

	x.out.WriteString(xml.Header)
	x.out.WriteString("\n\n")

	err := doc.traverse(map[nodeType]*visitor{
		orderedListNode:     x.element("ol"),
		unorderedListNode:   x.element("ul"),
		listItemNode:        x.element("li"),
		textNode:            &visitor{enter: x.text},
		boldNode:            x.element("strong"),
		italicsNode:         x.element("i"),
		underlinedNode:      x.element("em"),
		strikeNode:          x.element("del"),
		superscriptNode:     x.element("sup"),
		subscriptNode:       x.element("sub"),
		tableNode:           x.element("table"),
		tableRowNode:        x.element("tr"),
		tableCellNode:       x.element("td"),
		tableHeaderRowNode:  x.element("th"),
		tableHeaderCellNode: x.element("td"),
		heading1Node:        x.heading(1),
		heading2Node:        x.heading(2),
		heading3Node:        x.heading(3),
		heading4Node:        x.heading(4),
		heading5Node:        x.heading(5),
		heading6Node:        x.heading(6),
		paragraphNode:       x.element("p"),
		lineBreakNode:       &visitor{enter: x.tag("br")},
		escapeNode:          &visitor{enter: x.text},
		noWikiNode:          &visitor{enter: x.noWiki},
		noWikiInlineNode:    &visitor{enter: x.noWikiInline},
		// placeholderNode not supported, yet.
		imageNode:          &visitor{enter: x.image, leave: x.close("img")},
		linkNode:           &visitor{enter: x.link, leave: x.close("a")},
		horizontalLineNode: &visitor{enter: x.tag("hr")},
	})
	if err != nil {
		return err
	}
	x.writeString("\n\n\n")
	return x.out.Flush()
}
<|endoftext|>


<commit_before>package HologramGo

import (
	"fmt"
	"os"
	"strconv"
)

// Device object returned in the response.
type Device map[string]interface{}

// Devices is just a list of Device(s).
type Devices []interface{}

// GetDevices returns device details.
func GetDevices() Devices {

	req := createGetRequest("/devices")

	resp, err := sendRequest(req)
	if err != nil {
		fmt.Printf("Could not send request: %v\n", err)
		os.Exit(1)
	}

	return unmarshallIntoArrayObject(resp)
}

// GetDevice returns a device detail based on the given deviceid.
func GetDevice(deviceid int) Device {

	req := createGetRequest("/devices/" + strconv.Itoa(deviceid))

	resp, err := sendRequest(req)

	if err != nil {
		fmt.Printf("Could not send request: %v\n", err)
		os.Exit(1)
	}

	return unmarshallIntoObject(resp)
}

// ClaimOwnershipAndActivateDevice claims ownership and activate the given device.
func ClaimOwnershipAndActivateDevice(simnumber int) Device {

	var params Parameters
	req := createPostRequest("/cellular/sim_"+strconv.Itoa(simnumber)+"/claim", params)

	resp, err := sendRequest(req)
	if err != nil {
		fmt.Printf("Could not send request: %v\n", err)
		os.Exit(1)
	}

	return unmarshallIntoObject(resp)
}

// PurchaseAndAssignPhoneNumberToDevice purchases and assigns a phone number to the device.
func PurchaseAndAssignPhoneNumberToDevice(deviceid int) Device {

	var params Parameters
	req := createPostRequest("/devices/"+strconv.Itoa(deviceid)+"/addnumber", params)

	resp, err := sendRequest(req)
	if err != nil {
		fmt.Printf("Could not send request: %v\n", err)
		os.Exit(1)
	}

	return unmarshallIntoObject(resp)
}

///////////////////////////////////////////////////
// GENERIC DEVICE GETTER FUNCTIONS
///////////////////////////////////////////////////

// GetDeviceId returns the id.
func (device Device) GetDeviceId() float64 {
	return device["id"].(float64)
}

// GetDeviceUserId returns the user id.
func (device Device) GetDeviceUserId() float64 {
	return device["userid"].(float64)
}

// GetDeviceName returns the device name.
func (device Device) GetDeviceName() string {
	return device["name"].(string)
}

// GetDeviceType returns the device type.
func (device Device) GetDeviceType() string {
	return device["type"].(string)
}

// GetWhenCreated returns a UNIX timestamp of the creation time.
func (device Device) GetWhenCreated() string {
	return device["whencreated"].(string)
}

// GetPhoneNumber returns a phone number.
func (device Device) GetPhoneNumber() string {
	return device["phonenumber"].(string)
}

// GetTunnelable returns true if it is tunnelable.
func (device Device) GetTunnelable() bool {
	return device["tunnelable"].(bool)
}

// TODO: links/cellular
<commit_msg>fix lint issues<commit_after>package HologramGo

import (
	"fmt"
	"os"
	"strconv"
)

// Device object returned in the response.
type Device map[string]interface{}

// Devices is just a list of Device(s).
type Devices []interface{}

// GetDevices returns device details.
func GetDevices() Devices {

	req := createGetRequest("/devices")

	resp, err := sendRequest(req)
	if err != nil {
		fmt.Printf("Could not send request: %v\n", err)
		os.Exit(1)
	}

	return unmarshallIntoArrayObject(resp)
}

// GetDevice returns a device detail based on the given deviceid.
func GetDevice(deviceid int) Device {

	req := createGetRequest("/devices/" + strconv.Itoa(deviceid))

	resp, err := sendRequest(req)

	if err != nil {
		fmt.Printf("Could not send request: %v\n", err)
		os.Exit(1)
	}

	return unmarshallIntoObject(resp)
}

// ClaimOwnershipAndActivateDevice claims ownership and activate the given device.
func ClaimOwnershipAndActivateDevice(simnumber int) Device {

	var params Parameters
	req := createPostRequest("/cellular/sim_"+strconv.Itoa(simnumber)+"/claim", params)

	resp, err := sendRequest(req)
	if err != nil {
		fmt.Printf("Could not send request: %v\n", err)
		os.Exit(1)
	}

	return unmarshallIntoObject(resp)
}

// PurchaseAndAssignPhoneNumberToDevice purchases and assigns a phone number to the device.
func PurchaseAndAssignPhoneNumberToDevice(deviceid int) Device {

	var params Parameters
	req := createPostRequest("/devices/"+strconv.Itoa(deviceid)+"/addnumber", params)

	resp, err := sendRequest(req)
	if err != nil {
		fmt.Printf("Could not send request: %v\n", err)
		os.Exit(1)
	}

	return unmarshallIntoObject(resp)
}

///////////////////////////////////////////////////
// GENERIC DEVICE GETTER FUNCTIONS
///////////////////////////////////////////////////

// GetDeviceID returns the id.
func (device Device) GetDeviceID() float64 {
	return device["id"].(float64)
}

// GetDeviceUserID returns the user id.
func (device Device) GetDeviceUserID() float64 {
	return device["userid"].(float64)
}

// GetDeviceName returns the device name.
func (device Device) GetDeviceName() string {
	return device["name"].(string)
}

// GetDeviceType returns the device type.
func (device Device) GetDeviceType() string {
	return device["type"].(string)
}

// GetWhenCreated returns a UNIX timestamp of the creation time.
func (device Device) GetWhenCreated() string {
	return device["whencreated"].(string)
}

// GetPhoneNumber returns a phone number.
func (device Device) GetPhoneNumber() string {
	return device["phonenumber"].(string)
}

// GetTunnelable returns true if it is tunnelable.
func (device Device) GetTunnelable() bool {
	return device["tunnelable"].(bool)
}

// TODO: links/cellular
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/ninjasphere/go-ninja/events"
	"github.com/ninjasphere/go-ninja/model"
	"github.com/ninjasphere/go-ninja/support"
	"github.com/ninjasphere/go-zigbee"
	"github.com/ninjasphere/go-zigbee/nwkmgr"
)

const (
	ClusterIDBasic    uint32 = 0x00
	ClusterIDOnOff    uint32 = 0x06
	ClusterIDLevel    uint32 = 0x08 // We're always exporting as brightness for now
	ClusterIDColor    uint32 = 0x300
	ClusterIDTemp     uint32 = 0x402
	ClusterIDHumidity uint32 = 0x405
	ClusterIDPower    uint32 = 0x702
)

type ZStackConfig struct {
	Hostname       string
	OtasrvrPort    int
	GatewayPort    int
	NwkmgrPort     int
	StableFlagFile string
}

type Driver struct {
	support.DriverSupport

	devices map[uint64]*Device

	config *ZStackConfig

	nwkmgrConn  *zigbee.ZStackNwkMgr
	gatewayConn *zigbee.ZStackGateway
	otaConn     *zigbee.ZStackOta
}

func NewDriver(config *ZStackConfig) (*Driver, error) {
	driver := &Driver{
		config:  config,
		devices: make(map[uint64]*Device),
	}

	err := driver.Init(info)
	if err != nil {
		log.Fatalf("Failed to initialize fake driver: %s", err)
	}

	err = driver.Export(driver)
	if err != nil {
		log.Fatalf("Failed to export fake driver: %s", err)
	}

	userAgent := driver.Conn.GetServiceClient("$device/:deviceId/channel/user-agent")
	userAgent.OnEvent("pairing-requested", func(pairingRequest *events.PairingRequest, values map[string]string) bool {
		log.Infof("Pairing request received from %s for %d seconds", values["deviceId"], pairingRequest.Duration)

		duration := uint32(pairingRequest.Duration)

		if err := driver.EnableJoin(duration); err != nil {
			log.Warningf("Failed to enable joining: %s", err)
		}

		return true
	})

	return driver, nil

}

func (d *Driver) Reset(hard bool) error {
	return d.nwkmgrConn.Reset(hard)
}

func (d *Driver) Start() error {

	waitUntilZStackReady(d.config.StableFlagFile)

	var err error

	d.nwkmgrConn, err = zigbee.ConnectToNwkMgrServer(d.config.Hostname, d.config.NwkmgrPort)
	if err != nil {
		return fmt.Errorf("Error connecting to nwkmgr %s", err)
	}
	d.nwkmgrConn.OnDeviceFound = func(deviceInfo *nwkmgr.NwkDeviceInfoT) {
		d.onDeviceFound(deviceInfo)
	}

	/*done := false
	d.nwkmgrConn.OnNetworkReady = func() {
		if !done {
			done = true
		}
	}*/

	d.otaConn, err = zigbee.ConnectToOtaServer(d.config.Hostname, d.config.OtasrvrPort)
	if err != nil {
		return fmt.Errorf("Error connecting to ota server %s", err)
	}

	d.gatewayConn, err = zigbee.ConnectToGatewayServer(d.config.Hostname, d.config.GatewayPort)
	if err != nil {
		return fmt.Errorf("Error connecting to gateway %s", err)
	}

	/*keyResponse := &nwkmgr.NwkZigbeeGenericCnf{}

	keyRequest := &nwkmgr.NwkChangeNwkKeyReq{
		NewKey: []byte{0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39},
	}

	err = d.nwkmgrConn.SendCommand(keyRequest, keyResponse)
	if err != nil {
		return fmt.Errorf("Failed setting network key: %s", err)
	}

	spew.Dump(keyResponse)
	log.Println("Sleeping for 10 seconds after setting network key")

	time.Sleep(10 * time.Second)*/

	networkInfo := &nwkmgr.NwkZigbeeNwkInfoCnf{}

	err = d.nwkmgrConn.SendCommand(&nwkmgr.NwkZigbeeNwkInfoReq{}, networkInfo)
	if err != nil {
		if log.IsDebugEnabled() {
			spew.Dump(networkInfo)
		}
		return fmt.Errorf("Failed getting network info: %s", err)
	}

	if *networkInfo.Status == nwkmgr.NwkNetworkStatusT_NWK_DOWN {
		return fmt.Errorf("The ZigBee network is down")
	}

	localDevice := &nwkmgr.NwkGetLocalDeviceInfoCnf{}

	err = d.nwkmgrConn.SendCommand(&nwkmgr.NwkGetLocalDeviceInfoReq{}, localDevice)
	if err != nil {
		return fmt.Errorf("Failed getting local device info: %s", err)
	}

	if log.IsDebugEnabled() {
		spew.Dump("device info", localDevice.String())
	}

	networkKey := &nwkmgr.NwkGetNwkKeyCnf{}

	err = d.nwkmgrConn.SendCommand(&nwkmgr.NwkGetNwkKeyReq{}, networkKey)
	if err != nil {
		log.Fatalf("Failed getting network key: %s", err)
	}

	if log.IsDebugEnabled() {
		spew.Dump(networkKey)
	}

	log.Debugf("Started coordinator. Channel:%d Pan ID:0x%X Key:% X", *networkInfo.NwkChannel, *networkInfo.PanId, networkKey.NewKey)

	return d.FetchDevices()
}

func (d *Driver) EnableJoin(duration uint32) error {

	permitJoinRequest := &nwkmgr.NwkSetPermitJoinReq{
		PermitJoinTime: &duration,
		PermitJoin:     nwkmgr.NwkPermitJoinTypeT_PERMIT_ALL.Enum(),
	}

	permitJoinResponse := &nwkmgr.NwkZigbeeGenericCnf{}

	err := d.nwkmgrConn.SendCommand(permitJoinRequest, permitJoinResponse)
	if err != nil {
		return fmt.Errorf("Failed to enable joining: %s", err)
	}
	if permitJoinResponse.Status.String() != "STATUS_SUCCESS" {
		return fmt.Errorf("Failed to enable joining: %s", permitJoinResponse.Status)
	}

	d.SendEvent("pairing-started", &events.PairingStarted{
		Duration: int(duration),
	})

	go func() {
		time.Sleep(time.Second * time.Duration(duration))
		d.SendEvent("pairing-ended", &events.PairingStarted{
			Duration: int(duration),
		})
	}()
	return nil
}

func (d *Driver) FetchDevices() error {
	return d.nwkmgrConn.FetchDeviceList()
}

func (d *Driver) onDeviceFound(deviceInfo *nwkmgr.NwkDeviceInfoT) {

	log.Debugf("\n\n")
	log.Debugf("---- Found Device IEEE:%X ----\f", *deviceInfo.IeeeAddress)
	log.Debugf("Device Info: %v", *deviceInfo)

	if d.devices[*deviceInfo.IeeeAddress] != nil {
		// We've seen this already, but it may have been repaired. We *should* just be able to replace
		// the deviceInfo object, which is used for all communication.
		// TODO: Actually verify this. May need to re-run channel init.
		d.devices[*deviceInfo.IeeeAddress].deviceInfo = deviceInfo
	}

	device := &Device{
		driver:     d,
		deviceInfo: deviceInfo,
		info: &model.Device{
			NaturalID:     fmt.Sprintf("%X", *deviceInfo.IeeeAddress),
			NaturalIDType: "zigbee",
			Signatures:    &map[string]string{},
		},
	}

	for _, endpoint := range deviceInfo.SimpleDescList {
		if *endpoint.ProfileId == 0xC05E /*ZLL*/ {

			switch *endpoint.DeviceId {

			case 0x0000: // On/Off Light
				fallthrough
			case 0x0100: // Dimmable Light
				fallthrough
			case 0x0200: // Color Light
				fallthrough
			case 0x210: // Ext Color Light
				(*device.info.Signatures)["ninja:thingType"] = "light"
			}
		}
	}

	err := device.getBasicInfo()
	if err != nil {
		log.Debugf("Failed to get basic info: %s", err)
	}

	if device.ManufacturerName != "" {
		(*device.info.Signatures)["zigbee:ManufacturerName"] = device.ManufacturerName
	} else {
		device.ManufacturerName = "Unknown"
	}
	if device.ModelIdentifier != "" {
		(*device.info.Signatures)["zigbee:ModelIdentifier"] = device.ModelIdentifier
	} else {
		device.ModelIdentifier = fmt.Sprintf("MAC:%X", *deviceInfo.IeeeAddress)
	}

	name := fmt.Sprintf("%s by %s", device.ModelIdentifier, device.ManufacturerName)

	device.info.Name = &name

	if log.IsDebugEnabled() {
		spew.Dump(deviceInfo)
	}

	err = d.Conn.ExportDevice(device)
	if err != nil {
		log.Fatalf("Failed to export zigbee device %s: %s", name, err)
	}

	log.Debugf("Got device : %d", *deviceInfo.IeeeAddress)

	for _, endpoint := range deviceInfo.SimpleDescList {
		log.Debugf("Got endpoint : %d", *endpoint.EndpointId)

		if containsUInt32(endpoint.InputClusters, ClusterIDOnOff) {
			log.Debugf("This endpoint has on/off cluster")

			onOff := &OnOffChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDOnOff),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := onOff.init()
			if err != nil {
				log.Debugf("Failed initialising on/off channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDPower) {
			log.Debugf("This endpoint has power cluster")

			power := &PowerChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDPower),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := power.init()
			if err != nil {
				log.Debugf("Failed initialising power channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDTemp) {
			log.Debugf("This endpoint has temperature cluster")

			temp := &TempChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDTemp),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := temp.init()
			if err != nil {
				log.Debugf("Failed initialising temp channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDHumidity) {
			log.Debugf("This endpoint has humidity cluster")

			humidity := &HumidityChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDHumidity),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := humidity.init()
			if err != nil {
				log.Debugf("Failed initialising humidity channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDLevel) {
			log.Debugf("This endpoint has level cluster. Exporting as brightness channel")

			if log.IsDebugEnabled() {
				spew.Dump("brightness cluster", endpoint, ClusterIDLevel)
			}

			brightness := &BrightnessChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDLevel),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := brightness.init()
			if err != nil {
				log.Debugf("Failed initialising brightness channel: %s", err)
			}

		}

	}

	d.devices[*deviceInfo.IeeeAddress] = device

	fmt.Printf("---- Finished Device IEEE:%X ----\n", *deviceInfo.IeeeAddress)

}

func getCurDir() string {
	pwd, _ := os.Getwd()
	return pwd + "/"
}

func containsUInt32(hackstack []uint32, needle uint32) bool {
	for _, cluster := range hackstack {
		if cluster == needle {
			return true
		}
	}
	return false
}

func waitUntilZStackReady(checkFile string) {
	if checkFile == "" {
		return
	}

	// cooperate with zigbeeHAgw so that we don't start the zigbee driver
	// until we look somewhat stable.
	log.Debugf("waiting until zigbeeHAgw writes %s", checkFile)
	for {
		if _, err := os.Stat(checkFile); err == nil {
			break
		} else {
			time.Sleep(time.Second)
		}
	}
	log.Debugf("%s detected. start up continues...", checkFile)
}
<commit_msg>Restore info level logging of join windows. Use correct type on pairing-ended event.<commit_after>package main

import (
	"fmt"
	"os"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/ninjasphere/go-ninja/events"
	"github.com/ninjasphere/go-ninja/model"
	"github.com/ninjasphere/go-ninja/support"
	"github.com/ninjasphere/go-zigbee"
	"github.com/ninjasphere/go-zigbee/nwkmgr"
)

const (
	ClusterIDBasic    uint32 = 0x00
	ClusterIDOnOff    uint32 = 0x06
	ClusterIDLevel    uint32 = 0x08 // We're always exporting as brightness for now
	ClusterIDColor    uint32 = 0x300
	ClusterIDTemp     uint32 = 0x402
	ClusterIDHumidity uint32 = 0x405
	ClusterIDPower    uint32 = 0x702
)

type ZStackConfig struct {
	Hostname       string
	OtasrvrPort    int
	GatewayPort    int
	NwkmgrPort     int
	StableFlagFile string
}

type Driver struct {
	support.DriverSupport

	devices map[uint64]*Device

	config *ZStackConfig

	nwkmgrConn  *zigbee.ZStackNwkMgr
	gatewayConn *zigbee.ZStackGateway
	otaConn     *zigbee.ZStackOta

	devicesFound int
}

func NewDriver(config *ZStackConfig) (*Driver, error) {
	driver := &Driver{
		config:  config,
		devices: make(map[uint64]*Device),
	}

	err := driver.Init(info)
	if err != nil {
		log.Fatalf("Failed to initialize fake driver: %s", err)
	}

	err = driver.Export(driver)
	if err != nil {
		log.Fatalf("Failed to export fake driver: %s", err)
	}

	userAgent := driver.Conn.GetServiceClient("$device/:deviceId/channel/user-agent")
	userAgent.OnEvent("pairing-requested", func(pairingRequest *events.PairingRequest, values map[string]string) bool {
		log.Infof("Pairing request received from %s for %d seconds", values["deviceId"], pairingRequest.Duration)

		duration := uint32(pairingRequest.Duration)

		if err := driver.EnableJoin(duration); err != nil {
			log.Warningf("Failed to enable joining: %s", err)
		}

		return true
	})

	return driver, nil

}

func (d *Driver) Reset(hard bool) error {
	return d.nwkmgrConn.Reset(hard)
}

func (d *Driver) Start() error {

	waitUntilZStackReady(d.config.StableFlagFile)

	var err error

	d.nwkmgrConn, err = zigbee.ConnectToNwkMgrServer(d.config.Hostname, d.config.NwkmgrPort)
	if err != nil {
		return fmt.Errorf("Error connecting to nwkmgr %s", err)
	}
	d.nwkmgrConn.OnDeviceFound = func(deviceInfo *nwkmgr.NwkDeviceInfoT) {
		d.onDeviceFound(deviceInfo)
	}

	/*done := false
	d.nwkmgrConn.OnNetworkReady = func() {
		if !done {
			done = true
		}
	}*/

	d.otaConn, err = zigbee.ConnectToOtaServer(d.config.Hostname, d.config.OtasrvrPort)
	if err != nil {
		return fmt.Errorf("Error connecting to ota server %s", err)
	}

	d.gatewayConn, err = zigbee.ConnectToGatewayServer(d.config.Hostname, d.config.GatewayPort)
	if err != nil {
		return fmt.Errorf("Error connecting to gateway %s", err)
	}

	/*keyResponse := &nwkmgr.NwkZigbeeGenericCnf{}

	keyRequest := &nwkmgr.NwkChangeNwkKeyReq{
		NewKey: []byte{0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39},
	}

	err = d.nwkmgrConn.SendCommand(keyRequest, keyResponse)
	if err != nil {
		return fmt.Errorf("Failed setting network key: %s", err)
	}

	spew.Dump(keyResponse)
	log.Println("Sleeping for 10 seconds after setting network key")

	time.Sleep(10 * time.Second)*/

	networkInfo := &nwkmgr.NwkZigbeeNwkInfoCnf{}

	err = d.nwkmgrConn.SendCommand(&nwkmgr.NwkZigbeeNwkInfoReq{}, networkInfo)
	if err != nil {
		if log.IsDebugEnabled() {
			spew.Dump(networkInfo)
		}
		return fmt.Errorf("Failed getting network info: %s", err)
	}

	if *networkInfo.Status == nwkmgr.NwkNetworkStatusT_NWK_DOWN {
		return fmt.Errorf("The ZigBee network is down")
	}

	localDevice := &nwkmgr.NwkGetLocalDeviceInfoCnf{}

	err = d.nwkmgrConn.SendCommand(&nwkmgr.NwkGetLocalDeviceInfoReq{}, localDevice)
	if err != nil {
		return fmt.Errorf("Failed getting local device info: %s", err)
	}

	if log.IsDebugEnabled() {
		spew.Dump("device info", localDevice.String())
	}

	networkKey := &nwkmgr.NwkGetNwkKeyCnf{}

	err = d.nwkmgrConn.SendCommand(&nwkmgr.NwkGetNwkKeyReq{}, networkKey)
	if err != nil {
		log.Fatalf("Failed getting network key: %s", err)
	}

	if log.IsDebugEnabled() {
		spew.Dump(networkKey)
	}

	log.Debugf("Started coordinator. Channel:%d Pan ID:0x%X Key:% X", *networkInfo.NwkChannel, *networkInfo.PanId, networkKey.NewKey)

	return d.FetchDevices()
}

func (d *Driver) EnableJoin(duration uint32) error {

	go func() {
	   	save := d.devicesFound;
		time.Sleep(time.Second * time.Duration(duration))
		d.Log.Infof("Join window closes after %d seconds.", duration);
		d.SendEvent("pairing-ended", &events.PairingEnded{
			DevicesFound: int(d.devicesFound - save),
		})
	}()

	permitJoinRequest := &nwkmgr.NwkSetPermitJoinReq{
		PermitJoinTime: &duration,
		PermitJoin:     nwkmgr.NwkPermitJoinTypeT_PERMIT_ALL.Enum(),
	}

	permitJoinResponse := &nwkmgr.NwkZigbeeGenericCnf{}

	err := d.nwkmgrConn.SendCommand(permitJoinRequest, permitJoinResponse)
	if err != nil {
		return fmt.Errorf("Failed to enable joining: %s", err)
	}
	if permitJoinResponse.Status.String() != "STATUS_SUCCESS" {
		return fmt.Errorf("Failed to enable joining: %s", permitJoinResponse.Status)
	}

	d.SendEvent("pairing-started", &events.PairingStarted{
		Duration: int(duration),
	})

	d.Log.Infof("Join window opens for %d seconds", duration);

	return nil
}

func (d *Driver) FetchDevices() error {
	return d.nwkmgrConn.FetchDeviceList()
}

func (d *Driver) onDeviceFound(deviceInfo *nwkmgr.NwkDeviceInfoT) {

	log.Debugf("\n\n")
	log.Debugf("---- Found Device IEEE:%X ----\f", *deviceInfo.IeeeAddress)
	log.Debugf("Device Info: %v", *deviceInfo)

	if d.devices[*deviceInfo.IeeeAddress] != nil {
		// We've seen this already, but it may have been repaired. We *should* just be able to replace
		// the deviceInfo object, which is used for all communication.
		// TODO: Actually verify this. May need to re-run channel init.
		d.devices[*deviceInfo.IeeeAddress].deviceInfo = deviceInfo
	}

	device := &Device{
		driver:     d,
		deviceInfo: deviceInfo,
		info: &model.Device{
			NaturalID:     fmt.Sprintf("%X", *deviceInfo.IeeeAddress),
			NaturalIDType: "zigbee",
			Signatures:    &map[string]string{},
		},
	}

	for _, endpoint := range deviceInfo.SimpleDescList {
		if *endpoint.ProfileId == 0xC05E /*ZLL*/ {

			switch *endpoint.DeviceId {

			case 0x0000: // On/Off Light
				fallthrough
			case 0x0100: // Dimmable Light
				fallthrough
			case 0x0200: // Color Light
				fallthrough
			case 0x210: // Ext Color Light
				(*device.info.Signatures)["ninja:thingType"] = "light"
			}
		}
	}

	err := device.getBasicInfo()
	if err != nil {
		log.Debugf("Failed to get basic info: %s", err)
	}

	if device.ManufacturerName != "" {
		(*device.info.Signatures)["zigbee:ManufacturerName"] = device.ManufacturerName
	} else {
		device.ManufacturerName = "Unknown"
	}
	if device.ModelIdentifier != "" {
		(*device.info.Signatures)["zigbee:ModelIdentifier"] = device.ModelIdentifier
	} else {
		device.ModelIdentifier = fmt.Sprintf("MAC:%X", *deviceInfo.IeeeAddress)
	}

	name := fmt.Sprintf("%s by %s", device.ModelIdentifier, device.ManufacturerName)

	device.info.Name = &name

	if log.IsDebugEnabled() {
		spew.Dump(deviceInfo)
	}

	err = d.Conn.ExportDevice(device)
	if err != nil {
		log.Fatalf("Failed to export zigbee device %s: %s", name, err)
	}
	d.devicesFound++;

	log.Debugf("Got device : %d", *deviceInfo.IeeeAddress)

	for _, endpoint := range deviceInfo.SimpleDescList {
		log.Debugf("Got endpoint : %d", *endpoint.EndpointId)

		if containsUInt32(endpoint.InputClusters, ClusterIDOnOff) {
			log.Debugf("This endpoint has on/off cluster")

			onOff := &OnOffChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDOnOff),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := onOff.init()
			if err != nil {
				log.Debugf("Failed initialising on/off channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDPower) {
			log.Debugf("This endpoint has power cluster")

			power := &PowerChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDPower),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := power.init()
			if err != nil {
				log.Debugf("Failed initialising power channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDTemp) {
			log.Debugf("This endpoint has temperature cluster")

			temp := &TempChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDTemp),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := temp.init()
			if err != nil {
				log.Debugf("Failed initialising temp channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDHumidity) {
			log.Debugf("This endpoint has humidity cluster")

			humidity := &HumidityChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDHumidity),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := humidity.init()
			if err != nil {
				log.Debugf("Failed initialising humidity channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDLevel) {
			log.Debugf("This endpoint has level cluster. Exporting as brightness channel")

			if log.IsDebugEnabled() {
				spew.Dump("brightness cluster", endpoint, ClusterIDLevel)
			}

			brightness := &BrightnessChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDLevel),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := brightness.init()
			if err != nil {
				log.Debugf("Failed initialising brightness channel: %s", err)
			}

		}

	}

	d.devices[*deviceInfo.IeeeAddress] = device

	fmt.Printf("---- Finished Device IEEE:%X ----\n", *deviceInfo.IeeeAddress)

}

func getCurDir() string {
	pwd, _ := os.Getwd()
	return pwd + "/"
}

func containsUInt32(hackstack []uint32, needle uint32) bool {
	for _, cluster := range hackstack {
		if cluster == needle {
			return true
		}
	}
	return false
}

func waitUntilZStackReady(checkFile string) {
	if checkFile == "" {
		return
	}

	// cooperate with zigbeeHAgw so that we don't start the zigbee driver
	// until we look somewhat stable.
	log.Debugf("waiting until zigbeeHAgw writes %s", checkFile)
	for {
		if _, err := os.Stat(checkFile); err == nil {
			break
		} else {
			time.Sleep(time.Second)
		}
	}
	log.Debugf("%s detected. start up continues...", checkFile)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/ninjasphere/go-ninja/events"
	"github.com/ninjasphere/go-ninja/model"
	"github.com/ninjasphere/go-ninja/support"
	"github.com/ninjasphere/go-zigbee"
	"github.com/ninjasphere/go-zigbee/nwkmgr"
)

const (
	ClusterIDBasic    uint32 = 0x00
	ClusterIDOnOff    uint32 = 0x06
	ClusterIDLevel    uint32 = 0x08 // We're always exporting as brightness for now
	ClusterIDColor    uint32 = 0x300
	ClusterIDTemp     uint32 = 0x402
	ClusterIDHumidity uint32 = 0x405
	ClusterIDPower    uint32 = 0x702
)

type ZStackConfig struct {
	Hostname       string
	OtasrvrPort    int
	GatewayPort    int
	NwkmgrPort     int
	StableFlagFile string
}

type Driver struct {
	support.DriverSupport

	devices map[uint64]*Device

	config *ZStackConfig

	nwkmgrConn  *zigbee.ZStackNwkMgr
	gatewayConn *zigbee.ZStackGateway
	otaConn     *zigbee.ZStackOta

	devicesFound int
}

func NewDriver(config *ZStackConfig) (*Driver, error) {
	driver := &Driver{
		config:  config,
		devices: make(map[uint64]*Device),
	}

	err := driver.Init(info)
	if err != nil {
		log.Fatalf("Failed to initialize fake driver: %s", err)
	}

	err = driver.Export(driver)
	if err != nil {
		log.Fatalf("Failed to export fake driver: %s", err)
	}

	userAgent := driver.Conn.GetServiceClient("$device/:deviceId/channel/user-agent")
	userAgent.OnEvent("pairing-requested", func(pairingRequest *events.PairingRequest, values map[string]string) bool {

		duration := uint32(pairingRequest.Duration)

		if duration > 254 {
			duration = 254
		}

		log.Infof("Pairing request received from %s for %d seconds", values["deviceId"], duration)

		if err := driver.EnableJoin(duration); err != nil {
			log.Warningf("Failed to enable joining: %s", err)
		}

		return true
	})

	return driver, nil

}

func (d *Driver) Reset(hard bool) error {
	return d.nwkmgrConn.Reset(hard)
}

func (d *Driver) Start() error {

	waitUntilZStackReady(d.config.StableFlagFile)

	var err error

	d.nwkmgrConn, err = zigbee.ConnectToNwkMgrServer(d.config.Hostname, d.config.NwkmgrPort)
	if err != nil {
		return fmt.Errorf("Error connecting to nwkmgr %s", err)
	}
	d.nwkmgrConn.OnDeviceFound = func(deviceInfo *nwkmgr.NwkDeviceInfoT) {
		d.onDeviceFound(deviceInfo)
	}

	/*done := false
	d.nwkmgrConn.OnNetworkReady = func() {
		if !done {
			done = true
		}
	}*/

	d.otaConn, err = zigbee.ConnectToOtaServer(d.config.Hostname, d.config.OtasrvrPort)
	if err != nil {
		return fmt.Errorf("Error connecting to ota server %s", err)
	}

	d.gatewayConn, err = zigbee.ConnectToGatewayServer(d.config.Hostname, d.config.GatewayPort)
	if err != nil {
		return fmt.Errorf("Error connecting to gateway %s", err)
	}

	/*keyResponse := &nwkmgr.NwkZigbeeGenericCnf{}

	keyRequest := &nwkmgr.NwkChangeNwkKeyReq{
		NewKey: []byte{0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39},
	}

	err = d.nwkmgrConn.SendCommand(keyRequest, keyResponse)
	if err != nil {
		return fmt.Errorf("Failed setting network key: %s", err)
	}

	spew.Dump(keyResponse)
	log.Println("Sleeping for 10 seconds after setting network key")

	time.Sleep(10 * time.Second)*/

	networkInfo := &nwkmgr.NwkZigbeeNwkInfoCnf{}

	err = d.nwkmgrConn.SendCommand(&nwkmgr.NwkZigbeeNwkInfoReq{}, networkInfo)
	if err != nil {
		if log.IsDebugEnabled() {
			spew.Dump(networkInfo)
		}
		return fmt.Errorf("Failed getting network info: %s", err)
	}

	if *networkInfo.Status == nwkmgr.NwkNetworkStatusT_NWK_DOWN {
		return fmt.Errorf("The ZigBee network is down")
	}

	localDevice := &nwkmgr.NwkGetLocalDeviceInfoCnf{}

	err = d.nwkmgrConn.SendCommand(&nwkmgr.NwkGetLocalDeviceInfoReq{}, localDevice)
	if err != nil {
		return fmt.Errorf("Failed getting local device info: %s", err)
	}

	if log.IsDebugEnabled() {
		spew.Dump("device info", localDevice.String())
	}

	/*networkKey := &nwkmgr.NwkGetNwkKeyCnf{}

	err = d.nwkmgrConn.SendCommand(&nwkmgr.NwkGetNwkKeyReq{}, networkKey)
	if err != nil {
		log.Fatalf("Failed getting network key: %s", err)
	}

	if log.IsDebugEnabled() {
		spew.Dump(networkKey)
	}*/

	log.Debugf("Started coordinator. Channel:%d Pan ID:0x%X Key:% X", *networkInfo.NwkChannel, *networkInfo.PanId)

	return d.FetchDevices()

}

func (d *Driver) StartPairing(period uint32) (*uint32, error) {
	if period > 254 {
		period = 254
	}
	err := d.EnableJoin(period)
	return &period, err
}

func (d *Driver) EndPairing() error {
	err := d.EnableJoin(0)
	return err
}

func (d *Driver) EnableJoin(duration uint32) error {

	permitJoinRequest := &nwkmgr.NwkSetPermitJoinReq{
		PermitJoinTime: &duration,
		PermitJoin:     nwkmgr.NwkPermitJoinTypeT_PERMIT_ALL.Enum(),
	}

	permitJoinResponse := &nwkmgr.NwkZigbeeGenericCnf{}

	err := d.nwkmgrConn.SendCommand(permitJoinRequest, permitJoinResponse)
	if err != nil {
		return fmt.Errorf("Failed to enable joining: %s", err)
	}
	if permitJoinResponse.Status.String() != "STATUS_SUCCESS" {
		return fmt.Errorf("Failed to enable joining: %s", permitJoinResponse.Status)
	}

	d.SendEvent("pairing-started", &events.PairingStarted{
		Duration: int(duration),
	})

	go func() {
		save := d.devicesFound
		time.Sleep(time.Second * time.Duration(duration))
		d.Log.Infof("Join window closes after %d seconds.", duration)
		d.SendEvent("pairing-ended", &events.PairingEnded{
			DevicesFound: int(d.devicesFound - save),
		})
	}()

	d.Log.Infof("Join window opens for %d seconds", duration)

	return nil
}

func (d *Driver) FetchDevices() error {
	return d.nwkmgrConn.FetchDeviceList()
}

func (d *Driver) onDeviceFound(deviceInfo *nwkmgr.NwkDeviceInfoT) {

	log.Debugf("\n\n")
	log.Debugf("---- Found Device IEEE:%X ----\f", *deviceInfo.IeeeAddress)
	log.Debugf("Device Info: %v", *deviceInfo)

	if d.devices[*deviceInfo.IeeeAddress] != nil {
		// We've seen this already, but it may have been repaired. We *should* just be able to replace
		// the deviceInfo object, which is used for all communication.
		// TODO: Actually verify this. May need to re-run channel init.
		d.devices[*deviceInfo.IeeeAddress].deviceInfo = deviceInfo
	}

	device := &Device{
		driver:     d,
		deviceInfo: deviceInfo,
		info: &model.Device{
			NaturalID:     fmt.Sprintf("%X", *deviceInfo.IeeeAddress),
			NaturalIDType: "zigbee",
			Signatures:    &map[string]string{},
		},
	}

	for _, endpoint := range deviceInfo.SimpleDescList {
		if *endpoint.ProfileId == 0xC05E /*ZLL*/ {

			switch *endpoint.DeviceId {

			case 0x0000: // On/Off Light
				fallthrough
			case 0x0100: // Dimmable Light
				fallthrough
			case 0x0200: // Color Light
				fallthrough
			case 0x210: // Ext Color Light
				(*device.info.Signatures)["ninja:thingType"] = "light"
			}
		}
	}

	err := device.getBasicInfo()
	if err != nil {
		log.Debugf("Failed to get basic info: %s", err)
	}

	if device.ManufacturerName != "" {
		(*device.info.Signatures)["zigbee:ManufacturerName"] = device.ManufacturerName
	} else {
		device.ManufacturerName = "Unknown"
	}
	if device.ModelIdentifier != "" {
		(*device.info.Signatures)["zigbee:ModelIdentifier"] = device.ModelIdentifier
	} else {
		device.ModelIdentifier = fmt.Sprintf("MAC:%X", *deviceInfo.IeeeAddress)
	}

	name := fmt.Sprintf("%s by %s", device.ModelIdentifier, device.ManufacturerName)

	device.info.Name = &name

	if log.IsDebugEnabled() {
		spew.Dump(deviceInfo)
	}

	err = d.Conn.ExportDevice(device)
	if err != nil {
		log.Fatalf("Failed to export zigbee device %s: %s", name, err)
	}
	d.devicesFound++

	log.Debugf("Got device : %d", *deviceInfo.IeeeAddress)

	for _, endpoint := range deviceInfo.SimpleDescList {
		log.Debugf("Got endpoint : %d", *endpoint.EndpointId)

		if containsUInt32(endpoint.InputClusters, ClusterIDOnOff) {
			log.Debugf("This endpoint has on/off cluster")

			onOff := &OnOffChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDOnOff),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := onOff.init()
			if err != nil {
				log.Debugf("Failed initialising on/off channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDPower) {
			log.Debugf("This endpoint has power cluster")

			power := &PowerChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDPower),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := power.init()
			if err != nil {
				log.Debugf("Failed initialising power channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDTemp) {
			log.Debugf("This endpoint has temperature cluster")

			temp := &TempChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDTemp),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := temp.init()
			if err != nil {
				log.Debugf("Failed initialising temp channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDHumidity) {
			log.Debugf("This endpoint has humidity cluster")

			humidity := &HumidityChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDHumidity),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := humidity.init()
			if err != nil {
				log.Debugf("Failed initialising humidity channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDLevel) {
			log.Debugf("This endpoint has level cluster. Exporting as brightness channel")

			if log.IsDebugEnabled() {
				spew.Dump("brightness cluster", endpoint, ClusterIDLevel)
			}

			brightness := &BrightnessChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDLevel),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := brightness.init()
			if err != nil {
				log.Debugf("Failed initialising brightness channel: %s", err)
			}

		}

	}

	d.devices[*deviceInfo.IeeeAddress] = device

	fmt.Printf("---- Finished Device IEEE:%X ----\n", *deviceInfo.IeeeAddress)

}

func getCurDir() string {
	pwd, _ := os.Getwd()
	return pwd + "/"
}

func containsUInt32(hackstack []uint32, needle uint32) bool {
	for _, cluster := range hackstack {
		if cluster == needle {
			return true
		}
	}
	return false
}

func waitUntilZStackReady(checkFile string) {
	if checkFile == "" {
		return
	}

	// cooperate with zigbeeHAgw so that we don't start the zigbee driver
	// until we look somewhat stable.
	log.Debugf("waiting until zigbeeHAgw writes %s", checkFile)
	for {
		if _, err := os.Stat(checkFile); err == nil {
			break
		} else {
			time.Sleep(time.Second)
		}
	}
	log.Debugf("%s detected. start up continues...", checkFile)
}
<commit_msg>Don't export devices if we can't talk to them. Wait 30s and try again. And if we don't have any nice name to say, don't say anything at all.<commit_after>package main

import (
	"fmt"
	"os"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/ninjasphere/go-ninja/events"
	"github.com/ninjasphere/go-ninja/model"
	"github.com/ninjasphere/go-ninja/support"
	"github.com/ninjasphere/go-zigbee"
	"github.com/ninjasphere/go-zigbee/nwkmgr"
)

const (
	ClusterIDBasic    uint32 = 0x00
	ClusterIDOnOff    uint32 = 0x06
	ClusterIDLevel    uint32 = 0x08 // We're always exporting as brightness for now
	ClusterIDColor    uint32 = 0x300
	ClusterIDTemp     uint32 = 0x402
	ClusterIDHumidity uint32 = 0x405
	ClusterIDPower    uint32 = 0x702
)

type ZStackConfig struct {
	Hostname       string
	OtasrvrPort    int
	GatewayPort    int
	NwkmgrPort     int
	StableFlagFile string
}

type Driver struct {
	support.DriverSupport

	devices map[uint64]*Device

	config *ZStackConfig

	nwkmgrConn  *zigbee.ZStackNwkMgr
	gatewayConn *zigbee.ZStackGateway
	otaConn     *zigbee.ZStackOta

	devicesFound int
}

func NewDriver(config *ZStackConfig) (*Driver, error) {
	driver := &Driver{
		config:  config,
		devices: make(map[uint64]*Device),
	}

	err := driver.Init(info)
	if err != nil {
		log.Fatalf("Failed to initialize fake driver: %s", err)
	}

	err = driver.Export(driver)
	if err != nil {
		log.Fatalf("Failed to export fake driver: %s", err)
	}

	userAgent := driver.Conn.GetServiceClient("$device/:deviceId/channel/user-agent")
	userAgent.OnEvent("pairing-requested", func(pairingRequest *events.PairingRequest, values map[string]string) bool {

		duration := uint32(pairingRequest.Duration)

		if duration > 254 {
			duration = 254
		}

		log.Infof("Pairing request received from %s for %d seconds", values["deviceId"], duration)

		if err := driver.EnableJoin(duration); err != nil {
			log.Warningf("Failed to enable joining: %s", err)
		}

		return true
	})

	return driver, nil

}

func (d *Driver) Reset(hard bool) error {
	return d.nwkmgrConn.Reset(hard)
}

func (d *Driver) Start() error {

	waitUntilZStackReady(d.config.StableFlagFile)

	var err error

	d.nwkmgrConn, err = zigbee.ConnectToNwkMgrServer(d.config.Hostname, d.config.NwkmgrPort)
	if err != nil {
		return fmt.Errorf("Error connecting to nwkmgr %s", err)
	}
	d.nwkmgrConn.OnDeviceFound = func(deviceInfo *nwkmgr.NwkDeviceInfoT) {
		d.onDeviceFound(deviceInfo)
	}

	/*done := false
	d.nwkmgrConn.OnNetworkReady = func() {
		if !done {
			done = true
		}
	}*/

	d.otaConn, err = zigbee.ConnectToOtaServer(d.config.Hostname, d.config.OtasrvrPort)
	if err != nil {
		return fmt.Errorf("Error connecting to ota server %s", err)
	}

	d.gatewayConn, err = zigbee.ConnectToGatewayServer(d.config.Hostname, d.config.GatewayPort)
	if err != nil {
		return fmt.Errorf("Error connecting to gateway %s", err)
	}

	/*keyResponse := &nwkmgr.NwkZigbeeGenericCnf{}

	keyRequest := &nwkmgr.NwkChangeNwkKeyReq{
		NewKey: []byte{0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39},
	}

	err = d.nwkmgrConn.SendCommand(keyRequest, keyResponse)
	if err != nil {
		return fmt.Errorf("Failed setting network key: %s", err)
	}

	spew.Dump(keyResponse)
	log.Println("Sleeping for 10 seconds after setting network key")

	time.Sleep(10 * time.Second)*/

	networkInfo := &nwkmgr.NwkZigbeeNwkInfoCnf{}

	err = d.nwkmgrConn.SendCommand(&nwkmgr.NwkZigbeeNwkInfoReq{}, networkInfo)
	if err != nil {
		if log.IsDebugEnabled() {
			spew.Dump(networkInfo)
		}
		return fmt.Errorf("Failed getting network info: %s", err)
	}

	if *networkInfo.Status == nwkmgr.NwkNetworkStatusT_NWK_DOWN {
		return fmt.Errorf("The ZigBee network is down")
	}

	localDevice := &nwkmgr.NwkGetLocalDeviceInfoCnf{}

	err = d.nwkmgrConn.SendCommand(&nwkmgr.NwkGetLocalDeviceInfoReq{}, localDevice)
	if err != nil {
		return fmt.Errorf("Failed getting local device info: %s", err)
	}

	if log.IsDebugEnabled() {
		spew.Dump("device info", localDevice.String())
	}

	/*networkKey := &nwkmgr.NwkGetNwkKeyCnf{}

	err = d.nwkmgrConn.SendCommand(&nwkmgr.NwkGetNwkKeyReq{}, networkKey)
	if err != nil {
		log.Fatalf("Failed getting network key: %s", err)
	}

	if log.IsDebugEnabled() {
		spew.Dump(networkKey)
	}*/

	log.Debugf("Started coordinator. Channel:%d Pan ID:0x%X", *networkInfo.NwkChannel, *networkInfo.PanId)

	d.StartFetchingDevices()

	return nil
}

func (d *Driver) StartPairing(period uint32) (*uint32, error) {
	if period > 254 {
		period = 254
	}
	err := d.EnableJoin(period)
	return &period, err
}

func (d *Driver) EndPairing() error {
	err := d.EnableJoin(0)
	return err
}

func (d *Driver) EnableJoin(duration uint32) error {

	permitJoinRequest := &nwkmgr.NwkSetPermitJoinReq{
		PermitJoinTime: &duration,
		PermitJoin:     nwkmgr.NwkPermitJoinTypeT_PERMIT_ALL.Enum(),
	}

	permitJoinResponse := &nwkmgr.NwkZigbeeGenericCnf{}

	err := d.nwkmgrConn.SendCommand(permitJoinRequest, permitJoinResponse)
	if err != nil {
		return fmt.Errorf("Failed to enable joining: %s", err)
	}
	if permitJoinResponse.Status.String() != "STATUS_SUCCESS" {
		return fmt.Errorf("Failed to enable joining: %s", permitJoinResponse.Status)
	}

	d.SendEvent("pairing-started", &events.PairingStarted{
		Duration: int(duration),
	})

	go func() {
		save := d.devicesFound
		time.Sleep(time.Second * time.Duration(duration))
		d.Log.Infof("Join window closes after %d seconds.", duration)
		d.SendEvent("pairing-ended", &events.PairingEnded{
			DevicesFound: int(d.devicesFound - save),
		})
	}()

	d.Log.Infof("Join window opens for %d seconds", duration)
	return nil
}

func (d *Driver) StartFetchingDevices() {
	go func() {
		for {
			d.nwkmgrConn.FetchDeviceList()
			time.Sleep(time.Second * 30)
		}
	}()
}

func (d *Driver) onDeviceFound(deviceInfo *nwkmgr.NwkDeviceInfoT) {

	// XXX: This device status is often wrong. Useless.
	/*if *deviceInfo.DeviceStatus != nwkmgr.NwkDeviceStatusT_DEVICE_ON_LINE {
		log.Debugf("---- Found Offline Device IEEE:%X ----\f", *deviceInfo.IeeeAddress)

		// TODO: Inform the device (if we've seen it online) to stop polling

		return
	}*/

	if d.devices[*deviceInfo.IeeeAddress] != nil {
		// We've seen this already, but it may have been re-paired. We *should* just be able to replace
		// the deviceInfo object, which is used for all communication.
		// TODO: Actually verify this. May need to re-run channel init.
		d.devices[*deviceInfo.IeeeAddress].deviceInfo = deviceInfo
		return
	}

	device := &Device{
		driver:     d,
		deviceInfo: deviceInfo,
		info: &model.Device{
			NaturalID:     fmt.Sprintf("%X", *deviceInfo.IeeeAddress),
			NaturalIDType: "zigbee",
			Signatures:    &map[string]string{},
		},
	}

	for _, endpoint := range deviceInfo.SimpleDescList {
		if *endpoint.ProfileId == 0xC05E /*ZLL*/ {

			switch *endpoint.DeviceId {

			case 0x0000: // On/Off Light
				fallthrough
			case 0x0100: // Dimmable Light
				fallthrough
			case 0x0200: // Color Light
				fallthrough
			case 0x210: // Ext Color Light
				(*device.info.Signatures)["ninja:thingType"] = "light"
			}
		}
	}

	err := device.getBasicInfo()
	if err != nil {
		log.Debugf("Failed to get basic info for: %X : %s", *deviceInfo.IeeeAddress, err)
		return
	}

	name := ""

	if device.ModelIdentifier != "" {
		(*device.info.Signatures)["zigbee:ModelIdentifier"] = device.ModelIdentifier
		name = device.ModelIdentifier
	}

	if device.ManufacturerName != "" {
		(*device.info.Signatures)["zigbee:ManufacturerName"] = device.ManufacturerName
		if device.ModelIdentifier != "" {
			name += " by "
		}
		name += device.ManufacturerName
	}

	log.Debugf("\n\n")
	log.Infof("---- Found Device IEEE:%X Name:%s ----\f", *deviceInfo.IeeeAddress, name)
	log.Debugf("Device Info: %v", *deviceInfo)

	if name != "" {
		device.info.Name = &name
	}

	if log.IsDebugEnabled() {
		spew.Dump(deviceInfo)
	}

	err = d.Conn.ExportDevice(device)
	if err != nil {
		log.Fatalf("Failed to export zigbee device %s: %s", name, err)
	}
	d.devicesFound++

	d.devices[*deviceInfo.IeeeAddress] = device

	log.Debugf("Got device : %d", *deviceInfo.IeeeAddress)

	for _, endpoint := range deviceInfo.SimpleDescList {
		log.Debugf("Got endpoint : %d", *endpoint.EndpointId)

		if containsUInt32(endpoint.InputClusters, ClusterIDOnOff) {
			log.Debugf("This endpoint has on/off cluster")

			onOff := &OnOffChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDOnOff),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := onOff.init()
			if err != nil {
				log.Debugf("Failed initialising on/off channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDPower) {
			log.Debugf("This endpoint has power cluster")

			power := &PowerChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDPower),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := power.init()
			if err != nil {
				log.Debugf("Failed initialising power channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDTemp) {
			log.Debugf("This endpoint has temperature cluster")

			temp := &TempChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDTemp),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := temp.init()
			if err != nil {
				log.Debugf("Failed initialising temp channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDHumidity) {
			log.Debugf("This endpoint has humidity cluster")

			humidity := &HumidityChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDHumidity),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := humidity.init()
			if err != nil {
				log.Debugf("Failed initialising humidity channel: %s", err)
			}

		}

		if containsUInt32(endpoint.InputClusters, ClusterIDLevel) {
			log.Debugf("This endpoint has level cluster. Exporting as brightness channel")

			if log.IsDebugEnabled() {
				spew.Dump("brightness cluster", endpoint, ClusterIDLevel)
			}

			brightness := &BrightnessChannel{
				Channel: Channel{
					ID:       fmt.Sprintf("%d-%d", *endpoint.EndpointId, ClusterIDLevel),
					device:   device,
					endpoint: endpoint,
				},
			}

			err := brightness.init()
			if err != nil {
				log.Debugf("Failed initialising brightness channel: %s", err)
			}

		}

	}

	fmt.Printf("---- Finished Device IEEE:%X ----\n", *deviceInfo.IeeeAddress)

}

func getCurDir() string {
	pwd, _ := os.Getwd()
	return pwd + "/"
}

func containsUInt32(hackstack []uint32, needle uint32) bool {
	for _, cluster := range hackstack {
		if cluster == needle {
			return true
		}
	}
	return false
}

func waitUntilZStackReady(checkFile string) {
	if checkFile == "" {
		return
	}

	// cooperate with zigbeeHAgw so that we don't start the zigbee driver
	// until we look somewhat stable.
	log.Debugf("waiting until zigbeeHAgw writes %s", checkFile)
	for {
		if _, err := os.Stat(checkFile); err == nil {
			break
		} else {
			time.Sleep(time.Second)
		}
	}
	log.Debugf("%s detected. start up continues...", checkFile)
}
<|endoftext|>


<commit_before>package main

import (
	"os"

	log "github.com/Sirupsen/logrus"
	"github.com/mitchellh/go-homedir"
	"github.com/spf13/cobra"
	"github.com/xolan/pin/list"
)

func sanity_check() {
	log.Debugln("Checking sanity...")
	var path, _ = homedir.Expand("~/.pin")
	if _, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666); !os.IsNotExist(err) {
		log.Debugf("Found %s", path)
	} else {
		log.Errorln(err.Error())
	}
}

func config(v bool) {
	if v {
		log.SetLevel(log.DebugLevel)
	} else {
		log.SetLevel(log.ErrorLevel)
	}
	sanity_check()
}

func main() {
	var Verbose bool

	var PinCmd = &cobra.Command{
		Use:   "pin",
		Short: "Pin is a command pinner, similar to aliasing",
		Long:  `Pin is a command pinner, similar to aliasing`,
		Run: func(cmd *cobra.Command, args []string) {
			config(Verbose)
		},
	}

	var ListCmd = &cobra.Command{
		Use:   "list",
		Short: "Display a list of pinned commands",
		Long:  "Display a list of pinned commands",
		Run: func(cmd *cobra.Command, args []string) {
			config(Verbose)
			list.List()
		},
	}

	var GenDocsCmd = &cobra.Command{
		Use:	"gendocs",
		Short:  "Generate documentation for this program",
		Long:  "Generate documentation for this program",
		Run: func(cmd *cobra.Command, args []string) {
			config(Verbose)
			cobra.GenMarkdownTree(PinCmd, "./")
		},
	}

	PinCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
	PinCmd.AddCommand(ListCmd)
	PinCmd.AddCommand(GenDocsCmd)
	PinCmd.Execute()
}
<commit_msg>"pin" now displays usage if nothing else is provided<commit_after>package main

import (
	"fmt"
	"os"

	log "github.com/Sirupsen/logrus"
	"github.com/mitchellh/go-homedir"
	"github.com/spf13/cobra"
	"github.com/xolan/pin/list"
)

func sanity_check() {
	log.Debugln("Checking sanity...")
	var path, _ = homedir.Expand("~/.pin")
	if _, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666); !os.IsNotExist(err) {
		log.Debugf("Found %s", path)
	} else {
		log.Errorln(err.Error())
	}
}

func config(v bool) {
	if v {
		log.SetLevel(log.DebugLevel)
	} else {
		log.SetLevel(log.ErrorLevel)
	}
	sanity_check()
}

func main() {
	var Verbose bool

	var PinCmd = &cobra.Command{
		Use:   "pin",
		Short: "Pin is a command pinner, similar to aliasing",
		Long:  `Pin is a command pinner, similar to aliasing`,
		Run: func(cmd *cobra.Command, args []string) {
			config(Verbose)
			fmt.Println(cmd.UsageString())
		},
	}

	var ListCmd = &cobra.Command{
		Use:   "list",
		Short: "Display a list of pinned commands",
		Long:  "Display a list of pinned commands",
		Run: func(cmd *cobra.Command, args []string) {
			config(Verbose)
			list.List()
		},
	}

	var GenDocsCmd = &cobra.Command{
		Use:   "gendocs",
		Short: "Generate documentation for this program",
		Long:  "Generate documentation for this program",
		Run: func(cmd *cobra.Command, args []string) {
			config(Verbose)
			cobra.GenMarkdownTree(PinCmd, "./")
		},
	}

	PinCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
	PinCmd.AddCommand(ListCmd)
	PinCmd.AddCommand(GenDocsCmd)
	PinCmd.Execute()
}
<|endoftext|>