<commit_before>package dht

import (
	"bytes"
	"testing"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"

	ds "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore"
	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"

	ci "github.com/jbenet/go-ipfs/crypto"
	inet "github.com/jbenet/go-ipfs/net"
	mux "github.com/jbenet/go-ipfs/net/mux"
	netservice "github.com/jbenet/go-ipfs/net/service"
	peer "github.com/jbenet/go-ipfs/peer"
	u "github.com/jbenet/go-ipfs/util"

	"fmt"
	"time"
)

func setupDHT(ctx context.Context, t *testing.T, p peer.Peer) *IpfsDHT {
	peerstore := peer.NewPeerstore()

	dhts := netservice.NewService(ctx, nil) // nil handler for now, need to patch it
	net, err := inet.NewIpfsNetwork(ctx, p, peerstore, &mux.ProtocolMap{
		mux.ProtocolID_Routing: dhts,
	})
	if err != nil {
		t.Fatal(err)
	}

	d := NewDHT(ctx, p, peerstore, net, dhts, ds.NewMapDatastore())
	dhts.SetHandler(d)
	return d
}

func setupDHTS(ctx context.Context, n int, t *testing.T) ([]ma.Multiaddr, []peer.Peer, []*IpfsDHT) {
	var addrs []ma.Multiaddr
	for i := 0; i < n; i++ {
		a, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 5000+i))
		if err != nil {
			t.Fatal(err)
		}
		addrs = append(addrs, a)
	}

	var peers []peer.Peer
	for i := 0; i < n; i++ {
		p := makePeer(addrs[i])
		peers = append(peers, p)
	}

	dhts := make([]*IpfsDHT, n)
	for i := 0; i < n; i++ {
		dhts[i] = setupDHT(ctx, t, peers[i])
	}

	return addrs, peers, dhts
}

func makePeer(addr ma.Multiaddr) peer.Peer {
	sk, pk, err := ci.GenerateKeyPair(ci.RSA, 512)
	if err != nil {
		panic(err)
	}
	p, err := peer.WithKeyPair(sk, pk)
	if err != nil {
		panic(err)
	}
	p.AddAddress(addr)
	return p
}

func TestPing(t *testing.T) {
	// t.Skip("skipping test to debug another")
	ctx := context.Background()
	u.Debug = false
	addrA, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/2222")
	if err != nil {
		t.Fatal(err)
	}
	addrB, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5678")
	if err != nil {
		t.Fatal(err)
	}

	peerA := makePeer(addrA)
	peerB := makePeer(addrB)

	dhtA := setupDHT(ctx, t, peerA)
	dhtB := setupDHT(ctx, t, peerB)

	defer dhtA.Close()
	defer dhtB.Close()
	defer dhtA.dialer.(inet.Network).Close()
	defer dhtB.dialer.(inet.Network).Close()

	_, err = dhtA.Connect(ctx, peerB)
	if err != nil {
		t.Fatal(err)
	}

	//Test that we can ping the node
	ctxT, _ := context.WithTimeout(ctx, 100*time.Millisecond)
	err = dhtA.Ping(ctxT, peerB)
	if err != nil {
		t.Fatal(err)
	}

	ctxT, _ = context.WithTimeout(ctx, 100*time.Millisecond)
	err = dhtB.Ping(ctxT, peerA)
	if err != nil {
		t.Fatal(err)
	}
}

func TestValueGetSet(t *testing.T) {
	// t.Skip("skipping test to debug another")

	ctx := context.Background()
	u.Debug = false
	addrA, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/11235")
	if err != nil {
		t.Fatal(err)
	}
	addrB, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/15679")
	if err != nil {
		t.Fatal(err)
	}

	peerA := makePeer(addrA)
	peerB := makePeer(addrB)

	dhtA := setupDHT(ctx, t, peerA)
	dhtB := setupDHT(ctx, t, peerB)

	defer dhtA.Close()
	defer dhtB.Close()
	defer dhtA.dialer.(inet.Network).Close()
	defer dhtB.dialer.(inet.Network).Close()

	_, err = dhtA.Connect(ctx, peerB)
	if err != nil {
		t.Fatal(err)
	}

	ctxT, _ := context.WithTimeout(ctx, time.Second)
	dhtA.PutValue(ctxT, "hello", []byte("world"))

	ctxT, _ = context.WithTimeout(ctx, time.Second*2)
	val, err := dhtA.GetValue(ctxT, "hello")
	if err != nil {
		t.Fatal(err)
	}

	if string(val) != "world" {
		t.Fatalf("Expected 'world' got '%s'", string(val))
	}

	ctxT, _ = context.WithTimeout(ctx, time.Second*2)
	val, err = dhtB.GetValue(ctxT, "hello")
	if err != nil {
		t.Fatal(err)
	}

	if string(val) != "world" {
		t.Fatalf("Expected 'world' got '%s'", string(val))
	}
}

func TestProvides(t *testing.T) {
	// t.Skip("skipping test to debug another")
	ctx := context.Background()

	u.Debug = false

	_, peers, dhts := setupDHTS(ctx, 4, t)
	defer func() {
		for i := 0; i < 4; i++ {
			dhts[i].Close()
			defer dhts[i].dialer.(inet.Network).Close()
		}
	}()

	_, err := dhts[0].Connect(ctx, peers[1])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[2])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[3])
	if err != nil {
		t.Fatal(err)
	}

	err = dhts[3].putLocal(u.Key("hello"), []byte("world"))
	if err != nil {
		t.Fatal(err)
	}

	bits, err := dhts[3].getLocal(u.Key("hello"))
	if err != nil && bytes.Equal(bits, []byte("world")) {
		t.Fatal(err)
	}

	err = dhts[3].Provide(ctx, u.Key("hello"))
	if err != nil {
		t.Fatal(err)
	}

	time.Sleep(time.Millisecond * 60)

	ctxT, _ := context.WithTimeout(ctx, time.Second)
	provchan := dhts[0].FindProvidersAsync(ctxT, u.Key("hello"), 1)

	after := time.After(time.Second)
	select {
	case prov := <-provchan:
		if prov == nil {
			t.Fatal("Got back nil provider")
		}
	case <-after:
		t.Fatal("Did not get a provider back.")
	}
}

func TestProvidesAsync(t *testing.T) {
	// t.Skip("skipping test to debug another")

	ctx := context.Background()
	u.Debug = false

	_, peers, dhts := setupDHTS(ctx, 4, t)
	defer func() {
		for i := 0; i < 4; i++ {
			dhts[i].Close()
			defer dhts[i].dialer.(inet.Network).Close()
		}
	}()

	_, err := dhts[0].Connect(ctx, peers[1])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[2])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[3])
	if err != nil {
		t.Fatal(err)
	}

	err = dhts[3].putLocal(u.Key("hello"), []byte("world"))
	if err != nil {
		t.Fatal(err)
	}

	bits, err := dhts[3].getLocal(u.Key("hello"))
	if err != nil && bytes.Equal(bits, []byte("world")) {
		t.Fatal(err)
	}

	err = dhts[3].Provide(ctx, u.Key("hello"))
	if err != nil {
		t.Fatal(err)
	}

	time.Sleep(time.Millisecond * 60)

	ctxT, _ := context.WithTimeout(ctx, time.Millisecond*300)
	provs := dhts[0].FindProvidersAsync(ctxT, u.Key("hello"), 5)
	select {
	case p, ok := <-provs:
		if !ok {
			t.Fatal("Provider channel was closed...")
		}
		if p == nil {
			t.Fatal("Got back nil provider!")
		}
		if !p.ID().Equal(dhts[3].self.ID()) {
			t.Fatalf("got a provider, but not the right one. %s", p)
		}
	case <-ctxT.Done():
		t.Fatal("Didnt get back providers")
	}
}

func TestLayeredGet(t *testing.T) {
	// t.Skip("skipping test to debug another")

	ctx := context.Background()
	u.Debug = false
	_, peers, dhts := setupDHTS(ctx, 4, t)
	defer func() {
		for i := 0; i < 4; i++ {
			dhts[i].Close()
			defer dhts[i].dialer.(inet.Network).Close()
		}
	}()

	_, err := dhts[0].Connect(ctx, peers[1])
	if err != nil {
		t.Fatalf("Failed to connect: %s", err)
	}

	_, err = dhts[1].Connect(ctx, peers[2])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[3])
	if err != nil {
		t.Fatal(err)
	}

	err = dhts[3].putLocal(u.Key("hello"), []byte("world"))
	if err != nil {
		t.Fatal(err)
	}

	err = dhts[3].Provide(ctx, u.Key("hello"))
	if err != nil {
		t.Fatal(err)
	}

	time.Sleep(time.Millisecond * 60)

	ctxT, _ := context.WithTimeout(ctx, time.Second)
	val, err := dhts[0].GetValue(ctxT, u.Key("hello"))
	if err != nil {
		t.Fatal(err)
	}

	if string(val) != "world" {
		t.Fatal("Got incorrect value.")
	}

}

func TestFindPeer(t *testing.T) {
	// t.Skip("skipping test to debug another")

	ctx := context.Background()
	u.Debug = false

	_, peers, dhts := setupDHTS(ctx, 4, t)
	defer func() {
		for i := 0; i < 4; i++ {
			dhts[i].Close()
			dhts[i].dialer.(inet.Network).Close()
		}
	}()

	_, err := dhts[0].Connect(ctx, peers[1])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[2])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[3])
	if err != nil {
		t.Fatal(err)
	}

	ctxT, _ := context.WithTimeout(ctx, time.Second)
	p, err := dhts[0].FindPeer(ctxT, peers[2].ID())
	if err != nil {
		t.Fatal(err)
	}

	if p == nil {
		t.Fatal("Failed to find peer.")
	}

	if !p.ID().Equal(peers[2].ID()) {
		t.Fatal("Didnt find expected peer.")
	}
}

func TestConnectCollision(t *testing.T) {
	// t.Skip("skipping test to debug another")

	runTimes := 10

	for rtime := 0; rtime < runTimes; rtime++ {
		log.Notice("Running Time: ", rtime)

		ctx := context.Background()
		u.Debug = false
		addrA, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/11235")
		if err != nil {
			t.Fatal(err)
		}
		addrB, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/15679")
		if err != nil {
			t.Fatal(err)
		}

		peerA := makePeer(addrA)
		peerB := makePeer(addrB)

		dhtA := setupDHT(ctx, t, peerA)
		dhtB := setupDHT(ctx, t, peerB)

		done := make(chan struct{})
		go func() {
			_, err = dhtA.Connect(ctx, peerB)
			if err != nil {
				t.Fatal(err)
			}
			done <- struct{}{}
		}()
		go func() {
			_, err = dhtB.Connect(ctx, peerA)
			if err != nil {
				t.Fatal(err)
			}
			done <- struct{}{}
		}()

		timeout := time.After(time.Second)
		select {
		case <-done:
		case <-timeout:
			t.Fatal("Timeout received!")
		}
		select {
		case <-done:
		case <-timeout:
			t.Fatal("Timeout received!")
		}

		dhtA.Close()
		dhtB.Close()
		dhtA.dialer.(inet.Network).Close()
		dhtB.dialer.(inet.Network).Close()

		<-time.After(200 * time.Millisecond)
	}
}
<commit_msg>swarm + net: add explicit listen addresses<commit_after>package dht

import (
	"bytes"
	"testing"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"

	ds "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore"
	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"

	ci "github.com/jbenet/go-ipfs/crypto"
	inet "github.com/jbenet/go-ipfs/net"
	mux "github.com/jbenet/go-ipfs/net/mux"
	netservice "github.com/jbenet/go-ipfs/net/service"
	peer "github.com/jbenet/go-ipfs/peer"
	u "github.com/jbenet/go-ipfs/util"

	"fmt"
	"time"
)

func setupDHT(ctx context.Context, t *testing.T, p peer.Peer) *IpfsDHT {
	peerstore := peer.NewPeerstore()

	dhts := netservice.NewService(ctx, nil) // nil handler for now, need to patch it
	net, err := inet.NewIpfsNetwork(ctx, p.Addresses(), p, peerstore, &mux.ProtocolMap{
		mux.ProtocolID_Routing: dhts,
	})
	if err != nil {
		t.Fatal(err)
	}

	d := NewDHT(ctx, p, peerstore, net, dhts, ds.NewMapDatastore())
	dhts.SetHandler(d)
	return d
}

func setupDHTS(ctx context.Context, n int, t *testing.T) ([]ma.Multiaddr, []peer.Peer, []*IpfsDHT) {
	var addrs []ma.Multiaddr
	for i := 0; i < n; i++ {
		a, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 5000+i))
		if err != nil {
			t.Fatal(err)
		}
		addrs = append(addrs, a)
	}

	var peers []peer.Peer
	for i := 0; i < n; i++ {
		p := makePeer(addrs[i])
		peers = append(peers, p)
	}

	dhts := make([]*IpfsDHT, n)
	for i := 0; i < n; i++ {
		dhts[i] = setupDHT(ctx, t, peers[i])
	}

	return addrs, peers, dhts
}

func makePeer(addr ma.Multiaddr) peer.Peer {
	sk, pk, err := ci.GenerateKeyPair(ci.RSA, 512)
	if err != nil {
		panic(err)
	}
	p, err := peer.WithKeyPair(sk, pk)
	if err != nil {
		panic(err)
	}
	p.AddAddress(addr)
	return p
}

func TestPing(t *testing.T) {
	// t.Skip("skipping test to debug another")
	ctx := context.Background()
	u.Debug = false
	addrA, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/2222")
	if err != nil {
		t.Fatal(err)
	}
	addrB, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5678")
	if err != nil {
		t.Fatal(err)
	}

	peerA := makePeer(addrA)
	peerB := makePeer(addrB)

	dhtA := setupDHT(ctx, t, peerA)
	dhtB := setupDHT(ctx, t, peerB)

	defer dhtA.Close()
	defer dhtB.Close()
	defer dhtA.dialer.(inet.Network).Close()
	defer dhtB.dialer.(inet.Network).Close()

	_, err = dhtA.Connect(ctx, peerB)
	if err != nil {
		t.Fatal(err)
	}

	//Test that we can ping the node
	ctxT, _ := context.WithTimeout(ctx, 100*time.Millisecond)
	err = dhtA.Ping(ctxT, peerB)
	if err != nil {
		t.Fatal(err)
	}

	ctxT, _ = context.WithTimeout(ctx, 100*time.Millisecond)
	err = dhtB.Ping(ctxT, peerA)
	if err != nil {
		t.Fatal(err)
	}
}

func TestValueGetSet(t *testing.T) {
	// t.Skip("skipping test to debug another")

	ctx := context.Background()
	u.Debug = false
	addrA, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/11235")
	if err != nil {
		t.Fatal(err)
	}
	addrB, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/15679")
	if err != nil {
		t.Fatal(err)
	}

	peerA := makePeer(addrA)
	peerB := makePeer(addrB)

	dhtA := setupDHT(ctx, t, peerA)
	dhtB := setupDHT(ctx, t, peerB)

	defer dhtA.Close()
	defer dhtB.Close()
	defer dhtA.dialer.(inet.Network).Close()
	defer dhtB.dialer.(inet.Network).Close()

	_, err = dhtA.Connect(ctx, peerB)
	if err != nil {
		t.Fatal(err)
	}

	ctxT, _ := context.WithTimeout(ctx, time.Second)
	dhtA.PutValue(ctxT, "hello", []byte("world"))

	ctxT, _ = context.WithTimeout(ctx, time.Second*2)
	val, err := dhtA.GetValue(ctxT, "hello")
	if err != nil {
		t.Fatal(err)
	}

	if string(val) != "world" {
		t.Fatalf("Expected 'world' got '%s'", string(val))
	}

	ctxT, _ = context.WithTimeout(ctx, time.Second*2)
	val, err = dhtB.GetValue(ctxT, "hello")
	if err != nil {
		t.Fatal(err)
	}

	if string(val) != "world" {
		t.Fatalf("Expected 'world' got '%s'", string(val))
	}
}

func TestProvides(t *testing.T) {
	// t.Skip("skipping test to debug another")
	ctx := context.Background()

	u.Debug = false

	_, peers, dhts := setupDHTS(ctx, 4, t)
	defer func() {
		for i := 0; i < 4; i++ {
			dhts[i].Close()
			defer dhts[i].dialer.(inet.Network).Close()
		}
	}()

	_, err := dhts[0].Connect(ctx, peers[1])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[2])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[3])
	if err != nil {
		t.Fatal(err)
	}

	err = dhts[3].putLocal(u.Key("hello"), []byte("world"))
	if err != nil {
		t.Fatal(err)
	}

	bits, err := dhts[3].getLocal(u.Key("hello"))
	if err != nil && bytes.Equal(bits, []byte("world")) {
		t.Fatal(err)
	}

	err = dhts[3].Provide(ctx, u.Key("hello"))
	if err != nil {
		t.Fatal(err)
	}

	time.Sleep(time.Millisecond * 60)

	ctxT, _ := context.WithTimeout(ctx, time.Second)
	provchan := dhts[0].FindProvidersAsync(ctxT, u.Key("hello"), 1)

	after := time.After(time.Second)
	select {
	case prov := <-provchan:
		if prov == nil {
			t.Fatal("Got back nil provider")
		}
	case <-after:
		t.Fatal("Did not get a provider back.")
	}
}

func TestProvidesAsync(t *testing.T) {
	// t.Skip("skipping test to debug another")

	ctx := context.Background()
	u.Debug = false

	_, peers, dhts := setupDHTS(ctx, 4, t)
	defer func() {
		for i := 0; i < 4; i++ {
			dhts[i].Close()
			defer dhts[i].dialer.(inet.Network).Close()
		}
	}()

	_, err := dhts[0].Connect(ctx, peers[1])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[2])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[3])
	if err != nil {
		t.Fatal(err)
	}

	err = dhts[3].putLocal(u.Key("hello"), []byte("world"))
	if err != nil {
		t.Fatal(err)
	}

	bits, err := dhts[3].getLocal(u.Key("hello"))
	if err != nil && bytes.Equal(bits, []byte("world")) {
		t.Fatal(err)
	}

	err = dhts[3].Provide(ctx, u.Key("hello"))
	if err != nil {
		t.Fatal(err)
	}

	time.Sleep(time.Millisecond * 60)

	ctxT, _ := context.WithTimeout(ctx, time.Millisecond*300)
	provs := dhts[0].FindProvidersAsync(ctxT, u.Key("hello"), 5)
	select {
	case p, ok := <-provs:
		if !ok {
			t.Fatal("Provider channel was closed...")
		}
		if p == nil {
			t.Fatal("Got back nil provider!")
		}
		if !p.ID().Equal(dhts[3].self.ID()) {
			t.Fatalf("got a provider, but not the right one. %s", p)
		}
	case <-ctxT.Done():
		t.Fatal("Didnt get back providers")
	}
}

func TestLayeredGet(t *testing.T) {
	// t.Skip("skipping test to debug another")

	ctx := context.Background()
	u.Debug = false
	_, peers, dhts := setupDHTS(ctx, 4, t)
	defer func() {
		for i := 0; i < 4; i++ {
			dhts[i].Close()
			defer dhts[i].dialer.(inet.Network).Close()
		}
	}()

	_, err := dhts[0].Connect(ctx, peers[1])
	if err != nil {
		t.Fatalf("Failed to connect: %s", err)
	}

	_, err = dhts[1].Connect(ctx, peers[2])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[3])
	if err != nil {
		t.Fatal(err)
	}

	err = dhts[3].putLocal(u.Key("hello"), []byte("world"))
	if err != nil {
		t.Fatal(err)
	}

	err = dhts[3].Provide(ctx, u.Key("hello"))
	if err != nil {
		t.Fatal(err)
	}

	time.Sleep(time.Millisecond * 60)

	ctxT, _ := context.WithTimeout(ctx, time.Second)
	val, err := dhts[0].GetValue(ctxT, u.Key("hello"))
	if err != nil {
		t.Fatal(err)
	}

	if string(val) != "world" {
		t.Fatal("Got incorrect value.")
	}

}

func TestFindPeer(t *testing.T) {
	// t.Skip("skipping test to debug another")

	ctx := context.Background()
	u.Debug = false

	_, peers, dhts := setupDHTS(ctx, 4, t)
	defer func() {
		for i := 0; i < 4; i++ {
			dhts[i].Close()
			dhts[i].dialer.(inet.Network).Close()
		}
	}()

	_, err := dhts[0].Connect(ctx, peers[1])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[2])
	if err != nil {
		t.Fatal(err)
	}

	_, err = dhts[1].Connect(ctx, peers[3])
	if err != nil {
		t.Fatal(err)
	}

	ctxT, _ := context.WithTimeout(ctx, time.Second)
	p, err := dhts[0].FindPeer(ctxT, peers[2].ID())
	if err != nil {
		t.Fatal(err)
	}

	if p == nil {
		t.Fatal("Failed to find peer.")
	}

	if !p.ID().Equal(peers[2].ID()) {
		t.Fatal("Didnt find expected peer.")
	}
}

func TestConnectCollision(t *testing.T) {
	// t.Skip("skipping test to debug another")

	runTimes := 10

	for rtime := 0; rtime < runTimes; rtime++ {
		log.Notice("Running Time: ", rtime)

		ctx := context.Background()
		u.Debug = false
		addrA, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/11235")
		if err != nil {
			t.Fatal(err)
		}
		addrB, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/15679")
		if err != nil {
			t.Fatal(err)
		}

		peerA := makePeer(addrA)
		peerB := makePeer(addrB)

		dhtA := setupDHT(ctx, t, peerA)
		dhtB := setupDHT(ctx, t, peerB)

		done := make(chan struct{})
		go func() {
			_, err = dhtA.Connect(ctx, peerB)
			if err != nil {
				t.Fatal(err)
			}
			done <- struct{}{}
		}()
		go func() {
			_, err = dhtB.Connect(ctx, peerA)
			if err != nil {
				t.Fatal(err)
			}
			done <- struct{}{}
		}()

		timeout := time.After(time.Second)
		select {
		case <-done:
		case <-timeout:
			t.Fatal("Timeout received!")
		}
		select {
		case <-done:
		case <-timeout:
			t.Fatal("Timeout received!")
		}

		dhtA.Close()
		dhtB.Close()
		dhtA.dialer.(inet.Network).Close()
		dhtB.dialer.(inet.Network).Close()

		<-time.After(200 * time.Millisecond)
	}
}
<|endoftext|>


<commit_before>package libvirt

import (
	"encoding/xml"
	"fmt"
	"log"
	"testing"

	libvirt "github.com/dmacvicar/libvirt-go"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccLibvirtDomain_Basic(t *testing.T) {
	var domain libvirt.VirDomain
	var config = fmt.Sprintf(`
            resource "libvirt_domain" "acceptance-test-domain-1" {
                    name = "terraform-test"
            }`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: config,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain-1", &domain),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain-1", "name", "terraform-test"),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain-1", "memory", "512"),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain-1", "vcpu", "1"),
				),
			},
		},
	})
}

func TestAccLibvirtDomain_Detailed(t *testing.T) {
	var domain libvirt.VirDomain
	var config = fmt.Sprintf(`
            resource "libvirt_domain" "acceptance-test-domain-2" {
                    name = "terraform-test"
                    memory = 384
                    vcpu = 2
            }`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: config,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain-2", &domain),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain-2", "name", "terraform-test"),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain-2", "memory", "384"),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain-2", "vcpu", "2"),
				),
			},
		},
	})
}

func TestAccLibvirtDomain_Volume(t *testing.T) {
	var domain libvirt.VirDomain
	var volume libvirt.VirStorageVol

	var configVolAttached = fmt.Sprintf(`
            resource "libvirt_volume" "acceptance-test-volume" {
                    name = "terraform-test"
            }

            resource "libvirt_domain" "acceptance-test-domain" {
                    name = "terraform-test"
                    disk {
                            volume_id = "${libvirt_volume.acceptance-test-volume.id}"
                    }
            }`)

	var configVolDettached = fmt.Sprintf(`
            resource "libvirt_domain" "acceptance-test-domain" {
                    name = "terraform-test"
            }`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: configVolAttached,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					testAccCheckLibvirtVolumeExists("libvirt_volume.acceptance-test-volume", &volume),
				),
			},
			resource.TestStep{
				Config: configVolDettached,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					testAccCheckLibvirtVolumeDoesNotExists("libvirt_volume.acceptance-test-volume", &volume),
				),
			},
		},
	})
}

func TestAccLibvirtDomain_VolumeTwoDisks(t *testing.T) {
	var domain libvirt.VirDomain
	var volume libvirt.VirStorageVol

	var configVolAttached = fmt.Sprintf(`
            resource "libvirt_volume" "acceptance-test-volume1" {
                    name = "terraform-test-vol1"
            }

            resource "libvirt_volume" "acceptance-test-volume2" {
                    name = "terraform-test-vol2"
            }

            resource "libvirt_domain" "acceptance-test-domain" {
                    name = "terraform-test-domain"
                    disk {
                            volume_id = "${libvirt_volume.acceptance-test-volume1.id}"
                    }

                    disk {
                            volume_id = "${libvirt_volume.acceptance-test-volume2.id}"
                    }
            }`)

	var configVolDettached = fmt.Sprintf(`
            resource "libvirt_domain" "acceptance-test-domain" {
                    name = "terraform-test-domain"
            }`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: configVolAttached,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					testAccCheckLibvirtVolumeExists("libvirt_volume.acceptance-test-volume1", &volume),
					testAccCheckLibvirtVolumeExists("libvirt_volume.acceptance-test-volume2", &volume),
				),
			},
			resource.TestStep{
				Config: configVolDettached,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					testAccCheckLibvirtVolumeDoesNotExists("libvirt_volume.acceptance-test-volume1", &volume),
					testAccCheckLibvirtVolumeDoesNotExists("libvirt_volume.acceptance-test-volume2", &volume),
				),
			},
		},
	})
}

func TestAccLibvirtDomain_NetworkInterface(t *testing.T) {
	var domain libvirt.VirDomain

	var config = fmt.Sprintf(`
            resource "libvirt_volume" "acceptance-test-volume" {
                    name = "terraform-test"
            }

            resource "libvirt_domain" "acceptance-test-domain" {
                    name = "terraform-test"
                    network_interface = {
                            network_name = "default"
                    }
                    network_interface = {
                            bridge = "br0"
                            mac = "52:54:00:A9:F5:17"
                    }
                    disk {
                            volume_id = "${libvirt_volume.acceptance-test-volume.id}"
                    }
            }`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: config,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain", "network_interface.0.network_name", "default"),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain", "network_interface.1.mac", "52:54:00:A9:F5:17"),
				),
			},
		},
	})
}

func TestAccLibvirtDomain_IgnitionObject(t *testing.T) {
	var domain libvirt.VirDomain

	var config = fmt.Sprintf(`
	    resource "ignition_systemd_unit" "acceptance-test-systemd" {
    		name = "example.service"
    		content = "[Service]\nType=oneshot\nExecStart=/usr/bin/echo Hello World\n\n[Install]\nWantedBy=multi-user.target"
	    }

	    resource "ignition_config" "acceptance-test-config" {
    		systemd = [
        		"${ignition_systemd_unit.acceptance-test-systemd.id}",
    		]
	    }

	    resource "libvirt_domain" "acceptance-test-domain" {
		name = "terraform-test-domain"
		coreos_ignition = "${ignition_config.acceptance-test-config.rendered}"
	    }
	`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config:             config,
				ExpectNonEmptyPlan: true,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					testAccCheckIgnitionFileNameExists(&domain),
				),
			},
		},
	})
}

func testAccCheckLibvirtDomainDestroy(s *terraform.State) error {
	virtConn := testAccProvider.Meta().(*Client).libvirt

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "libvirt_domain" {
			continue
		}

		// Try to find the server
		_, err := virtConn.LookupByUUIDString(rs.Primary.ID)
		if err == nil {
			return fmt.Errorf(
				"Error waiting for domain (%s) to be destroyed: %s",
				rs.Primary.ID, err)
		}
	}

	return nil
}

func testAccCheckLibvirtDomainExists(n string, domain *libvirt.VirDomain) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No libvirt domain ID is set")
		}

		virConn := testAccProvider.Meta().(*Client).libvirt

		retrieveDomain, err := virConn.LookupByUUIDString(rs.Primary.ID)

		if err != nil {
			return err
		}

		log.Printf("The ID is %s", rs.Primary.ID)

		realId, err := retrieveDomain.GetUUIDString()
		if err != nil {
			return err
		}

		if realId != rs.Primary.ID {
			return fmt.Errorf("Libvirt domain not found")
		}

		*domain = retrieveDomain

		return nil
	}
}

func testAccCheckIgnitionFileNameExists(domain *libvirt.VirDomain) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		var ignStr string
		for _, rs := range s.RootModule().Resources {
			if rs.Type != "libvirt_domain" {
				continue
			}
			ignStr = rs.Primary.Attributes["coreos_ignition"]
		}

		xmlDesc, err := domain.GetXMLDesc(0)
		if err != nil {
			return fmt.Errorf("Error retrieving libvirt domain XML description: %s", err)
		}

		domainDef := newDomainDef()
		err = xml.Unmarshal([]byte(xmlDesc), &domainDef)
		if err != nil {
			return fmt.Errorf("Error reading libvirt domain XML description: %s", err)
		}

		ignitionFile := domainDef.Metadata.TerraformLibvirt.IgnitionFile
		if ignitionFile == "" {
			return fmt.Errorf("No ignition file meta-data")
		}

		hashStr := hash(ignStr)
		hashFile := fmt.Sprint("/tmp/", hashStr, ".ign")
		if ignitionFile != hashFile {
			return fmt.Errorf("Igntion file metadata incorrect %s %s", ignitionFile, hashFile)
		}
		return nil
	}
}

func TestHash(t *testing.T) {
	actual := hash("this is a test")
	expected := "2e99758548972a8e8822ad47fa1017ff72f06f3ff6a016851f45c398732bc50c"

	if actual != expected {
		t.Errorf("Expected %s, got %s", expected, actual)
	}
}
<commit_msg>Add test for graphics<commit_after>package libvirt

import (
	"encoding/xml"
	"fmt"
	"log"
	"testing"

	libvirt "github.com/dmacvicar/libvirt-go"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccLibvirtDomain_Basic(t *testing.T) {
	var domain libvirt.VirDomain
	var config = fmt.Sprintf(`
            resource "libvirt_domain" "acceptance-test-domain-1" {
                    name = "terraform-test"
            }`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: config,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain-1", &domain),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain-1", "name", "terraform-test"),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain-1", "memory", "512"),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain-1", "vcpu", "1"),
				),
			},
		},
	})
}

func TestAccLibvirtDomain_Detailed(t *testing.T) {
	var domain libvirt.VirDomain
	var config = fmt.Sprintf(`
            resource "libvirt_domain" "acceptance-test-domain-2" {
                    name = "terraform-test"
                    memory = 384
                    vcpu = 2
            }`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: config,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain-2", &domain),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain-2", "name", "terraform-test"),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain-2", "memory", "384"),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain-2", "vcpu", "2"),
				),
			},
		},
	})
}

func TestAccLibvirtDomain_Volume(t *testing.T) {
	var domain libvirt.VirDomain
	var volume libvirt.VirStorageVol

	var configVolAttached = fmt.Sprintf(`
            resource "libvirt_volume" "acceptance-test-volume" {
                    name = "terraform-test"
            }

            resource "libvirt_domain" "acceptance-test-domain" {
                    name = "terraform-test"
                    disk {
                            volume_id = "${libvirt_volume.acceptance-test-volume.id}"
                    }
            }`)

	var configVolDettached = fmt.Sprintf(`
            resource "libvirt_domain" "acceptance-test-domain" {
                    name = "terraform-test"
            }`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: configVolAttached,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					testAccCheckLibvirtVolumeExists("libvirt_volume.acceptance-test-volume", &volume),
				),
			},
			resource.TestStep{
				Config: configVolDettached,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					testAccCheckLibvirtVolumeDoesNotExists("libvirt_volume.acceptance-test-volume", &volume),
				),
			},
		},
	})
}

func TestAccLibvirtDomain_VolumeTwoDisks(t *testing.T) {
	var domain libvirt.VirDomain
	var volume libvirt.VirStorageVol

	var configVolAttached = fmt.Sprintf(`
            resource "libvirt_volume" "acceptance-test-volume1" {
                    name = "terraform-test-vol1"
            }

            resource "libvirt_volume" "acceptance-test-volume2" {
                    name = "terraform-test-vol2"
            }

            resource "libvirt_domain" "acceptance-test-domain" {
                    name = "terraform-test-domain"
                    disk {
                            volume_id = "${libvirt_volume.acceptance-test-volume1.id}"
                    }

                    disk {
                            volume_id = "${libvirt_volume.acceptance-test-volume2.id}"
                    }
            }`)

	var configVolDettached = fmt.Sprintf(`
            resource "libvirt_domain" "acceptance-test-domain" {
                    name = "terraform-test-domain"
            }`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: configVolAttached,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					testAccCheckLibvirtVolumeExists("libvirt_volume.acceptance-test-volume1", &volume),
					testAccCheckLibvirtVolumeExists("libvirt_volume.acceptance-test-volume2", &volume),
				),
			},
			resource.TestStep{
				Config: configVolDettached,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					testAccCheckLibvirtVolumeDoesNotExists("libvirt_volume.acceptance-test-volume1", &volume),
					testAccCheckLibvirtVolumeDoesNotExists("libvirt_volume.acceptance-test-volume2", &volume),
				),
			},
		},
	})
}

func TestAccLibvirtDomain_NetworkInterface(t *testing.T) {
	var domain libvirt.VirDomain

	var config = fmt.Sprintf(`
            resource "libvirt_volume" "acceptance-test-volume" {
                    name = "terraform-test"
            }

            resource "libvirt_domain" "acceptance-test-domain" {
                    name = "terraform-test"
                    network_interface = {
                            network_name = "default"
                    }
                    network_interface = {
                            bridge = "br0"
                            mac = "52:54:00:A9:F5:17"
                    }
                    disk {
                            volume_id = "${libvirt_volume.acceptance-test-volume.id}"
                    }
            }`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: config,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain", "network_interface.0.network_name", "default"),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain", "network_interface.1.mac", "52:54:00:A9:F5:17"),
				),
			},
		},
	})
}

func TestAccLibvirtDomain_Graphics(t *testing.T) {
	var domain libvirt.VirDomain

	var config = fmt.Sprintf(`
            resource "libvirt_volume" "acceptance-test-graphics" {
                    name = "terraform-test"
            }

            resource "libvirt_domain" "acceptance-test-domain" {
                    name = "terraform-test"
                    graphics {
                            type = "spice"
                            autoport = "yes"
                            listen_type = "none"
                    }
            }`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: config,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain", "graphics.type", "spice"),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain", "graphics.autoport", "yes"),
					resource.TestCheckResourceAttr(
						"libvirt_domain.acceptance-test-domain", "graphics.listen_type", "none"),
				),
			},
		},
	})
}

func TestAccLibvirtDomain_IgnitionObject(t *testing.T) {
	var domain libvirt.VirDomain

	var config = fmt.Sprintf(`
	    resource "ignition_systemd_unit" "acceptance-test-systemd" {
    		name = "example.service"
    		content = "[Service]\nType=oneshot\nExecStart=/usr/bin/echo Hello World\n\n[Install]\nWantedBy=multi-user.target"
	    }

	    resource "ignition_config" "acceptance-test-config" {
    		systemd = [
        		"${ignition_systemd_unit.acceptance-test-systemd.id}",
    		]
	    }

	    resource "libvirt_domain" "acceptance-test-domain" {
		name = "terraform-test-domain"
		coreos_ignition = "${ignition_config.acceptance-test-config.rendered}"
	    }
	`)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLibvirtDomainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config:             config,
				ExpectNonEmptyPlan: true,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
					testAccCheckIgnitionFileNameExists(&domain),
				),
			},
		},
	})
}

func testAccCheckLibvirtDomainDestroy(s *terraform.State) error {
	virtConn := testAccProvider.Meta().(*Client).libvirt

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "libvirt_domain" {
			continue
		}

		// Try to find the server
		_, err := virtConn.LookupByUUIDString(rs.Primary.ID)
		if err == nil {
			return fmt.Errorf(
				"Error waiting for domain (%s) to be destroyed: %s",
				rs.Primary.ID, err)
		}
	}

	return nil
}

func testAccCheckLibvirtDomainExists(n string, domain *libvirt.VirDomain) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No libvirt domain ID is set")
		}

		virConn := testAccProvider.Meta().(*Client).libvirt

		retrieveDomain, err := virConn.LookupByUUIDString(rs.Primary.ID)

		if err != nil {
			return err
		}

		log.Printf("The ID is %s", rs.Primary.ID)

		realId, err := retrieveDomain.GetUUIDString()
		if err != nil {
			return err
		}

		if realId != rs.Primary.ID {
			return fmt.Errorf("Libvirt domain not found")
		}

		*domain = retrieveDomain

		return nil
	}
}

func testAccCheckIgnitionFileNameExists(domain *libvirt.VirDomain) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		var ignStr string
		for _, rs := range s.RootModule().Resources {
			if rs.Type != "libvirt_domain" {
				continue
			}
			ignStr = rs.Primary.Attributes["coreos_ignition"]
		}

		xmlDesc, err := domain.GetXMLDesc(0)
		if err != nil {
			return fmt.Errorf("Error retrieving libvirt domain XML description: %s", err)
		}

		domainDef := newDomainDef()
		err = xml.Unmarshal([]byte(xmlDesc), &domainDef)
		if err != nil {
			return fmt.Errorf("Error reading libvirt domain XML description: %s", err)
		}

		ignitionFile := domainDef.Metadata.TerraformLibvirt.IgnitionFile
		if ignitionFile == "" {
			return fmt.Errorf("No ignition file meta-data")
		}

		hashStr := hash(ignStr)
		hashFile := fmt.Sprint("/tmp/", hashStr, ".ign")
		if ignitionFile != hashFile {
			return fmt.Errorf("Igntion file metadata incorrect %s %s", ignitionFile, hashFile)
		}
		return nil
	}
}

func TestHash(t *testing.T) {
	actual := hash("this is a test")
	expected := "2e99758548972a8e8822ad47fa1017ff72f06f3ff6a016851f45c398732bc50c"

	if actual != expected {
		t.Errorf("Expected %s, got %s", expected, actual)
	}
}
<|endoftext|>


<commit_before>package tumblr

import (
	"net/http"
	"net/url"
	"path"
)

func callAPI(u url.URL) (*TumblrAPIResponse, error) {
	resp, err := http.Get(u.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	// TODO JSON decoding
}

type TumblrAPIResponse struct {
	Meta     Meta
	Response interface{}
}

type Meta struct {
	Status int64
	Msg    string
}

type Blog struct {
	BaseHostname string
}

type Post struct{}

const (
	urlBase = "http://api.tumblr.com/v2/blog/"
)

// Post Types
const (
	Text   = "text"
	Quote  = "quote"
	Link   = "link"
	Answer = "answer"
	Video  = "video"
	Audio  = "audio"
	Photo  = "photo"
	Chat   = "chat"
)

// Where the request is directed to
func (blog Blog) entityURL(entityType string) (*url.URL, error) {
	url, err := url.Parse(urlBase)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, blog.BaseHostname, entityType)
	return url, nil
}

type LimitOffset struct {
	Limit  int
	Offset int
}

// Request Parameter Types
type PostRequestParams struct {
	PostType   string
	Id         int64
	Tag        string
	ReblogInfo bool
	NotesInfo  bool
	Filter     string
	LimitOffset
}

func (params PostRequestParams) validatePostRequestParams() error {
	if params.Limit < 0 || params.Limit > 20 {
		return TumblrError{"Post request parameter limit out of range"}
	}
	if params.Filter != "" &&
		!(params.Filter == "html" || params.Filter == "raw") {
		return TumblrError{`Filter, if specified, must be either "html" or "raw"`}
	}
	return nil
}

// Posts posted by a blog
func (blog Blog) Posts(params PostRequestParams) []Post {
	// Build URL
	url, err := blog.entityURL("posts")
	if err != nil {
		return nil
	}

	// PostType
	if params.PostType != "" {
		url.Path = path.Join(url.Path, params.PostType)
	}

	// Id
	if params.Id != 0 {
		url.Query().Set("id", string(params.Id))
	}

	// Tag
	if params.Tag != "" {
		url.Query().Set("tag", params.Tag)
	}

	// ReblogInfo
	if params.ReblogInfo {
		url.Query().Set("reblog_info", "true")
	}

	// NotesInfo
	if params.NotesInfo {
		url.Query().Set("notes_info", "true")
	}

	addLimitOffset(url, params.LimitOffset)

	return nil
}

func addLimitOffset(url *url.URL, params LimitOffset) {
	// Limit
	if params.Limit != 0 {
		url.Query().Set("limit", string(params.Limit))
	}

	// Offset
	if params.Offset != 0 {
		url.Query().Set("offset", string(params.Offset))
	}
}
<commit_msg>Blogs are not the only entity; users and tags exist as well.  So differentiate here.<commit_after>package tumblr

import (
	"net/http"
	"net/url"
	"path"
)

func callAPI(u url.URL) (*TumblrAPIResponse, error) {
	resp, err := http.Get(u.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	// TODO JSON decoding
}

type TumblrAPIResponse struct {
	Meta     Meta
	Response interface{}
}

type Meta struct {
	Status int64
	Msg    string
}

type Blog struct {
	BaseHostname string
}

type Post struct{}

const (
	urlBaseBlog = "http://api.tumblr.com/v2/blog/"
	urlBaseUser = "http://api.tumblr.com/v2/user/"
)

// Post Types
const (
	Text   = "text"
	Quote  = "quote"
	Link   = "link"
	Answer = "answer"
	Video  = "video"
	Audio  = "audio"
	Photo  = "photo"
	Chat   = "chat"
)

// Where the request is directed to
func (blog Blog) blogEntityURL(entityType string) (*url.URL, error) {
	url, err := url.Parse(urlBaseBlog)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, blog.BaseHostname, entityType)
	return url, nil
}

type LimitOffset struct {
	Limit  int
	Offset int
}

// Request Parameter Types
type PostRequestParams struct {
	PostType   string
	Id         int64
	Tag        string
	ReblogInfo bool
	NotesInfo  bool
	Filter     string
	LimitOffset
}

func (params PostRequestParams) validatePostRequestParams() error {
	if params.Limit < 0 || params.Limit > 20 {
		return TumblrError{"Post request parameter limit out of range"}
	}
	if params.Filter != "" &&
		!(params.Filter == "html" || params.Filter == "raw") {
		return TumblrError{`Filter, if specified, must be either "html" or "raw"`}
	}
	return nil
}

// Posts posted by a blog
func (blog Blog) Posts(params PostRequestParams) []Post {
	// Build URL
	url, err := blog.blogEntityURL("posts")
	if err != nil {
		return nil
	}

	// PostType
	if params.PostType != "" {
		url.Path = path.Join(url.Path, params.PostType)
	}

	// Id
	if params.Id != 0 {
		url.Query().Set("id", string(params.Id))
	}

	// Tag
	if params.Tag != "" {
		url.Query().Set("tag", params.Tag)
	}

	// ReblogInfo
	if params.ReblogInfo {
		url.Query().Set("reblog_info", "true")
	}

	// NotesInfo
	if params.NotesInfo {
		url.Query().Set("notes_info", "true")
	}

	addLimitOffset(url, params.LimitOffset)

	return nil
}

func addLimitOffset(url *url.URL, params LimitOffset) {
	// Limit
	if params.Limit != 0 {
		url.Query().Set("limit", string(params.Limit))
	}

	// Offset
	if params.Offset != 0 {
		url.Query().Set("offset", string(params.Offset))
	}
}
<|endoftext|>


<commit_before>package dict

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

// POSTable represents a table for managing part of speeches.
type POSTable struct {
	POSs     []POS
	NameList []string
}

// POSID represents a ID of part of speech.
type POSID uint32

// POS represents a vector of part of speech.
type POS []POSID

const maxPOSID = 1<<32 - 1

// WriteTo saves a POS table.
func (p POSTable) WriteTo(w io.Writer) (int64, error) {
	var b bytes.Buffer
	enc := gob.NewEncoder(&b)
	if err := enc.Encode(p.POSs); err != nil {
		return 0, err
	}
	if err := enc.Encode(p.NameList); err != nil {
		return 0, err
	}
	return b.WriteTo(w)
}

// ReadPOSTable loads a POS table.
func ReadPOSTable(r io.Reader) (POSTable, error) {
	ret := POSTable{}
	dec := gob.NewDecoder(r)
	if err := dec.Decode(&ret.POSs); err != nil {
		return ret, fmt.Errorf("POSs read error, %v", err)
	}
	if err := dec.Decode(&ret.NameList); err != nil {
		return ret, fmt.Errorf("name list read error, %v", err)
	}
	return ret, nil
}

// POSMap represents a part of speech control table.
type POSMap map[string]POSID

// Add adds part of speech item to the POS control table and returns it's id.
func (p POSMap) Add(pos []string) POS {
	ret := make(POS, 0, len(pos))
	for _, name := range pos {
		id := p.add(name)
		ret = append(ret, id)
	}
	return ret
}

func (p POSMap) add(pos string) POSID {
	id, ok := p[pos]
	if !ok {
		if len(p) > maxPOSID {
			panic(fmt.Errorf("new POSID overflowed %q %d > %d", pos, len(p), maxPOSID))
		}
		id = POSID(len(p)) + 1
		p[pos] = id
	}
	return id
}

// List returns a list whose index is POS ID and value is its name.
func (p POSMap) List() []string {
	ret := make([]string, len(p)+1)
	for k, v := range p {
		ret[v] = k
	}
	return ret
}
<commit_msg>POSID uint32 -> uint16<commit_after>package dict

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

// POSTable represents a table for managing part of speeches.
type POSTable struct {
	POSs     []POS
	NameList []string
}

// POSID represents a ID of part of speech.
type POSID uint16

// POS represents a vector of part of speech.
type POS []POSID

const maxPOSID = 1<<16 - 1

// WriteTo saves a POS table.
func (p POSTable) WriteTo(w io.Writer) (int64, error) {
	var b bytes.Buffer
	enc := gob.NewEncoder(&b)
	if err := enc.Encode(p.POSs); err != nil {
		return 0, err
	}
	if err := enc.Encode(p.NameList); err != nil {
		return 0, err
	}
	return b.WriteTo(w)
}

// ReadPOSTable loads a POS table.
func ReadPOSTable(r io.Reader) (POSTable, error) {
	ret := POSTable{}
	dec := gob.NewDecoder(r)
	if err := dec.Decode(&ret.POSs); err != nil {
		return ret, fmt.Errorf("POSs read error, %v", err)
	}
	if err := dec.Decode(&ret.NameList); err != nil {
		return ret, fmt.Errorf("name list read error, %v", err)
	}
	return ret, nil
}

// POSMap represents a part of speech control table.
type POSMap map[string]POSID

// Add adds part of speech item to the POS control table and returns it's id.
func (p POSMap) Add(pos []string) POS {
	ret := make(POS, 0, len(pos))
	for _, name := range pos {
		id := p.add(name)
		ret = append(ret, id)
	}
	return ret
}

func (p POSMap) add(pos string) POSID {
	id, ok := p[pos]
	if !ok {
		if len(p) > maxPOSID {
			panic(fmt.Errorf("new POSID overflowed %q %d > %d", pos, len(p), maxPOSID))
		}
		id = POSID(len(p)) + 1
		p[pos] = id
	}
	return id
}

// List returns a list whose index is POS ID and value is its name.
func (p POSMap) List() []string {
	ret := make([]string, len(p)+1)
	for k, v := range p {
		ret[v] = k
	}
	return ret
}
<|endoftext|>


<commit_before>// 24 september 2014

package main

import (
	"path/filepath"
	"strings"
	"sort"
)

type Stage []*Executor

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

func (s Stage) Less(i, j int) bool {
	return s[i].Line[1] < s[j].Line[1]
}

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

var script []Stage
var nStages int

func buildScript() {
	script = nil
	nStages = 0

	// stage 1: make the object file directory
	e := &Executor{
		Name:	"Made working directory",
		Line:		[]string{"mkdir", "-p", ".qoobj"},
	}
	script = append(script, Stage{e})
	nStages++

	// stage 2: compile everything
	stage2 := Stage(nil)
	objects := []string(nil)
	for _, f := range cfiles {
		object := strings.Replace(f, string(filepath.Separator), "_", -1) + ".o"
		object = filepath.Join(".qoobj", object)
		e = &Executor{
			Name:	"Compiled " + f,
			Line:		[]string{toolchain.CC, f, "-c", "-o", object},
		}
		e.Line = append(e.Line, toolchain.CFLAGS...)
		stage2 = append(stage2, e)
		objects = append(objects, object)
	}
	sort.Sort(stage2)
	sort.Strings(objects)
	script = append(script, stage2)
	nStages += len(stage2)

	// 3) link
	target := targetName()
	e = &Executor{
		Name:	"Linked " + target,
		Line:		make([]string, 0, len(objects) + len(toolchain.LDFLAGS) + 10),
	}
	e.Line = append(e.Line, "gcc", "-o", target)
	e.Line = append(e.Line, objects...)
	e.Line = append(e.Line, toolchain.LDFLAGS...)
	script = append(script, Stage{e})
	nStages++
}
<commit_msg>"Added" C++ support... but now the linker has to change -_-<commit_after>// 24 september 2014

package main

import (
	"path/filepath"
	"strings"
	"sort"
)

type Stage []*Executor

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

func (s Stage) Less(i, j int) bool {
	return s[i].Line[1] < s[j].Line[1]
}

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

var script []Stage
var nStages int

func makeCompileStep(f string, cc string, flags []string) (*Executor, string) {
	object := strings.Replace(f, string(filepath.Separator), "_", -1) + ".o"
	object = filepath.Join(".qoobj", object)
	e := &Executor{
		Name:	"Compiled " + f,
		Line:		[]string{cc, f, "-c", "-o", object},
	}
	e.Line = append(e.Line, flags...)
	return e, object
}

func buildScript() {
	script = nil
	nStages = 0

	// stage 1: make the object file directory
	e := &Executor{
		Name:	"Made working directory",
		Line:		[]string{"mkdir", "-p", ".qoobj"},
	}
	script = append(script, Stage{e})
	nStages++

	// stage 2: compile everything
	stage2 := Stage(nil)
	objects := []string(nil)
	for _, f := range cfiles {
		e, object := makeCompileStep(f, toolchain.CC, toolchain.CFLAGS)
		stage2 = append(stage2, e)
		objects = append(objects, object)
	}
	for _, f := range cppfiles {
		e, object := makeCompileStep(f, toolchain.CPP, toolchain.CPPFLAGS)
		stage2 = append(stage2, e)
		objects = append(objects, object)
	}
	sort.Sort(stage2)
	sort.Strings(objects)
	script = append(script, stage2)
	nStages += len(stage2)

	// 3) link
	target := targetName()
	e = &Executor{
		Name:	"Linked " + target,
		Line:		make([]string, 0, len(objects) + len(toolchain.LDFLAGS) + 10),
	}
	e.Line = append(e.Line, "gcc", "-o", target)
	e.Line = append(e.Line, objects...)
	e.Line = append(e.Line, toolchain.LDFLAGS...)
	script = append(script, Stage{e})
	nStages++
}
<|endoftext|>


<commit_before>package base58

import (
	"crypto/rand"
	"encoding/hex"
	"testing"
)

type testValues struct {
	dec, enc string // decoded hex value
}

var n = 5000000
var testPairs = make([]testValues, 0, n)

func initTestPairs() {
	if len(testPairs) > 0 {
		return
	}
	// pre-make the test pairs, so it doesn't take up benchmark time...
	data := make([]byte, 32)
	for i := 0; i < n; i++ {
		rand.Read(data)
		testPairs = append(testPairs, testValues{dec: hex.EncodeToString(data), enc: FastBase58Encoding(data)})
	}
}

func TestFastEqTrivialEncodingAndDecoding(t *testing.T) {
	for j := 1; j < 256; j++ {
		var b = make([]byte, j)
		for i := 0; i < 100; i++ {
			rand.Read(b)
			fe := FastBase58Encoding(b)
			te := TrivialBase58Encoding(b)

			if fe != te {
				t.Errorf("encoding err: %#v", hex.EncodeToString(b))
			}

			fd, ferr := FastBase58Decoding(fe)
			if ferr != nil {
				t.Errorf("fast error: %v", ferr)
			}
			td, terr := TrivialBase58Decoding(te)
			if terr != nil {
				t.Errorf("trivial error: %v", terr)
			}

			if hex.EncodeToString(b) != hex.EncodeToString(td) {
				t.Errorf("decoding err: %s != %s", hex.EncodeToString(b), hex.EncodeToString(td))
			}
			if hex.EncodeToString(b) != hex.EncodeToString(fd) {
				t.Errorf("decoding err: %s != %s", hex.EncodeToString(b), hex.EncodeToString(fd))
			}
		}
	}
}

func BenchmarkTrivialBase58Encoding(b *testing.B) {
	data := make([]byte, 32)
	for i := 0; i < b.N; i++ {
		rand.Read(data)
		TrivialBase58Encoding(data)
	}
}

func BenchmarkFastBase58Encoding(b *testing.B) {
	data := make([]byte, 32)
	for i := 0; i < b.N; i++ {
		rand.Read(data)
		FastBase58Encoding(data)
	}
}

func BenchmarkTrivialBase58Decoding(b *testing.B) {
	initTestPairs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		TrivialBase58Decoding(testPairs[i].enc)
	}
}

func BenchmarkFastBase58Decoding(b *testing.B) {
	initTestPairs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		FastBase58Decoding(testPairs[i].enc)
	}
}
<commit_msg>test custom alphabets<commit_after>package base58

import (
	"crypto/rand"
	"encoding/hex"
	"testing"
)

type testValues struct {
	dec, enc string // decoded hex value
}

var n = 5000000
var testPairs = make([]testValues, 0, n)

func initTestPairs() {
	if len(testPairs) > 0 {
		return
	}
	// pre-make the test pairs, so it doesn't take up benchmark time...
	data := make([]byte, 32)
	for i := 0; i < n; i++ {
		rand.Read(data)
		testPairs = append(testPairs, testValues{dec: hex.EncodeToString(data), enc: FastBase58Encoding(data)})
	}
}

func randAlphabet() *Alphabet {
	// Permutes [0, 127] and returns the first 58 elements.
	// Like (math/rand).Perm but using crypto/rand.
	var randomness [128]byte
	rand.Read(randomness[:])

	var bts [128]byte
	for i, r := range randomness {
		j := int(r) % (i + 1)
		bts[i] = bts[j]
		bts[j] = byte(i)
	}
	return NewAlphabet(string(bts[:58]))
}

func TestFastEqTrivialEncodingAndDecoding(t *testing.T) {
	for k := 0; k < 10; k++ {
		testEncDecLoop(t, randAlphabet())
	}
	testEncDecLoop(t, BTCAlphabet)
	testEncDecLoop(t, FlickrAlphabet)
}

func testEncDecLoop(t *testing.T, alph *Alphabet) {
	for j := 1; j < 256; j++ {
		var b = make([]byte, j)
		for i := 0; i < 100; i++ {
			rand.Read(b)
			fe := FastBase58EncodingAlphabet(b, alph)
			te := TrivialBase58EncodingAlphabet(b, alph)

			if fe != te {
				t.Errorf("encoding err: %#v", hex.EncodeToString(b))
			}

			fd, ferr := FastBase58DecodingAlphabet(fe, alph)
			if ferr != nil {
				t.Errorf("fast error: %v", ferr)
			}
			td, terr := TrivialBase58DecodingAlphabet(te, alph)
			if terr != nil {
				t.Errorf("trivial error: %v", terr)
			}

			if hex.EncodeToString(b) != hex.EncodeToString(td) {
				t.Errorf("decoding err: %s != %s", hex.EncodeToString(b), hex.EncodeToString(td))
			}
			if hex.EncodeToString(b) != hex.EncodeToString(fd) {
				t.Errorf("decoding err: %s != %s", hex.EncodeToString(b), hex.EncodeToString(fd))
			}
		}
	}
}

func BenchmarkTrivialBase58Encoding(b *testing.B) {
	data := make([]byte, 32)
	for i := 0; i < b.N; i++ {
		rand.Read(data)
		TrivialBase58Encoding(data)
	}
}

func BenchmarkFastBase58Encoding(b *testing.B) {
	data := make([]byte, 32)
	for i := 0; i < b.N; i++ {
		rand.Read(data)
		FastBase58Encoding(data)
	}
}

func BenchmarkTrivialBase58Decoding(b *testing.B) {
	initTestPairs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		TrivialBase58Decoding(testPairs[i].enc)
	}
}

func BenchmarkFastBase58Decoding(b *testing.B) {
	initTestPairs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		FastBase58Decoding(testPairs[i].enc)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package base58

import (
	"crypto/sha256"
	"errors"
)

// ErrChecksum indicates that the checksum of a check-encoded string does not verify against
// the checksum.
var ErrChecksum = errors.New("checksum error")

// ErrInvalidFormat indicates that the check-encoded string has an invalid format.
var ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing")

// checksum: first four bytes of sha256^2
func checksum(input []byte) (cksum [4]byte) {
	h := sha256.Sum256(input)
	h2 := sha256.Sum256(h[:])
	copy(cksum[:], h2[:4])
	return
}

// CheckEncode prepends a version byte and appends a four byte checksum.
// func CheckEncode(input []byte, version byte) string {
// func CheckEncode(input []byte) string {
func CheckEncode(input []byte, version byte) string {
	var b1, b2 byte

	b1 = 0x5F
	b2 = 0xB1

	b := make([]byte, 0, 2+len(input)+4)
	b = append(b, b1)
	b = append(b, b2)
	b = append(b, input[:]...)
	cksum := checksum(b)

	b = append(b, cksum[:]...)
	return Encode(b)
}

/*
// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum.
func CheckDecode(input string) (result []byte, version byte, err error) {
	decoded := Decode(input)
	if len(decoded) < 5 {
		return nil, 0, ErrInvalidFormat
	}
	version = decoded[0]
	var cksum [4]byte
	copy(cksum[:], decoded[len(decoded)-4:])
	if checksum(decoded[:len(decoded)-4]) != cksum {
		return nil, 0, ErrChecksum
	}
	payload := decoded[1 : len(decoded)-4]
	result = append(result, payload...)
	return
}
*/

// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum.
func CheckDecode(input string) (result []byte, v1 byte, v2 byte, err error) {
	decoded := Decode(input)
	if len(decoded) < 6 {
		return nil, 0, 0, ErrInvalidFormat
	}

	v1 = decoded[0]
	v2 = decoded[1]

	var cksum [4]byte
	copy(cksum[:], decoded[len(decoded)-4:])
	if checksum(decoded[:len(decoded)-4]) != cksum {
		return nil, 0, 0, ErrChecksum
	}
	payload := decoded[2 : len(decoded)-4]
	result = append(result, payload...)
	return
}
<commit_msg>make checkDecode consistant with btcsuite/btcutil<commit_after>// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package base58

import (
	"crypto/sha256"
	"errors"
)

// ErrChecksum indicates that the checksum of a check-encoded string does not verify against
// the checksum.
var ErrChecksum = errors.New("checksum error")

// ErrInvalidFormat indicates that the check-encoded string has an invalid format.
var ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing")

// checksum: first four bytes of sha256^2
func checksum(input []byte) (cksum [4]byte) {
	h := sha256.Sum256(input)
	h2 := sha256.Sum256(h[:])
	copy(cksum[:], h2[:4])
	return
}

// CheckEncode prepends a version byte and appends a four byte checksum.
// func CheckEncode(input []byte, version byte) string {
// func CheckEncode(input []byte) string {
func CheckEncode(input []byte, version byte) string {
	var b1, b2 byte

	b1 = 0x5F
	b2 = 0xB1

	b := make([]byte, 0, 2+len(input)+4)
	b = append(b, b1)
	b = append(b, b2)
	b = append(b, input[:]...)
	cksum := checksum(b)

	b = append(b, cksum[:]...)
	return Encode(b)
}

// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum.
func CheckDecode(input string) (result []byte, version byte, err error) {
	decoded := Decode(input)
	if len(decoded) < 5 {
		return nil, 0, ErrInvalidFormat
	}
	version = decoded[0]
	var cksum [4]byte
	copy(cksum[:], decoded[len(decoded)-4:])
	if checksum(decoded[:len(decoded)-4]) != cksum {
		return nil, 0, ErrChecksum
	}
	payload := decoded[1 : len(decoded)-4]
	result = append(result, payload...)
	return
}

/*
// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum.
func CheckDecode(input string) (result []byte, v1 byte, v2 byte, err error) {
	decoded := Decode(input)
	if len(decoded) < 6 {
		return nil, 0, 0, ErrInvalidFormat
	}

	v1 = decoded[0]
	v2 = decoded[1]

	var cksum [4]byte
	copy(cksum[:], decoded[len(decoded)-4:])
	if checksum(decoded[:len(decoded)-4]) != cksum {
		return nil, 0, 0, ErrChecksum
	}
	payload := decoded[2 : len(decoded)-4]
	result = append(result, payload...)
	return
}
*/
<|endoftext|>


<commit_before>package webtail

import (
	"encoding/json"
	"sort"

	"github.com/LeKovr/webtail/worker"
)

type messageIn struct {
	Type    string `json:"type"`
	Channel string `json:"channel,omitempty"`
}
type messageOut struct {
	Type    string `json:"type"`
	Channel string `json:"channel,omitempty"`
	Data    string `json:"data,omitempty"`
}
type messageStats struct {
	Type string            `json:"type"`
	Data map[string]uint64 `json:"data,omitempty"`
}
type messageIndex struct {
	Type  string       `json:"type"`
	Data  worker.Index `json:"data"`
	Error string       `json:"error,omitempty"`
}

func (h *Hub) fromClient(msg *Message) {
	var data []byte
	in := messageIn{}
	err := json.Unmarshal(msg.Message, &in)
	if err != nil {
		data, _ = json.Marshal(messageOut{Type: "error", Data: "parse error"})
		h.send(msg.Client, data)
		return
	}
	h.log.Printf("debug: Received from Client: (%+v)", in)
	switch in.Type {
	case "attach":
		data = h.attach(in.Channel, msg.Client)
	case "detach":
		// проверить, что клиент подписан
		if !h.wh.WorkerExists(in.Channel) {
			// unknown producer
			data, _ = json.Marshal(messageOut{Type: "error", Data: "unknown channel", Channel: in.Channel})
		} else if _, ok := h.subscribers[in.Channel][msg.Client]; !ok {
			// no subscriber
			data, _ = json.Marshal(messageOut{Type: "error", Data: "not subscribed", Channel: in.Channel})
		} else {
			// удалить подписку
			data, _ = json.Marshal(messageOut{Type: "detach", Channel: in.Channel})

			delete(h.subscribers[in.Channel], msg.Client)
			h.stats[in.Channel]--
			if h.stats[in.Channel] == 0 {
				// если подписчиков не осталось - отправить true в unregister продюсера и  удалить из массива
				err = h.wh.WorkerStop(in.Channel)
				if err != nil {
					h.log.Printf("warn: worker stop error: %+v", err)
				}
			}
		}

	case "stats":
		// вернуть массив счетчиков подписок на каналы
		data, _ = json.Marshal(messageStats{Type: "stats", Data: h.stats})

	case "trace":
		// включить/выключить трассировку
		h.wh.SetTrace(in.Channel == "on")
	}
	if len(data) > 0 {
		h.send(msg.Client, data)
	}
}

// process message from worker
func (h *Hub) fromWorker(msg *worker.Message) bool {

	if h.wh.TraceEnabled() {
		h.log.Printf("debug: Trace from Worker: (%+v)", msg)
	}

	data, _ := json.Marshal(messageOut{Type: "log", Data: msg.Data})

	if !h.wh.Append(msg.Channel, data) {
		return true
	}

	clients := h.subscribers[msg.Channel]

	for client := range clients {
		h.send(client, data)
	}
	return true
}

// process message from worker
func (h *Hub) fromIndexer(msg *worker.Index) {

	if h.wh.TraceEnabled() {
		h.log.Printf("debug: Trace from Indexer: (%+v)", msg)
	}

	data, _ := json.Marshal(messageIndex{Type: "index", Data: *msg})

	h.wh.Update(msg)

	clients := h.subscribers[""]

	for client := range clients {
		h.send(client, data)
	}
}

func (h *Hub) attach(channel string, client *Client) (data []byte) {
	var err error
	if !h.wh.ChannelExists(channel) {
		// проверить что путь зарегистрирован
		data, _ = json.Marshal(messageOut{Type: "error", Data: "unknown channel", Channel: channel})
		return
	} else if !h.wh.WorkerExists(channel) {
		// если нет продюсера - создать горутину
		if channel == "" {
			err = h.wh.IndexRun(h.index)
		} else {
			err = h.wh.WorkerRun(channel, h.receive)
		}
		if err != nil {
			h.log.Printf("warn: worker create error: %+v", err)
			data, _ = json.Marshal(messageOut{Type: "error", Data: "worker create error"})
			return
		}
		h.subscribers[channel] = make(subscribers)
	} else if _, ok := h.subscribers[channel][client]; ok {
		// клиент уже подписан - ответить "уже подписан" и выйти
		data, _ = json.Marshal(messageOut{Type: "error", Data: "attached already", Channel: channel})
		return
	}
	// Confirm attach
	// not via data because have to be first in response
	datac, _ := json.Marshal(messageOut{Type: "attach", Channel: channel})
	if !h.send(client, datac) {
		return
	}
	ok := h.sendReply(channel, client)
	if ok {
		// добавить клиента в подписчики
		h.subscribers[channel][client] = true
		h.stats[channel]++
	}
	return
}

func (h *Hub) sendReply(ch string, cl *Client) bool {

	if ch == "" {
		// отправить клиенту список каналов
		istore := h.wh.Index()
		// To store the keys in slice in sorted order
		var keys []string
		for k := range *istore {
			keys = append(keys, k)
		}
		sort.Strings(keys)

		for _, v := range keys {
			idx := &messageIndex{
				Type: "index",
				Data: worker.Index{
					Name:    v,
					ModTime: (*istore)[v].ModTime,
					Size:    (*istore)[v].Size,
				},
			}
			data, _ := json.Marshal(idx)
			if !h.send(cl, data) {
				return false
			}
		}

	} else {
		// отправить клиенту текущий буфер
		for _, item := range h.wh.Buffer(ch) {
			if !h.send(cl, item) {
				return false
			}
		}
	}
	return true
}

func (h *Hub) send(client *Client, data []byte) bool {

	h.log.Printf("debug: Send reply: %v", string(data))
	select {
	case client.send <- data:
	default:
		h.remove(client)
		return false
	}
	return true
}

// remove all client subscriptions
func (h *Hub) remove(client *Client) {
	for k := range h.subscribers {
		if _, ok := h.subscribers[k][client]; ok {
			h.log.Printf("debug: Remove subscriber from channel (%s)", k)

			delete(h.subscribers[k], client)
			h.stats[k]--
			if h.stats[k] == 0 {
				// если подписчиков не осталось - отправить true в unregister продюсера
				err := h.wh.WorkerStop(k)
				if err != nil {
					h.log.Printf("warn: worker stop error: %+v", err)
				}
			}
		}
	}
	close(client.send)
	delete(h.clients, client)

}
<commit_msg>refactoring1<commit_after>package webtail

import (
	"encoding/json"
	"sort"

	"github.com/LeKovr/webtail/worker"
)

type messageIn struct {
	Type    string `json:"type"`
	Channel string `json:"channel,omitempty"`
}
type messageOut struct {
	Type    string `json:"type"`
	Channel string `json:"channel,omitempty"`
	Data    string `json:"data,omitempty"`
}
type messageStats struct {
	Type string            `json:"type"`
	Data map[string]uint64 `json:"data,omitempty"`
}
type messageIndex struct {
	Type  string       `json:"type"`
	Data  worker.Index `json:"data"`
	Error string       `json:"error,omitempty"`
}

func (h *Hub) fromClient(msg *Message) {
	var data []byte
	in := messageIn{}
	err := json.Unmarshal(msg.Message, &in)
	if err != nil {
		data, _ = json.Marshal(messageOut{Type: "error", Data: "parse error"})
		h.send(msg.Client, data)
		return
	}
	h.log.Printf("debug: Received from Client: (%+v)", in)
	switch in.Type {
	case "attach":
		data = h.attach(in.Channel, msg.Client)
	case "detach":
		// проверить, что клиент подписан
		if !h.wh.WorkerExists(in.Channel) {
			// unknown producer
			data, _ = json.Marshal(messageOut{Type: "error", Data: "unknown channel", Channel: in.Channel})
		} else if _, ok := h.subscribers[in.Channel][msg.Client]; !ok {
			// no subscriber
			data, _ = json.Marshal(messageOut{Type: "error", Data: "not subscribed", Channel: in.Channel})
		} else {
			// удалить подписку
			data, _ = json.Marshal(messageOut{Type: "detach", Channel: in.Channel})
			h.unsubscribe(in.Channel, msg.Client)
		}

	case "stats":
		// вернуть массив счетчиков подписок на каналы
		data, _ = json.Marshal(messageStats{Type: "stats", Data: h.stats})

	case "trace":
		// включить/выключить трассировку
		h.wh.SetTrace(in.Channel == "on")
	}
	if len(data) > 0 {
		h.send(msg.Client, data)
	}
}

// process message from worker
func (h *Hub) fromWorker(msg *worker.Message) bool {

	if h.wh.TraceEnabled() {
		h.log.Printf("debug: Trace from Worker: (%+v)", msg)
	}

	data, _ := json.Marshal(messageOut{Type: "log", Data: msg.Data})

	if !h.wh.Append(msg.Channel, data) {
		return true
	}

	clients := h.subscribers[msg.Channel]

	for client := range clients {
		h.send(client, data)
	}
	return true
}

// process message from worker
func (h *Hub) fromIndexer(msg *worker.Index) {

	if h.wh.TraceEnabled() {
		h.log.Printf("debug: Trace from Indexer: (%+v)", msg)
	}

	data, _ := json.Marshal(messageIndex{Type: "index", Data: *msg})

	h.wh.Update(msg)

	clients := h.subscribers[""]

	for client := range clients {
		h.send(client, data)
	}
}

func (h *Hub) attach(channel string, client *Client) (data []byte) {
	var err error
	if !h.wh.ChannelExists(channel) {
		// проверить что путь зарегистрирован
		data, _ = json.Marshal(messageOut{Type: "error", Data: "unknown channel", Channel: channel})
		return
	} else if !h.wh.WorkerExists(channel) {
		// если нет продюсера - создать горутину
		if channel == "" {
			err = h.wh.IndexRun(h.index)
		} else {
			err = h.wh.WorkerRun(channel, h.receive)
		}
		if err != nil {
			h.log.Printf("warn: worker create error: %+v", err)
			data, _ = json.Marshal(messageOut{Type: "error", Data: "worker create error"})
			return
		}
		h.subscribers[channel] = make(subscribers)
	} else if _, ok := h.subscribers[channel][client]; ok {
		// клиент уже подписан - ответить "уже подписан" и выйти
		data, _ = json.Marshal(messageOut{Type: "error", Data: "attached already", Channel: channel})
		return
	}
	// Confirm attach
	// not via data because have to be first in response
	datac, _ := json.Marshal(messageOut{Type: "attach", Channel: channel})
	if !h.send(client, datac) {
		return
	}
	ok := h.sendReply(channel, client)
	if ok {
		// добавить клиента в подписчики
		h.subscribers[channel][client] = true
		h.stats[channel]++
	}
	return
}

func (h *Hub) sendReply(ch string, cl *Client) bool {

	if ch == "" {
		// отправить клиенту список каналов
		istore := h.wh.Index()
		// To store the keys in slice in sorted order
		var keys []string
		for k := range *istore {
			keys = append(keys, k)
		}
		sort.Strings(keys)

		for _, v := range keys {
			idx := &messageIndex{
				Type: "index",
				Data: worker.Index{
					Name:    v,
					ModTime: (*istore)[v].ModTime,
					Size:    (*istore)[v].Size,
				},
			}
			data, _ := json.Marshal(idx)
			if !h.send(cl, data) {
				return false
			}
		}

	} else {
		// отправить клиенту текущий буфер
		for _, item := range h.wh.Buffer(ch) {
			if !h.send(cl, item) {
				return false
			}
		}
	}
	return true
}

func (h *Hub) send(client *Client, data []byte) bool {

	h.log.Printf("debug: Send reply: %v", string(data))
	select {
	case client.send <- data:
	default:
		h.remove(client)
		return false
	}
	return true
}

// remove all client subscriptions
func (h *Hub) remove(client *Client) {
	for k := range h.subscribers {
		if _, ok := h.subscribers[k][client]; ok {
			h.log.Printf("debug: Remove subscriber from channel (%s)", k)
			h.unsubscribe(k, client)
		}
	}
	close(client.send)
	delete(h.clients, client)

}

func (h *Hub) unsubscribe(k string, client *Client) {
	delete(h.subscribers[k], client)
	h.stats[k]--
	if h.stats[k] == 0 {
		// если подписчиков не осталось - отправить true в unregister продюсера
		err := h.wh.WorkerStop(k)
		if err != nil {
			h.log.Printf("warn: worker stop error: %+v", err)
		}
	}
}
<|endoftext|>


<commit_before>// Package ui is the user interface of the editor.
package ui

import (
	"image"
	"image/draw"
	"strings"
	"sync"

	"github.com/eaburns/T/edit"
	"github.com/eaburns/T/rope"
	"github.com/golang/freetype/truetype"
	"golang.org/x/image/font"
)

// A Win is a window of columns of sheets.
type Win struct {
	dpi        float32
	face       font.Face // default font face
	lineHeight int
	size       image.Point
	cols       []*Col
	widths     []float64 // frac of width
	resizing   int       // col index being resized or -1
	mods       [4]bool   // currently held modifier keys
	*Col                 // focus
	output     *Sheet

	mu           sync.Mutex
	outputBuffer strings.Builder
}

// NewWin returns a new window.
// TODO: NewWin is just a temporary implementation.
func NewWin(dpi float32) *Win {
	face := truetype.NewFace(defaultFont, &truetype.Options{
		Size: float64(defaultFontSize),
		DPI:  float64(dpi * (72.0 / 96.0)),
	})
	h := (face.Metrics().Height + face.Metrics().Descent).Ceil()
	w := &Win{
		dpi:        dpi,
		face:       face,
		lineHeight: h,
		resizing:   -1,
	}
	w.cols = []*Col{NewCol(w)}
	w.widths = []float64{1.0}
	w.Col = w.cols[0]
	w.output = NewSheet(w, "Output")
	return w
}

// Add adds a new column to the window and returns it.
func (w *Win) Add() *Col {
	col := NewCol(w)
	f := 0.5
	if n := len(w.widths); n > 1 {
		f = (w.widths[n-2] + w.widths[n-1]) / 2.0
	}
	w.cols = append(w.cols, col)
	w.widths[len(w.widths)-1] = f
	w.widths = append(w.widths, 1.0)
	w.Resize(w.size)
	setWinFocus(w, col)
	return col
}

// Del deletes a column unless it is the last column.
func (w *Win) Del(c *Col) {
	if len(w.cols) == 1 {
		return
	}
	i := colIndex(c)
	if i < 0 {
		return
	}
	w.cols = append(w.cols[:i], w.cols[i+1:]...)
	w.widths = append(w.widths[:i], w.widths[i+1:]...)
	w.widths[len(w.cols)-1] = 1.0
	if w.Col == c {
		if i == 0 {
			setWinFocus(w, w.cols[0])
		} else {
			setWinFocus(w, w.cols[i-1])
		}
	}
	w.Resize(w.size)
}

// Tick handles tick events.
func (w *Win) Tick() bool {
	var redraw bool
	if showOutput(w) {
		redraw = true
	}
	for _, c := range w.cols {
		if c.Tick() {
			redraw = true
		}
	}
	return redraw
}

func showOutput(w *Win) bool {
	w.mu.Lock()
	output := w.outputBuffer.String()
	w.outputBuffer.Reset()
	w.mu.Unlock()

	if len(output) == 0 {
		return false
	}

	b := w.output.body
	b.Change(edit.Diffs{{
		At:   [2]int64{b.text.Len(), b.text.Len()},
		Text: rope.New(output),
	}})
	w.outputBuffer.Reset()
	for _, c := range w.cols {
		for _, r := range c.rows {
			if r == w.output {
				return false
			}
		}
	}
	w.cols[len(w.cols)-1].Add(w.output)
	return true
}

// Draw draws the window.
func (w *Win) Draw(dirty bool, drawImg draw.Image) {
	img := drawImg.(*image.RGBA)
	if w.size != img.Bounds().Size() {
		w.Resize(img.Bounds().Size())
	}
	for i, c := range w.cols {
		r := img.Bounds()
		r.Min.X = img.Bounds().Min.X + x0(w, i)
		r.Max.X = img.Bounds().Min.X + x1(w, i)
		c.Draw(dirty, img.SubImage(r).(*image.RGBA))
		if i < len(w.cols)-1 {
			r.Min.X = r.Max.X
			r.Max.X += framePx
			fillRect(img, frameBG, r)
		}
	}
}

// Resize handles resize events.
func (w *Win) Resize(size image.Point) {
	w.size = size

	if nc := len(w.cols); int(dx(w)) < nc*w.lineHeight+nc*framePx {
		// Too small to fit everything.
		// Space out as much as we can,
		// so if the window grows,
		// everything is in a good place.
		w.widths[0] = 0.0
		for i := 1; i < nc; i++ {
			w.widths[i] = w.widths[i-1] + 1.0/float64(nc)
		}
	}
	w.widths[len(w.widths)-1] = 1.0

	for i, c := range w.cols {
		c.Resize(image.Pt(x1(w, i)-x0(w, i), w.size.Y))
	}
}

// Move handles mouse move events.
func (w *Win) Move(pt image.Point) {
	if w.resizing >= 0 {
		// Center the pointer horizontally on the handle.
		x := pt.X + w.cols[w.resizing].HandleBounds().Dx()/2
		resizeCol(w, x)
		return
	}

	pt.X -= x0(w, focusedCol(w))
	w.Col.Move(pt)
}

func resizeCol(w *Win, x int) {
	dx := dx(w)
	newFrac := float64(x) / dx

	// Don't resize if either resized col would get too small.
	newX := int(newFrac * dx)
	var prev int
	if w.resizing > 0 {
		prev = x0(w, w.resizing)
	}
	if newX-prev-framePx < w.lineHeight {
		newFrac = float64(prev+w.lineHeight) / dx
	}
	next := x1(w, w.resizing+1)
	if next-newX-framePx < w.lineHeight {
		newFrac = float64(next-w.lineHeight-framePx) / dx
	}

	if w.widths[w.resizing] != newFrac {
		w.widths[w.resizing] = newFrac
		w.Resize(w.size)
	}
}

// Wheel handles mouse wheel events.
func (w *Win) Wheel(pt image.Point, x, y int) {
	for i, c := range w.cols {
		if pt.X < x1(w, i) {
			pt.X -= x0(w, i)
			c.Wheel(pt, x, y)
			return
		}
	}
}

// Click handles click events.
func (w *Win) Click(pt image.Point, button int) {
	if w.resizing >= 0 && button == -1 {
		w.resizing = -1
		return
	}
	if button == 1 {
		for i, c := range w.cols[:len(w.cols)-1] {
			handle := c.HandleBounds().Add(image.Pt(x0(w, i), 0))
			if pt.In(handle) {
				if w.Col != c {
					w.Col.Focus(false)
					c.Focus(true)
					w.Col = c
				}
				w.resizing = i
				return
			}
		}
	}

	if button > 0 {
		setWinFocusPt(w, pt)
	}
	pt.X -= x0(w, focusedCol(w))
	w.Col.Click(pt, button)
}

func setWinFocusPt(w *Win, pt image.Point) {
	for i, c := range w.cols {
		if pt.X < x1(w, i) {
			setWinFocus(w, c)
			break
		}
	}
}

// Focus handles focus change events.
func (w *Win) Focus(focus bool) {
	if !focus {
		w.mods = [4]bool{}
	}
	w.Col.Focus(focus)
}

// Mod handles modifier key state change events.
func (w *Win) Mod(m int) {
	switch {
	case m > 0 && m < len(w.mods):
		w.mods[m] = true
	case m < 0 && -m < len(w.mods):
		w.mods[-m] = false
	}
	w.Col.Mod(m)
}

// OutputString appends a string to the Output sheet
// and ensures that the Output sheet is visible.
// It is safe for concurrent calls.
func (w *Win) OutputString(str string) {
	w.mu.Lock()
	w.outputBuffer.WriteString(str)
	w.mu.Unlock()
}

// OutputBytes appends bytes to the Output sheet
// and ensures that the Output sheet is visible.
// It is safe for concurrent calls.
func (w *Win) OutputBytes(data []byte) {
	w.mu.Lock()
	w.outputBuffer.Write(data)
	w.mu.Unlock()
}

func x0(w *Win, i int) int {
	if i == 0 {
		return 0
	}
	return x1(w, i-1) + framePx
}

func x1(w *Win, i int) int { return int(w.widths[i] * dx(w)) }

func dx(w *Win) float64 { return float64(w.size.X) }

func setWinFocus(w *Win, c *Col) {
	if w.Col == c {
		return
	}
	w.Col.Focus(false)
	c.Focus(true)
	w.Col = c
}

func focusedCol(w *Win) int {
	i := colIndex(w.Col)
	if i < 0 {
		return 0
	}
	return i
}
<commit_msg>ui: scroll the Output sheet to show newly appended text<commit_after>// Package ui is the user interface of the editor.
package ui

import (
	"image"
	"image/draw"
	"strings"
	"sync"

	"github.com/eaburns/T/edit"
	"github.com/eaburns/T/rope"
	"github.com/golang/freetype/truetype"
	"golang.org/x/image/font"
)

// A Win is a window of columns of sheets.
type Win struct {
	dpi        float32
	face       font.Face // default font face
	lineHeight int
	size       image.Point
	cols       []*Col
	widths     []float64 // frac of width
	resizing   int       // col index being resized or -1
	mods       [4]bool   // currently held modifier keys
	*Col                 // focus
	output     *Sheet

	mu           sync.Mutex
	outputBuffer strings.Builder
}

// NewWin returns a new window.
// TODO: NewWin is just a temporary implementation.
func NewWin(dpi float32) *Win {
	face := truetype.NewFace(defaultFont, &truetype.Options{
		Size: float64(defaultFontSize),
		DPI:  float64(dpi * (72.0 / 96.0)),
	})
	h := (face.Metrics().Height + face.Metrics().Descent).Ceil()
	w := &Win{
		dpi:        dpi,
		face:       face,
		lineHeight: h,
		resizing:   -1,
	}
	w.cols = []*Col{NewCol(w)}
	w.widths = []float64{1.0}
	w.Col = w.cols[0]
	w.output = NewSheet(w, "Output")
	return w
}

// Add adds a new column to the window and returns it.
func (w *Win) Add() *Col {
	col := NewCol(w)
	f := 0.5
	if n := len(w.widths); n > 1 {
		f = (w.widths[n-2] + w.widths[n-1]) / 2.0
	}
	w.cols = append(w.cols, col)
	w.widths[len(w.widths)-1] = f
	w.widths = append(w.widths, 1.0)
	w.Resize(w.size)
	setWinFocus(w, col)
	return col
}

// Del deletes a column unless it is the last column.
func (w *Win) Del(c *Col) {
	if len(w.cols) == 1 {
		return
	}
	i := colIndex(c)
	if i < 0 {
		return
	}
	w.cols = append(w.cols[:i], w.cols[i+1:]...)
	w.widths = append(w.widths[:i], w.widths[i+1:]...)
	w.widths[len(w.cols)-1] = 1.0
	if w.Col == c {
		if i == 0 {
			setWinFocus(w, w.cols[0])
		} else {
			setWinFocus(w, w.cols[i-1])
		}
	}
	w.Resize(w.size)
}

// Tick handles tick events.
func (w *Win) Tick() bool {
	var redraw bool
	if showOutput(w) {
		redraw = true
	}
	for _, c := range w.cols {
		if c.Tick() {
			redraw = true
		}
	}
	return redraw
}

func showOutput(w *Win) bool {
	w.mu.Lock()
	output := w.outputBuffer.String()
	w.outputBuffer.Reset()
	w.mu.Unlock()

	if len(output) == 0 {
		return false
	}

	b := w.output.body
	b.Change(edit.Diffs{{
		At:   [2]int64{b.text.Len(), b.text.Len()},
		Text: rope.New(output),
	}})
	setDot(b, 1, b.text.Len(), b.text.Len())
	// TODO: only showAddr on Output if it was visible to begin with.
	// If the user scrolls up, for example, we shouldn't scroll them back down.
	// This should probably just be the behavior of b.Change by default.
	showAddr(b, b.dots[1].At[1])

	w.outputBuffer.Reset()
	for _, c := range w.cols {
		for _, r := range c.rows {
			if r == w.output {
				return true
			}
		}
	}
	w.cols[len(w.cols)-1].Add(w.output)
	return true
}

// Draw draws the window.
func (w *Win) Draw(dirty bool, drawImg draw.Image) {
	img := drawImg.(*image.RGBA)
	if w.size != img.Bounds().Size() {
		w.Resize(img.Bounds().Size())
	}
	for i, c := range w.cols {
		r := img.Bounds()
		r.Min.X = img.Bounds().Min.X + x0(w, i)
		r.Max.X = img.Bounds().Min.X + x1(w, i)
		c.Draw(dirty, img.SubImage(r).(*image.RGBA))
		if i < len(w.cols)-1 {
			r.Min.X = r.Max.X
			r.Max.X += framePx
			fillRect(img, frameBG, r)
		}
	}
}

// Resize handles resize events.
func (w *Win) Resize(size image.Point) {
	w.size = size

	if nc := len(w.cols); int(dx(w)) < nc*w.lineHeight+nc*framePx {
		// Too small to fit everything.
		// Space out as much as we can,
		// so if the window grows,
		// everything is in a good place.
		w.widths[0] = 0.0
		for i := 1; i < nc; i++ {
			w.widths[i] = w.widths[i-1] + 1.0/float64(nc)
		}
	}
	w.widths[len(w.widths)-1] = 1.0

	for i, c := range w.cols {
		c.Resize(image.Pt(x1(w, i)-x0(w, i), w.size.Y))
	}
}

// Move handles mouse move events.
func (w *Win) Move(pt image.Point) {
	if w.resizing >= 0 {
		// Center the pointer horizontally on the handle.
		x := pt.X + w.cols[w.resizing].HandleBounds().Dx()/2
		resizeCol(w, x)
		return
	}

	pt.X -= x0(w, focusedCol(w))
	w.Col.Move(pt)
}

func resizeCol(w *Win, x int) {
	dx := dx(w)
	newFrac := float64(x) / dx

	// Don't resize if either resized col would get too small.
	newX := int(newFrac * dx)
	var prev int
	if w.resizing > 0 {
		prev = x0(w, w.resizing)
	}
	if newX-prev-framePx < w.lineHeight {
		newFrac = float64(prev+w.lineHeight) / dx
	}
	next := x1(w, w.resizing+1)
	if next-newX-framePx < w.lineHeight {
		newFrac = float64(next-w.lineHeight-framePx) / dx
	}

	if w.widths[w.resizing] != newFrac {
		w.widths[w.resizing] = newFrac
		w.Resize(w.size)
	}
}

// Wheel handles mouse wheel events.
func (w *Win) Wheel(pt image.Point, x, y int) {
	for i, c := range w.cols {
		if pt.X < x1(w, i) {
			pt.X -= x0(w, i)
			c.Wheel(pt, x, y)
			return
		}
	}
}

// Click handles click events.
func (w *Win) Click(pt image.Point, button int) {
	if w.resizing >= 0 && button == -1 {
		w.resizing = -1
		return
	}
	if button == 1 {
		for i, c := range w.cols[:len(w.cols)-1] {
			handle := c.HandleBounds().Add(image.Pt(x0(w, i), 0))
			if pt.In(handle) {
				if w.Col != c {
					w.Col.Focus(false)
					c.Focus(true)
					w.Col = c
				}
				w.resizing = i
				return
			}
		}
	}

	if button > 0 {
		setWinFocusPt(w, pt)
	}
	pt.X -= x0(w, focusedCol(w))
	w.Col.Click(pt, button)
}

func setWinFocusPt(w *Win, pt image.Point) {
	for i, c := range w.cols {
		if pt.X < x1(w, i) {
			setWinFocus(w, c)
			break
		}
	}
}

// Focus handles focus change events.
func (w *Win) Focus(focus bool) {
	if !focus {
		w.mods = [4]bool{}
	}
	w.Col.Focus(focus)
}

// Mod handles modifier key state change events.
func (w *Win) Mod(m int) {
	switch {
	case m > 0 && m < len(w.mods):
		w.mods[m] = true
	case m < 0 && -m < len(w.mods):
		w.mods[-m] = false
	}
	w.Col.Mod(m)
}

// OutputString appends a string to the Output sheet
// and ensures that the Output sheet is visible.
// It is safe for concurrent calls.
func (w *Win) OutputString(str string) {
	w.mu.Lock()
	w.outputBuffer.WriteString(str)
	w.mu.Unlock()
}

// OutputBytes appends bytes to the Output sheet
// and ensures that the Output sheet is visible.
// It is safe for concurrent calls.
func (w *Win) OutputBytes(data []byte) {
	w.mu.Lock()
	w.outputBuffer.Write(data)
	w.mu.Unlock()
}

func x0(w *Win, i int) int {
	if i == 0 {
		return 0
	}
	return x1(w, i-1) + framePx
}

func x1(w *Win, i int) int { return int(w.widths[i] * dx(w)) }

func dx(w *Win) float64 { return float64(w.size.X) }

func setWinFocus(w *Win, c *Col) {
	if w.Col == c {
		return
	}
	w.Col.Focus(false)
	c.Focus(true)
	w.Col = c
}

func focusedCol(w *Win) int {
	i := colIndex(w.Col)
	if i < 0 {
		return 0
	}
	return i
}
<|endoftext|>