<commit_before>package broker

import (
	"bytes"
	"context"
	"fmt"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/celrenheit/sandflake"
	"github.com/gogo/protobuf/proto"

	"github.com/celrenheit/sandglass-grpc/go/sgproto"
	"github.com/celrenheit/sandglass/storage"
	"github.com/celrenheit/sandglass/topic"
)

func (b *Broker) Acknowledge(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, offset sandflake.ID) (bool, error) {
	return b.mark(ctx, topicName, partitionName, consumerGroup, consumerName, offset, sgproto.MarkKind_Acknowledged)
}

func (b *Broker) AcknowledgeMessages(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, offsets []sandflake.ID) error {
	topic := b.getTopic(ConsumerOffsetTopicName)
	p := topic.ChoosePartitionForKey(partitionKey(topicName, partitionName, consumerGroup, consumerName))

	n := b.getPartitionLeader(ConsumerOffsetTopicName, p.Id)
	if n == nil {
		return ErrNoLeaderFound
	}

	if n.Name != b.Name() {
		change := &sgproto.MultiOffsetChangeRequest{
			Topic:         topicName,
			Partition:     partitionName,
			ConsumerGroup: consumerGroup,
			ConsumerName:  consumerName,
			Offsets:       offsets,
		}

		_, err := n.AcknowledgeMessages(ctx, change)

		if err != nil {
			return err
		}

		return nil
	}

	msgs := []*sgproto.Message{}
	for _, offset := range offsets {
		msgs = append(msgs, &sgproto.Message{
			Topic:         ConsumerOffsetTopicName,
			Partition:     p.Id,
			Offset:        offset,
			Key:           partitionKey(topicName, partitionName, consumerGroup, consumerName),
			ClusteringKey: generateClusterKey(offset, sgproto.MarkKind_Acknowledged),
			Value:         []byte{byte(sgproto.MarkKind_Acknowledged)},
		})
	}

	return b.PublishMessages(ctx, msgs)
}

func (b *Broker) Commit(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, offset sandflake.ID) (bool, error) {
	return b.mark(ctx, topicName, partitionName, consumerGroup, consumerName, offset, sgproto.MarkKind_Commited)
}

func (b *Broker) MarkConsumed(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, offset sandflake.ID) (bool, error) {
	return b.mark(ctx, topicName, partitionName, consumerGroup, consumerName, offset, sgproto.MarkKind_Consumed)
}

func (b *Broker) mark(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, offset sandflake.ID, kind sgproto.MarkKind) (bool, error) {
	topic := b.getTopic(ConsumerOffsetTopicName)
	p := topic.ChoosePartitionForKey(partitionKey(topicName, partitionName, consumerGroup, consumerName))

	n := b.getPartitionLeader(ConsumerOffsetTopicName, p.Id)
	if n == nil {
		return false, ErrNoLeaderFound
	}

	if n.Name != b.Name() {
		change := &sgproto.OffsetChangeRequest{
			Topic:         topicName,
			Partition:     partitionName,
			ConsumerGroup: consumerGroup,
			ConsumerName:  consumerName,
			Offset:        offset,
		}

		var (
			res *sgproto.OffsetChangeReply
			err error
		)
		switch kind {
		case sgproto.MarkKind_Acknowledged:
			res, err = n.Acknowledge(ctx, change)
		case sgproto.MarkKind_Commited:
			res, err = n.Commit(ctx, change)
		case sgproto.MarkKind_Consumed:
			res, err = n.MarkConsumed(ctx, change)
		}
		if err != nil {
			return false, err
		}

		return res.Success, nil
	}

	state := &sgproto.MarkState{
		Kind: kind,
	}

	value, err := proto.Marshal(state)
	if err != nil {
		return false, err
	}

	res, err := b.PublishMessage(ctx, &sgproto.Message{
		Topic:         ConsumerOffsetTopicName,
		Partition:     p.Id,
		Offset:        offset,
		Key:           partitionKey(topicName, partitionName, consumerGroup, consumerName),
		ClusteringKey: generateClusterKey(offset, kind),
		Value:         value,
	})
	return res != nil, err
}

func (b *Broker) LastOffset(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, kind sgproto.MarkKind) (sandflake.ID, error) {
	topic := b.getTopic(ConsumerOffsetTopicName)
	pk := partitionKey(topicName, partitionName, consumerGroup, consumerName)
	p := topic.ChoosePartitionForKey(pk)

	n := b.getPartitionLeader(ConsumerOffsetTopicName, p.Id)
	if n == nil {
		return sandflake.Nil, ErrNoLeaderFound
	}

	if n.Name != b.Name() {
		res, err := n.LastOffset(ctx, &sgproto.LastOffsetRequest{
			Topic:         topicName,
			Partition:     partitionName,
			ConsumerGroup: consumerGroup,
			ConsumerName:  consumerName,
			Kind:          kind,
		})
		if err != nil {
			return sandflake.Nil, err
		}

		return res.Offset, nil
	}

	lastKind := byte(kind)

	return b.last(p, pk, lastKind)
}

func (b *Broker) GetMarkStateMessage(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, offset sandflake.ID) (*sgproto.Message, error) {
	topic := b.getTopic(ConsumerOffsetTopicName)
	pk := partitionKey(topicName, partitionName, consumerGroup, consumerName)
	p := topic.ChoosePartitionForKey(pk)

	n := b.getPartitionLeader(ConsumerOffsetTopicName, p.Id)
	if n == nil {
		return nil, ErrNoLeaderFound
	}

	if n.Name != b.Name() {
		res, err := n.GetMarkStateMessage(ctx, &sgproto.OffsetChangeRequest{
			Topic:         topicName,
			Partition:     partitionName,
			ConsumerGroup: consumerGroup,
			ConsumerName:  consumerName,
			Offset:        offset,
		})
		if err != nil {
			return nil, err
		}

		return res, nil
	}

	key := generatePrefixConsumerOffsetKey(pk, offset)

	msg, err := p.GetMessage(sandflake.Nil, key, nil)
	if err != nil {
		return nil, err
	}

	if msg == nil {
		return nil, status.Error(codes.NotFound, "mark state not found")
	}

	return msg, nil
}

func (b *Broker) last(p *topic.Partition, pk []byte, kind byte) (sandflake.ID, error) {
	msg, err := p.GetMessage(sandflake.Nil, pk, []byte{kind})
	if err != nil {
		return sandflake.Nil, err
	}

	if msg == nil {
		return sandflake.Nil, nil
	}

	if len(msg.Value) == 0 {
		return sandflake.Nil, fmt.Errorf("LastCommitedOffset malformed value '%v'", msg.Value)
	}

	return msg.Offset, nil
}

func (b *Broker) isAcknoweldged(ctx context.Context, topicName, partition, consumerGroup string, offset sandflake.ID) (bool, error) {
	topic := b.getTopic(ConsumerOffsetTopicName)
	if topic == nil {
		return false, ErrTopicNotFound
	}
	pk := partitionKey(topicName, partition, consumerGroup, "NOT SET")
	p := topic.ChoosePartitionForKey(pk)
	clusterKey := generateClusterKey(offset, sgproto.MarkKind_Acknowledged)
	return b.hasKeyInPartition(ctx, ConsumerOffsetTopicName, p, pk, clusterKey)
}

func partitionKey(topicName, partitionName, consumerGroup, consumerName string) []byte {
	return bytes.Join([][]byte{
		[]byte("offsets"),
		[]byte(topicName),
		[]byte(partitionName),
		[]byte(consumerGroup),
		// []byte(consumerName),
	}, storage.Separator)
}

func generateClusterKey(offset sandflake.ID, kind sgproto.MarkKind) []byte {
	return bytes.Join([][]byte{
		offset.Bytes(),
		[]byte{byte(kind)},
	}, storage.Separator)
}
<commit_msg>fix multiple acks<commit_after>package broker

import (
	"bytes"
	"context"
	"fmt"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/celrenheit/sandflake"
	"github.com/gogo/protobuf/proto"

	"github.com/celrenheit/sandglass-grpc/go/sgproto"
	"github.com/celrenheit/sandglass/storage"
	"github.com/celrenheit/sandglass/topic"
)

func (b *Broker) Acknowledge(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, offset sandflake.ID) (bool, error) {
	return b.mark(ctx, topicName, partitionName, consumerGroup, consumerName, offset, sgproto.MarkKind_Acknowledged)
}

// FIXME: share same code between AcknowledgeMessages a AcknowledgeMessage
func (b *Broker) AcknowledgeMessages(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, offsets []sandflake.ID) error {
	topic := b.getTopic(ConsumerOffsetTopicName)
	p := topic.ChoosePartitionForKey(partitionKey(topicName, partitionName, consumerGroup, consumerName))

	n := b.getPartitionLeader(ConsumerOffsetTopicName, p.Id)
	if n == nil {
		return ErrNoLeaderFound
	}

	if n.Name != b.Name() {
		change := &sgproto.MultiOffsetChangeRequest{
			Topic:         topicName,
			Partition:     partitionName,
			ConsumerGroup: consumerGroup,
			ConsumerName:  consumerName,
			Offsets:       offsets,
		}

		_, err := n.AcknowledgeMessages(ctx, change)

		if err != nil {
			return err
		}

		return nil
	}

	msgs := []*sgproto.Message{}
	for _, offset := range offsets {
		state := &sgproto.MarkState{
			Kind: sgproto.MarkKind_Acknowledged,
		}

		value, err := proto.Marshal(state)
		if err != nil {
			return err
		}

		msgs = append(msgs, &sgproto.Message{
			Topic:         ConsumerOffsetTopicName,
			Partition:     p.Id,
			Offset:        offset,
			Key:           partitionKey(topicName, partitionName, consumerGroup, consumerName),
			ClusteringKey: generateClusterKey(offset, sgproto.MarkKind_Acknowledged),
			Value:         value,
		})
	}

	return b.PublishMessages(ctx, msgs)
}

func (b *Broker) Commit(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, offset sandflake.ID) (bool, error) {
	return b.mark(ctx, topicName, partitionName, consumerGroup, consumerName, offset, sgproto.MarkKind_Commited)
}

func (b *Broker) MarkConsumed(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, offset sandflake.ID) (bool, error) {
	return b.mark(ctx, topicName, partitionName, consumerGroup, consumerName, offset, sgproto.MarkKind_Consumed)
}

func (b *Broker) mark(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, offset sandflake.ID, kind sgproto.MarkKind) (bool, error) {
	topic := b.getTopic(ConsumerOffsetTopicName)
	p := topic.ChoosePartitionForKey(partitionKey(topicName, partitionName, consumerGroup, consumerName))

	n := b.getPartitionLeader(ConsumerOffsetTopicName, p.Id)
	if n == nil {
		return false, ErrNoLeaderFound
	}

	if n.Name != b.Name() {
		change := &sgproto.OffsetChangeRequest{
			Topic:         topicName,
			Partition:     partitionName,
			ConsumerGroup: consumerGroup,
			ConsumerName:  consumerName,
			Offset:        offset,
		}

		var (
			res *sgproto.OffsetChangeReply
			err error
		)
		switch kind {
		case sgproto.MarkKind_Acknowledged:
			res, err = n.Acknowledge(ctx, change)
		case sgproto.MarkKind_Commited:
			res, err = n.Commit(ctx, change)
		case sgproto.MarkKind_Consumed:
			res, err = n.MarkConsumed(ctx, change)
		}
		if err != nil {
			return false, err
		}

		return res.Success, nil
	}

	state := &sgproto.MarkState{
		Kind: kind,
	}

	value, err := proto.Marshal(state)
	if err != nil {
		return false, err
	}

	res, err := b.PublishMessage(ctx, &sgproto.Message{
		Topic:         ConsumerOffsetTopicName,
		Partition:     p.Id,
		Offset:        offset,
		Key:           partitionKey(topicName, partitionName, consumerGroup, consumerName),
		ClusteringKey: generateClusterKey(offset, kind),
		Value:         value,
	})
	return res != nil, err
}

func (b *Broker) LastOffset(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, kind sgproto.MarkKind) (sandflake.ID, error) {
	topic := b.getTopic(ConsumerOffsetTopicName)
	pk := partitionKey(topicName, partitionName, consumerGroup, consumerName)
	p := topic.ChoosePartitionForKey(pk)

	n := b.getPartitionLeader(ConsumerOffsetTopicName, p.Id)
	if n == nil {
		return sandflake.Nil, ErrNoLeaderFound
	}

	if n.Name != b.Name() {
		res, err := n.LastOffset(ctx, &sgproto.LastOffsetRequest{
			Topic:         topicName,
			Partition:     partitionName,
			ConsumerGroup: consumerGroup,
			ConsumerName:  consumerName,
			Kind:          kind,
		})
		if err != nil {
			return sandflake.Nil, err
		}

		return res.Offset, nil
	}

	lastKind := byte(kind)

	return b.last(p, pk, lastKind)
}

func (b *Broker) GetMarkStateMessage(ctx context.Context, topicName, partitionName, consumerGroup, consumerName string, offset sandflake.ID) (*sgproto.Message, error) {
	topic := b.getTopic(ConsumerOffsetTopicName)
	pk := partitionKey(topicName, partitionName, consumerGroup, consumerName)
	p := topic.ChoosePartitionForKey(pk)

	n := b.getPartitionLeader(ConsumerOffsetTopicName, p.Id)
	if n == nil {
		return nil, ErrNoLeaderFound
	}

	if n.Name != b.Name() {
		res, err := n.GetMarkStateMessage(ctx, &sgproto.OffsetChangeRequest{
			Topic:         topicName,
			Partition:     partitionName,
			ConsumerGroup: consumerGroup,
			ConsumerName:  consumerName,
			Offset:        offset,
		})
		if err != nil {
			return nil, err
		}

		return res, nil
	}

	key := generatePrefixConsumerOffsetKey(pk, offset)

	msg, err := p.GetMessage(sandflake.Nil, key, nil)
	if err != nil {
		return nil, err
	}

	if msg == nil {
		return nil, status.Error(codes.NotFound, "mark state not found")
	}

	return msg, nil
}

func (b *Broker) last(p *topic.Partition, pk []byte, kind byte) (sandflake.ID, error) {
	msg, err := p.GetMessage(sandflake.Nil, pk, []byte{kind})
	if err != nil {
		return sandflake.Nil, err
	}

	if msg == nil {
		return sandflake.Nil, nil
	}

	if len(msg.Value) == 0 {
		return sandflake.Nil, fmt.Errorf("LastCommitedOffset malformed value '%v'", msg.Value)
	}

	return msg.Offset, nil
}

func (b *Broker) isAcknoweldged(ctx context.Context, topicName, partition, consumerGroup string, offset sandflake.ID) (bool, error) {
	topic := b.getTopic(ConsumerOffsetTopicName)
	if topic == nil {
		return false, ErrTopicNotFound
	}
	pk := partitionKey(topicName, partition, consumerGroup, "NOT SET")
	p := topic.ChoosePartitionForKey(pk)
	clusterKey := generateClusterKey(offset, sgproto.MarkKind_Acknowledged)
	return b.hasKeyInPartition(ctx, ConsumerOffsetTopicName, p, pk, clusterKey)
}

func partitionKey(topicName, partitionName, consumerGroup, consumerName string) []byte {
	return bytes.Join([][]byte{
		[]byte("offsets"),
		[]byte(topicName),
		[]byte(partitionName),
		[]byte(consumerGroup),
		// []byte(consumerName),
	}, storage.Separator)
}

func generateClusterKey(offset sandflake.ID, kind sgproto.MarkKind) []byte {
	return bytes.Join([][]byte{
		offset.Bytes(),
		[]byte{byte(kind)},
	}, storage.Separator)
}
<|endoftext|>


<commit_before>/*
The MIT License (MIT)

Copyright (c) 2016 winlin

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

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

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

/*
 This the main entrance of https-proxy, proxy to api or other http server.
*/
package main

import (
	"crypto/tls"
	"flag"
	"fmt"
	"github.com/ossrs/go-oryx-lib/https"
	"net/http"
	"os"
	"strings"
)

func main() {
	var httpPort, httpsPort int
	flag.IntVar(&httpPort, "http", 80, "http listen at. 0 to disable http.")
	flag.IntVar(&httpsPort, "https", 443, "https listen at. 0 to disable https. 443 to serve.")
	flag.Parse()

	if httpsPort != 0 && httpsPort != 443 {
		fmt.Println("https must be 0(disabled) or 443(enabled)")
		os.Exit(-1)
	}
	if httpPort == 0 && httpsPort == 0 {
		fmt.Println("http or https are disabled")
		os.Exit(-1)
	}

	html := http.Dir("./html")
	fh := http.FileServer(html)
	http.Handle("/", fh)

	var err error
	var m https.Manager
	if m, err = https.NewLetsencryptManager("", nil, "letsencrypt.cache"); err != nil {
		fmt.Println("https failed, err is", err)
		return
	}

	var protos []string
	if httpPort != 0 {
		protos = append(protos, fmt.Sprintf("http(:%v)", httpPort))
	}
	if httpsPort != 0 {
		protos = append(protos, fmt.Sprintf("https(:%v)", httpsPort))
	}
	fmt.Println(fmt.Sprintf("%v html root at %v", strings.Join(protos, ", "), string(html)))

	go func() {
		if err := http.ListenAndServe(":http", nil); err != nil {
			panic(err)
		}
	}()

	svr := &http.Server{
		Addr: ":https",
		TLSConfig: &tls.Config{
			GetCertificate: m.GetCertificate,
		},
	}

	if err := svr.ListenAndServeTLS("", ""); err != nil {
		panic(err)
	}
}
<commit_msg>update code<commit_after>/*
The MIT License (MIT)

Copyright (c) 2016 winlin

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

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

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

/*
 This the main entrance of https-proxy, proxy to api or other http server.
*/
package main

import (
	"crypto/tls"
	"flag"
	"fmt"
	"github.com/ossrs/go-oryx-lib/https"
	"net/http"
	"os"
	"strings"
	"sync"
)

func main() {
	var httpPort, httpsPort int
	flag.IntVar(&httpPort, "http", 80, "http listen at. 0 to disable http.")
	flag.IntVar(&httpsPort, "https", 443, "https listen at. 0 to disable https. 443 to serve.")
	flag.Parse()

	if httpsPort != 0 && httpsPort != 443 {
		fmt.Println("https must be 0(disabled) or 443(enabled)")
		os.Exit(-1)
	}
	if httpPort == 0 && httpsPort == 0 {
		fmt.Println("http or https are disabled")
		os.Exit(-1)
	}

	html := http.Dir("./html")
	fh := http.FileServer(html)
	http.Handle("/", fh)

	var protos []string
	if httpPort != 0 {
		protos = append(protos, fmt.Sprintf("http(:%v)", httpPort))
	}
	if httpsPort != 0 {
		protos = append(protos, fmt.Sprintf("https(:%v)", httpsPort))
	}
	fmt.Println(fmt.Sprintf("%v html root at %v", strings.Join(protos, ", "), string(html)))

	wg := sync.WaitGroup{}
	go func() {
		defer wg.Done()

		if httpPort == 0 {
			return
		}

		if err := http.ListenAndServe(fmt.Sprintf(":%v", httpPort), nil); err != nil {
			panic(err)
		}
	}()
	wg.Add(1)

	go func() {
		defer wg.Done()

		if httpsPort == 0 {
			return
		}

		var err error
		var m https.Manager
		if m, err = https.NewLetsencryptManager("", nil, "letsencrypt.cache"); err != nil {
			panic(err)
		}

		svr := &http.Server{
			Addr: fmt.Sprintf(":%v", httpsPort),
			TLSConfig: &tls.Config{
				GetCertificate: m.GetCertificate,
			},
		}

		if err := svr.ListenAndServeTLS("", ""); err != nil {
			panic(err)
		}
	}()

	wg.Wait()
}
<|endoftext|>


<commit_before>package log

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"testing"
)

// logTest is used to test the output generated by key/value pairs.
type logTest struct {
	values   V
	expected string
}

func (l *logTest) String() string {
	return fmt.Sprintf("TEST-%s", l.expected)
}

var logTests = []logTest{
	{
		expected: "service:debugging",
		values: V{
			"service": "debugging",
			"key":     1,
		},
	},
	{
		expected: "key:1",
		values: V{
			"service": "debugging",
			"key":     1,
		},
	},
	{
		expected: "key:-43",
		values: V{
			"service": V{"test": 3.0},
			"key":     -43,
		},
	},
	{
		expected: "service:map[test:3]",
		values: V{
			"service": V{"test": 3},
			"key":     -43,
		},
	},
	{
		expected: "struct:TEST-foobar ",
		values: V{
			"service": "`'log_test'a`accenté`df!#$!@#%^",
			"struct":  &logTest{expected: "foobar"},
		},
	},
}

// Test logging to the default logger
func TestDefault(t *testing.T) {
	logger, err := NewStdErr("myapp: ")
	if err != nil {
		t.Fatalf("log: error creating default logger :%s", err)
	}

	// Record what is written for testing
	var recorder bytes.Buffer
	logger.Writer = &recorder

	// Test levels
	logger.Level = LevelError

	// Add to our list of outputs
	Add(logger)

	// Add another logger just to check
	auxillaryLogger, err := NewStdErr("auxillary: ")
	if err != nil {
		t.Fatalf("log: error creating aux logger :%s", err)
	}
	var recorder2 bytes.Buffer
	auxillaryLogger.Writer = &recorder2
	Add(auxillaryLogger)

	// Write out a string at debug level
	Debug(logTests[0].values)

	result, err := recorder.ReadString('\n')
	if err != nil && err != io.EOF {
		t.Fatalf("log: error on default logger %s", err)
	}
	if len(result) > 0 {
		t.Fatalf("log: debug logging wrote to logger got:%s", result)
	}

	// Test the auxillary logger for output
	result, err = recorder2.ReadString('\n')
	if err != nil {
		t.Fatalf("log: error on aux logger %s", err)
	}
	if len(result) == 0 {
		t.Fatalf("log: aux log empty got:%s", result)
	}

	// Check that logging has the expected results
	for _, test := range logTests {

		// Write to log
		Error(test.values)

		// Test reading recorder line
		result, err := recorder.ReadString('\n')
		if err != nil && err != io.EOF {
			t.Fatalf("log: error on default logger %s", err)
		}
		// Check expected strings
		if !strings.Contains(result, test.expected) {
			t.Fatalf("log: mismatch on default logger expected:%s got:%s", test.expected, result)
		}
	}

}

// TestFile tests the file logger (might fail in some environments)
// this test requires write access to the pkg directory
func TestFile(t *testing.T) {
	// Create file logger at a given path (we must have permissions to do so)
	path := "test.log"
	logger, err := NewFile(path)
	if err != nil {
		t.Fatalf("log: error creating file logger :%s", err)
	}

	logger.Prefix = "HYPERLOG: "

	// Add to our list of outputs
	Add(logger)

	// Write to logger
	Fatal(V{"action": "fatal"})

	// Test the file exists
	_, err = os.Stat(path)
	if err != nil {
		t.Fatalf("error creating log file %s", err)
	}

	// Read file to check contents
	got, err := ioutil.ReadFile(path)
	if err != nil {
		t.Fatalf("error reading log file %s", err)
	}

	// Delete the file after test, regardless of outcome
	err = os.Remove(path)
	if err != nil {
		t.Fatalf("error deleting test.log file %s", err)
	}

	expected := "HYPERLOG: action:fatal #fatal"
	if !strings.HasPrefix(string(got), expected) {
		t.Fatalf("error reading log file expected:\n%s got:\n%s\n", expected, string(got))
	}

}

// TestInvalidLevels tests we don't panic if invalid level key is set
func TestInvalidLevels(t *testing.T) {
	logger, err := NewStdErr("myapp: ")
	if err != nil {
		t.Fatalf("log: error creating default logger :%s", err)
	}
	Add(logger)

	Debug(V{LevelKey: 2})
	Info(V{LevelKey: nil})
	Error(V{ErrorKey: nil})
	Fatal(V{LevelKey: t})
	Log(V{LevelKey: 2e21})
	Log(V{LevelKey: nil})
	Log(V{LevelKey: t})
	logger.LevelValue(V{LevelKey: -5e21})
	logger.LevelValue(V{LevelKey: t})
	logger.LevelValue(V{LevelKey: nil})
}
<commit_msg>Tidy up logging testing, add middleware test<commit_after>package log

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"testing"
)

// logTest is used to test the output generated by key/value pairs.
type logTest struct {
	values   V
	expected string
}

func (l *logTest) String() string {
	return fmt.Sprintf("TEST-%s", l.expected)
}

var logTests = []logTest{
	{
		expected: "service:debugging",
		values: V{
			"service": "debugging",
			"key":     1,
		},
	},
	{
		expected: "key:1",
		values: V{
			"service": "debugging",
			"key":     1,
		},
	},
	{
		expected: "key:-43",
		values: V{
			"service": V{"test": 3.0},
			"key":     -43,
		},
	},
	{
		expected: "service:map[test:3]",
		values: V{
			"service": V{"test": 3},
			"key":     -43,
		},
	},
	{
		expected: "struct:TEST-foobar ",
		values: V{
			"service": "`'log_test'a`accenté`df!#$!@#%^",
			"struct":  &logTest{expected: "foobar"},
		},
	},
}

// Test logging to the default logger
func TestDefault(t *testing.T) {
	logger, err := NewStdErr("myapp: ")
	if err != nil {
		t.Fatalf("log: error creating default logger :%s", err)
	}

	// Record what is written for testing
	var recorder bytes.Buffer
	logger.Writer = &recorder

	// Test levels
	logger.Level = LevelError

	// Add to our list of outputs
	Add(logger)

	// Add another logger just to check
	auxillaryLogger, err := NewStdErr("auxillary: ")
	if err != nil {
		t.Fatalf("log: error creating aux logger :%s", err)
	}
	var recorder2 bytes.Buffer
	auxillaryLogger.Writer = &recorder2
	auxillaryLogger.Level = LevelDebug
	Add(auxillaryLogger)

	// Write out a string at debug level
	Debug(logTests[0].values)
	Debug(logTests[1].values)

	result, err := recorder.ReadString('\n')
	if err != nil && err != io.EOF {
		t.Fatalf("log: error on default logger %s", err)
	}
	if len(result) > 0 {
		t.Fatalf("log: debug logging wrote to logger got:%s", result)
	}

	// Test the auxillary logger for output
	result, err = recorder2.ReadString('\n')
	if err != nil {
		t.Fatalf("log: error on aux logger %s", err)
	}
	if len(result) == 0 {
		t.Fatalf("log: aux log empty got:%s", result)
	}

	// Check that logging has the expected results
	for _, test := range logTests {

		// Write to log
		Error(test.values)

		// Test reading recorder line
		result, err := recorder.ReadString('\n')
		if err != nil && err != io.EOF {
			t.Fatalf("log: error on default logger %s", err)
		}
		// Check expected strings
		if !strings.Contains(result, test.expected) {
			t.Fatalf("log: mismatch on default logger expected:%s got:%s", test.expected, result)
		}
	}

}

// TestFile tests the file logger (might fail in some environments)
// this test requires write access to the pkg directory
func TestFile(t *testing.T) {
	// Create file logger at a given path (we must have permissions to do so)
	path := "test.log"
	logger, err := NewFile(path)
	if err != nil {
		t.Fatalf("log: error creating file logger :%s", err)
	}

	logger.Prefix = "HYPERLOG: "

	// Add to our list of outputs
	Add(logger)

	// Write to logger
	Fatal(V{"action": "fatal"})

	// Test the file exists
	_, err = os.Stat(path)
	if err != nil {
		t.Fatalf("error creating log file %s", err)
	}

	// Read file to check contents
	got, err := ioutil.ReadFile(path)
	if err != nil {
		t.Fatalf("error reading log file %s", err)
	}

	// Delete the file after test, regardless of outcome
	err = os.Remove(path)
	if err != nil {
		t.Fatalf("error deleting test.log file %s", err)
	}

	expected := "HYPERLOG: action:fatal #fatal"
	if !strings.HasPrefix(string(got), expected) {
		t.Fatalf("error reading log file expected:\n%s got:\n%s\n", expected, string(got))
	}

}

// TestInvalidLevels tests we don't panic if invalid level key is set
func TestInvalidLevels(t *testing.T) {
	logger, err := NewStdErr("myapp: ")
	if err != nil {
		t.Fatalf("log: error creating default logger :%s", err)
	}
	// Write to a buffer to avoid spamming stdout
	var recorder2 bytes.Buffer
	logger.Writer = &recorder2
	Add(logger)
	Debug(V{LevelKey: 2})
	Info(V{LevelKey: nil})
	Error(V{ErrorKey: nil})
	Fatal(V{LevelKey: t})
	Log(V{LevelKey: 2e21})
	Log(V{LevelKey: nil})
	Log(V{LevelKey: t})
	logger.LevelValue(V{LevelKey: -5e21})
	logger.LevelValue(V{LevelKey: t})
	logger.LevelValue(V{LevelKey: nil})
}

// TestTrace tests tracing using the context pkg is working correctly.
func TestTrace(t *testing.T) {
	logger, err := NewStdErr("myapp: ")
	if err != nil {
		t.Fatalf("log: error creating default logger :%s", err)
	}
	// Write to a buffer to avoid spamming stdout
	var recorder2 bytes.Buffer
	logger.Writer = &recorder2
	Add(logger)

	// Setup request and recorder
	r := httptest.NewRequest("GET", "/users/create", nil)
	w := httptest.NewRecorder()

	// Add to context for request
	fn := Middleware(mockHandler)

	// Call middleware
	fn(w, r)

	// Get value back out of context
	Trace(r)
}

func mockHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello world"))
}
<|endoftext|>


<commit_before>package eerrors

import (
	"fmt"
	"io"
	"strings"
	"sync/atomic"
	"unsafe"

	errors "github.com/segmentio/errors-go"
	"github.com/stephane-martin/skewer/utils/waiter"
	uatomic "go.uber.org/atomic"
)

type errNode struct {
	err  error
	next *errNode
}

func writePrefixLine(w io.Writer, prefix string, s string) {
	first := true
	for len(s) > 0 {
		if first {
			first = false
		} else {
			io.WriteString(w, prefix)
		}

		idx := strings.IndexByte(s, '\n')
		if idx < 0 {
			idx = len(s) - 1
		}

		io.WriteString(w, s[:idx+1])
		s = s[idx+1:]
	}
}

type ErrorSlice []error

func (s ErrorSlice) Empty() bool {
	return len(s) == 0
}

func (s ErrorSlice) Error() string {
	if len(s) == 0 {
		return ""
	}
	var buf strings.Builder
	first := true
	for _, item := range s {
		if first {
			first = false
		} else {
			buf.WriteString("; ")
		}
		buf.WriteString(item.Error())
	}
	return buf.String()
}

func (s ErrorSlice) Format(f fmt.State, c rune) {
	if len(s) == 0 {
		return
	}
	if c == 'v' && f.Flag('+') {
		io.WriteString(f, "the following errors occurred:")
		for _, item := range s {
			io.WriteString(f, "\n -  ")
			writePrefixLine(f, "    ", fmt.Sprintf("%+v", item))
		}
	} else {
		io.WriteString(f, s.Error())
	}
}

func (s ErrorSlice) Causes() []error {
	if len(s) == 0 {
		return nil
	}
	s2 := make([]error, 0, len(s))
	for _, err := range s {
		s2 = append(s2, err)
	}
	return s2
}

func (s ErrorSlice) WithTags(tags ...string) error {
	if len(s) == 0 {
		return nil
	}
	if len(tags) < 2 {
		return s
	}
	keyFlag := true
	key := ""
	value := ""
	etags := make([]errors.Tag, 0, len(tags)/2)
	for _, tag := range tags {
		if keyFlag {
			key = tag
		} else {
			value = tag
			etags = append(etags, errors.T(key, value))
		}
		keyFlag = !keyFlag
	}
	if len(tags) == 0 {
		return s
	}
	return errors.WithTags(s, etags...)
}

func (s ErrorSlice) Wrap(msg string) error {
	if len(s) == 0 {
		return nil
	}
	return errors.Wrap(s, msg)
}

func (s ErrorSlice) WithTypes(types ...string) error {
	if len(s) == 0 {
		return nil
	}
	return errors.WithTypes(s, types...)
}

func (s ErrorSlice) With(msg string, types []string, tags ...string) (err error) {
	if len(s) == 0 {
		return nil
	}
	err = s.WithTags(tags...)
	err = errors.WithTypes(err, types...)
	return errors.Wrap(err, msg)
}

func (s ErrorSlice) Is(typ string) bool {
	if len(s) == 0 {
		return false
	}
	for _, cause := range s {
		if errors.Is(typ, cause) {
			return true
		}
	}
	return false
}

type ChainedErrors struct {
	head   *errNode
	tail   *errNode
	closed uatomic.Bool
}

func ChainErrors() (c *ChainedErrors) {
	stub := new(errNode)
	c = &ChainedErrors{
		head: stub,
		tail: stub,
	}
	return c
}

func (c *ChainedErrors) Close() {
	c.closed.Store(true)
}

func (c *ChainedErrors) Append(s ...error) *ChainedErrors {
	if c.closed.Load() {
		return c
	}
	for _, e := range s {
		c.append(e)
	}
	return c
}

func (c *ChainedErrors) append(err error) *ChainedErrors {
	if err == nil {
		return c
	}
	if e, ok := err.(causes); ok {
		if e == nil {
			return c
		}
		return c.Extend(e.Causes())
	}

	n := new(errNode)
	n.err = err
	(*errNode)(atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.head)), unsafe.Pointer(n))).next = n
	return c
}

func (c *ChainedErrors) get() error {
	tail := c.tail
	next := tail.next
	if next != nil {
		(*errNode)(atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.tail)), unsafe.Pointer(next))).err = next.err
		return next.err
	}
	return nil
}

func (c *ChainedErrors) Sum() (s ErrorSlice) {
	if !c.closed.CAS(false, true) {
		return nil
	}
	s = make([]error, 0, 10)
	var err error
	for {
		err = c.get()
		if err == nil {
			break
		}
		s = append(s, err)
	}
	if len(s) == 0 {
		return nil
	}
	return s
}

func (c *ChainedErrors) Extend(s []error) *ChainedErrors {
	if c.closed.Load() {
		return c
	}
	for _, e := range s {
		c.append(e)
	}
	return c
}

func (c *ChainedErrors) Receive(ch <-chan error) *ChainedErrors {
	if c.closed.Load() {
		return c
	}
	for e := range ch {
		c.append(e)
	}
	return c
}

func (c *ChainedErrors) Send(ch chan<- error) {
	var err error
	w := waiter.Default()
	for {
		err = c.get()
		if err == nil {
			if c.closed.Load() {
				break
			}
			w.Wait()
		} else {
			w.Reset()
			ch <- err
		}
	}
	close(ch)
}

func Combine(errs ...error) (err error) {
	return ChainErrors().Extend(errs).Sum()
}
<commit_msg>more explicit<commit_after>package eerrors

import (
	"fmt"
	"io"
	"strings"
	"sync/atomic"
	"unsafe"

	errors "github.com/segmentio/errors-go"
	"github.com/stephane-martin/skewer/utils/waiter"
	uatomic "go.uber.org/atomic"
)

type errNode struct {
	err  error
	next *errNode
}

func writePrefixLine(w io.Writer, prefix string, s string) {
	first := true
	for len(s) > 0 {
		if first {
			first = false
		} else {
			io.WriteString(w, prefix)
		}

		idx := strings.IndexByte(s, '\n')
		if idx < 0 {
			idx = len(s) - 1
		}

		io.WriteString(w, s[:idx+1])
		s = s[idx+1:]
	}
}

type ErrorSlice []error

func (s ErrorSlice) Empty() bool {
	return len(s) == 0
}

func (s ErrorSlice) Error() string {
	if s.Empty() {
		return ""
	}
	var buf strings.Builder
	first := true
	for _, item := range s {
		if first {
			first = false
		} else {
			buf.WriteString("; ")
		}
		buf.WriteString(item.Error())
	}
	return buf.String()
}

func (s ErrorSlice) Format(f fmt.State, c rune) {
	if s.Empty() {
		return
	}
	if c == 'v' && f.Flag('+') {
		io.WriteString(f, "the following errors occurred:")
		for _, item := range s {
			io.WriteString(f, "\n -  ")
			writePrefixLine(f, "    ", fmt.Sprintf("%+v", item))
		}
	} else {
		io.WriteString(f, s.Error())
	}
}

func (s ErrorSlice) Causes() []error {
	if s.Empty() {
		return nil
	}
	s2 := make([]error, 0, len(s))
	for _, err := range s {
		s2 = append(s2, err)
	}
	return s2
}

func (s ErrorSlice) WithTags(tags ...string) error {
	if s.Empty() {
		return nil
	}
	if len(tags) < 2 {
		return s
	}
	keyFlag := true
	key := ""
	value := ""
	etags := make([]errors.Tag, 0, len(tags)/2)
	for _, tag := range tags {
		if keyFlag {
			key = tag
		} else {
			value = tag
			etags = append(etags, errors.T(key, value))
		}
		keyFlag = !keyFlag
	}
	if len(tags) == 0 {
		return s
	}
	return errors.WithTags(s, etags...)
}

func (s ErrorSlice) Wrap(msg string) error {
	if s.Empty() {
		return nil
	}
	return errors.Wrap(s, msg)
}

func (s ErrorSlice) WithTypes(types ...string) error {
	if s.Empty() {
		return nil
	}
	return errors.WithTypes(s, types...)
}

func (s ErrorSlice) With(msg string, types []string, tags ...string) (err error) {
	if s.Empty() {
		return nil
	}
	err = s.WithTags(tags...)
	err = errors.WithTypes(err, types...)
	return errors.Wrap(err, msg)
}

func (s ErrorSlice) Is(typ string) bool {
	if s.Empty() {
		return false
	}
	for _, cause := range s {
		if errors.Is(typ, cause) {
			return true
		}
	}
	return false
}

type ChainedErrors struct {
	head   *errNode
	tail   *errNode
	closed uatomic.Bool
}

func ChainErrors() (c *ChainedErrors) {
	stub := new(errNode)
	c = &ChainedErrors{
		head: stub,
		tail: stub,
	}
	return c
}

func (c *ChainedErrors) Close() {
	c.closed.Store(true)
}

func (c *ChainedErrors) Append(s ...error) *ChainedErrors {
	if c.closed.Load() {
		return c
	}
	for _, e := range s {
		c.append(e)
	}
	return c
}

func (c *ChainedErrors) append(err error) *ChainedErrors {
	if err == nil {
		return c
	}
	if e, ok := err.(causes); ok {
		if e == nil {
			return c
		}
		return c.Extend(e.Causes())
	}

	n := new(errNode)
	n.err = err
	(*errNode)(atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.head)), unsafe.Pointer(n))).next = n
	return c
}

func (c *ChainedErrors) get() error {
	tail := c.tail
	next := tail.next
	if next != nil {
		(*errNode)(atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.tail)), unsafe.Pointer(next))).err = next.err
		return next.err
	}
	return nil
}

func (c *ChainedErrors) Sum() (s ErrorSlice) {
	if !c.closed.CAS(false, true) {
		return nil
	}
	s = make([]error, 0, 10)
	var err error
	for {
		err = c.get()
		if err == nil {
			break
		}
		s = append(s, err)
	}
	if len(s) == 0 {
		return nil
	}
	return s
}

func (c *ChainedErrors) Extend(s []error) *ChainedErrors {
	if c.closed.Load() {
		return c
	}
	for _, e := range s {
		c.append(e)
	}
	return c
}

func (c *ChainedErrors) Receive(ch <-chan error) *ChainedErrors {
	if c.closed.Load() {
		return c
	}
	for e := range ch {
		c.append(e)
	}
	return c
}

func (c *ChainedErrors) Send(ch chan<- error) {
	var err error
	w := waiter.Default()
	for {
		err = c.get()
		if err == nil {
			if c.closed.Load() {
				break
			}
			w.Wait()
		} else {
			w.Reset()
			ch <- err
		}
	}
	close(ch)
}

func Combine(errs ...error) error {
	e := ChainErrors().Extend(errs).Sum()
	if e.Empty() {
		return nil
	}
	return e
}
<|endoftext|>


<commit_before>package logger

import (
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"sync"
)

var (
	reset  = "\033[0m"
	red    = "\033[31;1m"
	green  = "\033[32;1m"
	yellow = "\033[33;1m"
)

type Level int

const ENV_CART_LOG_LEVEL = "CART_LOG_LEVEL"

const (
	Info Level = 1 + iota
	Warn
	Error
)

var logLevel = Info
var Logger = &logger{}

type logger struct {
	mu    sync.Mutex
	info  *log.Logger
	warn  *log.Logger
	error *log.Logger
}

func init() {
	l := os.Getenv(ENV_CART_LOG_LEVEL)
	if len(l) == 0 {
		SetLevel(Info)
	} else {
		var level Level
		switch strings.ToLower(l) {
		case "info":
			level = Info
			break
		case "warn":
			level = Warn
			break
		case "error":
			level = Error
			break
		}
		SetLevel(level)
	}
	Logger.info = log.New(os.Stdout, "[INFO]  ", log.LstdFlags)
	Logger.warn = log.New(os.Stderr, "[WARN]  ", log.LstdFlags)
	Logger.error = log.New(os.Stderr, "[ERROR] ", log.LstdFlags)
	log.SetOutput(os.Stdout)
}

func SetLevel(level Level) {
	logLevel = level
}

func SetFileOutput(dir string) {
	writer := DefaultFileWriter(dir)
	SetOutput(writer)
}

func SetOutput(writer io.Writer) {
	Logger.info.SetOutput(writer)
	Logger.warn.SetOutput(writer)
	Logger.error.SetOutput(writer)
	log.SetOutput(writer)
	if writer == os.Stdout || writer == os.Stderr {
		Logger.info.SetPrefix(green+"[INFO]  "+reset)
		Logger.warn.SetPrefix(yellow+"[WARN]  "+reset)
		Logger.error.SetPrefix(red+"[ERROR] "+reset)
		Logger.warn.SetFlags(log.LstdFlags)
		Logger.info.SetFlags(log.LstdFlags)
		Logger.error.SetFlags(log.LstdFlags)
		log.SetFlags(log.LstdFlags)
	} else {
		Logger.info.SetPrefix("[INFO]  ")
		Logger.warn.SetPrefix("[WARN]  ")
		Logger.error.SetPrefix("[ERROR] ")
		Logger.info.SetFlags(log.Ltime)
		Logger.warn.SetFlags(log.Ltime)
		Logger.error.SetFlags(log.Ltime)
	}
}

func (l *logger) Info(format string, v ...interface{}) {
	if logLevel <= Info {
		l.OutPut(l.info, fmt.Sprintf(format, v...))
	}
}
func (l *logger) Warn(format string, v ...interface{}) {
	if logLevel <= Warn {
		l.OutPut(l.warn, fmt.Sprintf(format, v...))
	}
}
func (this *logger) Error(format string, v ...interface{}) {
	if logLevel <= Error {
		this.OutPut(this.error, fmt.Sprintf(format, v...))
	}
}

func (this *logger) OutPut(l *log.Logger, s string) {
	this.mu.Lock()
	defer this.mu.Unlock()
	l.Println(s)
}
<commit_msg>remove log format<commit_after>package logger

import (
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"sync"
)

var (
	reset  = "\033[0m"
	red    = "\033[31;1m"
	green  = "\033[32;1m"
	yellow = "\033[33;1m"
)

type Level int

const ENV_CART_LOG_LEVEL = "CART_LOG_LEVEL"

const (
	Info Level = 1 + iota
	Warn
	Error
)

var logLevel = Info
var Logger = &logger{}

type logger struct {
	mu    sync.Mutex
	info  *log.Logger
	warn  *log.Logger
	error *log.Logger
}

func init() {
	l := os.Getenv(ENV_CART_LOG_LEVEL)
	if len(l) == 0 {
		SetLevel(Info)
	} else {
		var level Level
		switch strings.ToLower(l) {
		case "info":
			level = Info
			break
		case "warn":
			level = Warn
			break
		case "error":
			level = Error
			break
		}
		SetLevel(level)
	}
	Logger.info = log.New(os.Stdout, "[INFO]  ", log.LstdFlags)
	Logger.warn = log.New(os.Stderr, "[WARN]  ", log.LstdFlags)
	Logger.error = log.New(os.Stderr, "[ERROR] ", log.LstdFlags)
	log.SetOutput(os.Stdout)
}

func SetLevel(level Level) {
	logLevel = level
}

func SetFileOutput(dir string) {
	writer := DefaultFileWriter(dir)
	SetOutput(writer)
}

func SetOutput(writer io.Writer) {
	Logger.info.SetOutput(writer)
	Logger.warn.SetOutput(writer)
	Logger.error.SetOutput(writer)
	log.SetOutput(writer)
	if writer == os.Stdout || writer == os.Stderr {
		Logger.info.SetPrefix(green + "[INFO]  " + reset)
		Logger.warn.SetPrefix(yellow + "[WARN]  " + reset)
		Logger.error.SetPrefix(red + "[ERROR] " + reset)
		Logger.warn.SetFlags(log.LstdFlags)
		Logger.info.SetFlags(log.LstdFlags)
		Logger.error.SetFlags(log.LstdFlags)
		log.SetFlags(log.LstdFlags)
	} else {
		Logger.info.SetPrefix("[INFO]  ")
		Logger.warn.SetPrefix("[WARN]  ")
		Logger.error.SetPrefix("[ERROR] ")
		Logger.info.SetFlags(log.Ltime)
		Logger.warn.SetFlags(log.Ltime)
		Logger.error.SetFlags(log.Ltime)
	}
}

func (l *logger) Info(format string, v ...interface{}) {
	if logLevel <= Info {
		l.OutPut(l.info, fmt.Sprintf(format, v...))
	}
}
func (l *logger) Warn(format string, v ...interface{}) {
	if logLevel <= Warn {
		l.OutPut(l.warn, fmt.Sprintf(format, v...))
	}
}
func (this *logger) Error(format string, v ...interface{}) {
	if logLevel <= Error {
		this.OutPut(this.error, fmt.Sprintf(format, v...))
	}
}

func (this *logger) OutPut(l *log.Logger, s string) {
	this.mu.Lock()
	defer this.mu.Unlock()
	l.Println(s)
}
<|endoftext|>


<commit_before>// Copyright 2015 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package routers

import (
	"fmt"
	"io"
	"os"
	"path"
	"strings"

	"github.com/Unknwon/com"
	"github.com/Unknwon/log"

	"github.com/peachdocs/peach/models"
	"github.com/peachdocs/peach/modules/middleware"
	"github.com/peachdocs/peach/modules/setting"
)

func renderEditPage(ctx *middleware.Context, documentPath string) {
	if setting.Extension.EnableEditPage {
		ctx.Data["EditPageLink"] = com.Expand(setting.Extension.EditPageLinkFormat, map[string]string{
			"lang": ctx.Locale.Language(),
			"blob": documentPath + ".md",
		})
	}
}

func Docs(ctx *middleware.Context) {
	toc := models.Tocs[ctx.Locale.Language()]
	if toc == nil {
		toc = models.Tocs[setting.Docs.Langs[0]]
	}
	ctx.Data["Toc"] = toc

	nodeName := strings.TrimPrefix(strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ".html")), setting.Page.DocsBaseURL)
	node, isDefault := toc.GetDoc(nodeName)
	if node == nil {
		NotFound(ctx)
		return
	}
	if !setting.ProdMode {
		node.ReloadContent()
	}

	langVer := toc.Lang
	if isDefault {
		ctx.Data["IsShowingDefault"] = isDefault
		langVer = setting.Docs.Langs[0]
	}
	ctx.Data["Title"] = node.Title
	ctx.Data["Content"] = fmt.Sprintf(`<script type="text/javascript" src="/%s/%s?=%d"></script>`, langVer, node.DocumentPath+".js", node.LastBuildTime)

	renderEditPage(ctx, node.DocumentPath)
	ctx.HTML(200, "docs")
}

func DocsStatic(ctx *middleware.Context) {
	if len(ctx.Params("*")) > 0 {
		f, err := os.Open(path.Join(models.Tocs[setting.Docs.Langs[0]].RootPath, "images", ctx.Params("*")))
		if err != nil {
			ctx.JSON(500, map[string]interface{}{
				"error": err.Error(),
			})
			return
		}
		defer f.Close()

		_, err = io.Copy(ctx.Resp, f)
		if err != nil {
			ctx.JSON(500, map[string]interface{}{
				"error": err.Error(),
			})
			return
		}
		return
	}
	ctx.Error(404)
}

func Hook(ctx *middleware.Context) {
	if ctx.Query("secret") != setting.Docs.Secret {
		ctx.Error(403)
		return
	}

	log.Info("Incoming hook update request")
	if err := models.ReloadDocs(); err != nil {
		ctx.Error(500)
		return
	}
	ctx.Status(200)
}
<commit_msg>init - 关键路由: docs部分.<commit_after>// Copyright 2015 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package routers

import (
	"fmt"
	"io"
	"os"
	"path"
	"strings"

	"github.com/Unknwon/com"
	"github.com/Unknwon/log"

	"github.com/peachdocs/peach/models"
	"github.com/peachdocs/peach/modules/middleware"
	"github.com/peachdocs/peach/modules/setting"
)

func renderEditPage(ctx *middleware.Context, documentPath string) {
	if setting.Extension.EnableEditPage {
		ctx.Data["EditPageLink"] = com.Expand(setting.Extension.EditPageLinkFormat, map[string]string{
			"lang": ctx.Locale.Language(),
			"blob": documentPath + ".md",
		})
	}
}

// 核心功能:
// 	- 文档路由处理
//
func Docs(ctx *middleware.Context) {
	toc := models.Tocs[ctx.Locale.Language()]
	if toc == nil {
		toc = models.Tocs[setting.Docs.Langs[0]]
	}
	ctx.Data["Toc"] = toc

	nodeName := strings.TrimPrefix(strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ".html")), setting.Page.DocsBaseURL)

	node, isDefault := toc.GetDoc(nodeName)	// 根据节点名, 提取文档内容
	if node == nil {
		NotFound(ctx)
		return
	}
	if !setting.ProdMode {
		node.ReloadContent()	// 解析 markdown 文件, 并渲染 HTML 页面数据
	}

	langVer := toc.Lang
	if isDefault {
		ctx.Data["IsShowingDefault"] = isDefault
		langVer = setting.Docs.Langs[0]
	}
	ctx.Data["Title"] = node.Title
	ctx.Data["Content"] = fmt.Sprintf(`<script type="text/javascript" src="/%s/%s?=%d"></script>`,
		langVer, node.DocumentPath+".js", node.LastBuildTime)

	renderEditPage(ctx, node.DocumentPath)		// todo: ?? 实现暂时没细看
	ctx.HTML(200, "docs")
}

func DocsStatic(ctx *middleware.Context) {
	if len(ctx.Params("*")) > 0 {
		f, err := os.Open(path.Join(models.Tocs[setting.Docs.Langs[0]].RootPath, "images", ctx.Params("*")))
		if err != nil {
			ctx.JSON(500, map[string]interface{}{
				"error": err.Error(),
			})
			return
		}
		defer f.Close()

		_, err = io.Copy(ctx.Resp, f)
		if err != nil {
			ctx.JSON(500, map[string]interface{}{
				"error": err.Error(),
			})
			return
		}
		return
	}
	ctx.Error(404)
}

func Hook(ctx *middleware.Context) {
	if ctx.Query("secret") != setting.Docs.Secret {
		ctx.Error(403)
		return
	}

	log.Info("Incoming hook update request")
	if err := models.ReloadDocs(); err != nil {
		ctx.Error(500)
		return
	}
	ctx.Status(200)
}
<|endoftext|>


<commit_before>package core

import (
	"errors"
	"fmt"
	"path"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	e "github.com/pkg/errors"
	ie "github.com/sahib/brig/catfs/errors"
	n "github.com/sahib/brig/catfs/nodes"
	h "github.com/sahib/brig/util/hashlib"
)

var (
	ErrIsGhost = errors.New("Is a ghost")
)

// mkdirParents takes the dirname of repoPath and makes sure all intermediate
// directories are created. The last directory will be returned.

// If any directory exist already, it will not be touched.
// You can also think of it as mkdir -p.
func mkdirParents(lkr *Linker, repoPath string) (*n.Directory, error) {
	repoPath = path.Clean(repoPath)

	elems := strings.Split(repoPath, "/")
	for idx := 0; idx < len(elems)-1; idx++ {
		dirname := strings.Join(elems[:idx+1], "/")
		if dirname == "" {
			dirname = "/"
		}

		dir, err := Mkdir(lkr, dirname, false)
		if err != nil {
			return nil, err
		}

		// Return it, if it's the last path component:
		if idx+1 == len(elems)-1 {
			return dir, nil
		}
	}

	return nil, fmt.Errorf("Empty path given")
}

// Mkdir creates the directory at repoPath and any intermediate directories if
// createParents is true. It will fail if there is already a file at `repoPath`
// and it is not a directory.
func Mkdir(lkr *Linker, repoPath string, createParents bool) (dir *n.Directory, err error) {
	dirname, basename := path.Split(repoPath)

	// Take special care of the root node:
	if basename == "" {
		return lkr.Root()
	}

	// Check if the parent exists:
	parent, err := lkr.LookupDirectory(dirname)
	if err != nil && !ie.IsNoSuchFileError(err) {
		return nil, e.Wrap(err, "dirname lookup failed")
	}

	log.Debugf("mkdir: %s", dirname)

	// If it's nil, we might need to create it:
	if parent == nil {
		if !createParents {
			return nil, ie.NoSuchFile(dirname)
		}

		parent, err = mkdirParents(lkr, repoPath)
		if err != nil {
			return nil, err
		}
	}

	child, err := parent.Child(lkr, basename)
	if err != nil {
		return nil, err
	}

	if child != nil {
		switch child.Type() {
		case n.NodeTypeDirectory:
			// Nothing to do really. Return the old child.
			return child.(*n.Directory), nil
		case n.NodeTypeFile:
			return nil, fmt.Errorf("`%s` exists and is a file", repoPath)
		case n.NodeTypeGhost:
			// Remove the ghost and continue with adding:
			if err := parent.RemoveChild(lkr, child); err != nil {
				return nil, err
			}
		default:
			return nil, ie.ErrBadNode
		}
	}

	// Make sure, NextInode() and StageNode is written in one batch.
	batch := lkr.kv.Batch()
	defer func() {
		if err != nil {
			batch.Rollback()
		} else {
			err = batch.Flush()
		}
	}()

	// Create it then!
	dir, err = n.NewEmptyDirectory(lkr, parent, basename, lkr.NextInode())
	if err != nil {
		return nil, err
	}

	if err := lkr.StageNode(dir); err != nil {
		return nil, e.Wrapf(err, "stage dir")
	}

	return dir, nil
}

// Remove removes a single node from a directory.
// `nd` is the node that shall be removed and may not be root.
// The parent directory is returned.
func Remove(lkr *Linker, nd n.ModNode, createGhost, force bool) (parentDir *n.Directory, ghost *n.Ghost, err error) {
	if !force && nd.Type() == n.NodeTypeGhost {
		return nil, nil, ErrIsGhost
	}

	parentDir, err = n.ParentDirectory(lkr, nd)
	if err != nil {
		return nil, nil, err
	}

	// We shouldn't delete the root directory
	// (only directory with a parent)
	if parentDir == nil {
		return nil, nil, fmt.Errorf("Refusing to delete /")
	}

	if err := parentDir.RemoveChild(lkr, nd); err != nil {
		return nil, nil, fmt.Errorf("Failed to remove child: %v", err)
	}

	lkr.MemIndexPurge(nd)

	// Make sure both StageNode() will be written in one batch:
	batch := lkr.kv.Batch()
	defer func() {
		if err != nil {
			batch.Rollback()
		} else {
			err = batch.Flush()
		}
	}()

	if err := lkr.StageNode(parentDir); err != nil {
		return nil, nil, err
	}

	if createGhost {
		ghost, err := n.MakeGhost(nd, lkr.NextInode())
		if err != nil {
			return nil, nil, err
		}

		parentDir.Add(lkr, ghost)
		if err != nil {
			return nil, nil, err
		}

		if err := lkr.StageNode(ghost); err != nil {
			return nil, nil, err
		}

		return parentDir, ghost, nil
	}

	return parentDir, nil, nil
}

// prepareParent tries to figure out the correct parent directory when attempting
// to move `nd` to `dstPath`. It also removes any nodes that are "in the way" if possible.
func prepareParent(lkr *Linker, nd n.ModNode, dstPath string) (*n.Directory, error) {
	// Check if the destination already exists:
	destNode, err := lkr.LookupModNode(dstPath)
	if err != nil && !ie.IsNoSuchFileError(err) {
		return nil, err
	}

	if destNode == nil {
		// No node at this place yet, attempt to look it up.
		return lkr.LookupDirectory(path.Dir(dstPath))
	}

	switch destNode.Type() {
	case n.NodeTypeDirectory:
		// Move inside of this directory.
		// Check if there is already a file
		destDir, ok := destNode.(*n.Directory)
		if !ok {
			return nil, ie.ErrBadNode
		}

		child, err := destDir.Child(lkr, nd.Name())
		if err != nil {
			return nil, err
		}

		// Oh, something is in there?
		if child != nil {
			if nd.Type() == n.NodeTypeFile {
				// TODO: more details
				return nil, fmt.Errorf("Cannot overwrite a directory with a file")
			}

			childDir, ok := child.(*n.Directory)
			if !ok {
				return nil, ie.ErrBadNode
			}

			if childDir.Size() > 0 {
				return nil, fmt.Errorf("Cannot move over: %s; directory is not empty!", child.Path())
			}

			// Okay, there is an empty directory. Let's remove it to
			// replace it with our source node.
			log.Warningf("Remove child dir: %v", childDir)
			if _, _, err := Remove(lkr, childDir, false, false); err != nil {
				return nil, err
			}
		}

		return destDir, nil
	case n.NodeTypeFile:
		log.Warningf("Remove file: %v", destNode.Path())
		parentDir, _, err := Remove(lkr, destNode, false, false)
		return parentDir, err
	case n.NodeTypeGhost:
		// It is already a ghost. Overwrite it and do not create a new one.
		log.Warningf("Remove ghost: %v", destNode.Path())
		parentDir, _, err := Remove(lkr, destNode, false, true)
		return parentDir, err
	default:
		return nil, ie.ErrBadNode
	}
}

func Copy(lkr *Linker, nd n.ModNode, dstPath string) (newNode n.ModNode, err error) {
	// Forbid moving a node inside of one of it's subdirectories.
	if nd.Path() == dstPath {
		return nil, fmt.Errorf("Source and Dest are the same file: %v", dstPath)
	}

	if strings.HasPrefix(path.Dir(dstPath), nd.Path()) {
		return nil, fmt.Errorf(
			"Cannot move `%s` into it's own subdir `%s`",
			nd.Path(),
			dstPath,
		)
	}

	// Make sure both StageNode() will be written in one batch:
	batch := lkr.kv.Batch()
	defer func() {
		if err != nil {
			batch.Rollback()
		} else {
			err = batch.Flush()
		}
	}()

	parentDir, err := prepareParent(lkr, nd, dstPath)
	if err != nil {
		return nil, e.Wrapf(err, "handle parent")
	}

	// And add it to the right destination dir:
	newNode = nd.Copy(lkr.NextInode())
	newNode.SetName(path.Base(dstPath))
	newNode.NotifyMove(lkr, newNode.Path())

	if err := parentDir.Add(lkr, newNode); err != nil {
		return nil, e.Wrapf(err, "parent add")
	}

	if err := lkr.StageNode(newNode); err != nil {
		return nil, err
	}

	return newNode, nil
}

func Move(lkr *Linker, nd n.ModNode, dstPath string) (err error) {
	// Forbid moving a node inside of one of it's subdirectories.
	if nd.Path() == dstPath {
		return fmt.Errorf("Source and Dest are the same file: %v", dstPath)
	}

	if strings.HasPrefix(path.Dir(dstPath), nd.Path()) {
		return fmt.Errorf(
			"Cannot move `%s` into it's own subdir `%s`",
			nd.Path(),
			dstPath,
		)
	}

	// Make sure both StageNode() will be written in one batch:
	batch := lkr.kv.Batch()
	defer func() {
		if err != nil {
			batch.Rollback()
		} else {
			err = batch.Flush()
		}
	}()

	parentDir, err := prepareParent(lkr, nd, dstPath)
	if err != nil {
		return err
	}

	// Remove the old node:
	oldPath := nd.Path()
	_, ghost, err := Remove(lkr, nd, true, true)
	if err != nil {
		return e.Wrapf(err, "remove old")
	}

	if parentDir.Path() == dstPath {
		dstPath = path.Join(parentDir.Path(), path.Base(oldPath))
	}

	// The node needs to be told that it's path changed,
	// since it might need to change it's hash value now.
	if err := nd.NotifyMove(lkr, dstPath); err != nil {
		return e.Wrapf(err, "notify move")
	}

	// And add it to the right destination dir:
	if err := parentDir.Add(lkr, nd); err != nil {
		return e.Wrapf(err, "parent add")
	}

	err = n.Walk(lkr, nd, true, func(child n.Node) error {
		return e.Wrapf(lkr.StageNode(child), "stage node")
	})

	if err != nil {
		return err
	}

	if err := lkr.AddMoveMapping(nd, ghost); err != nil {
		return e.Wrapf(err, "add move mapping")
	}

	return err
}

// TODO: This struct sucks.
type NodeUpdate struct {
	Hash   h.Hash
	Size   uint64
	Author string
	Key    []byte
}

func Stage(lkr *Linker, repoPath string, info *NodeUpdate) (file *n.File, err error) {
	node, err := lkr.LookupNode(repoPath)
	if err != nil && !ie.IsNoSuchFileError(err) {
		return nil, err
	}

	batch := lkr.kv.Batch()
	defer func() {
		if err != nil {
			batch.Rollback()
		} else {
			err = batch.Flush()
		}
	}()

	if node != nil && node.Type() == n.NodeTypeGhost {
		ghostParent, err := n.ParentDirectory(lkr, node)
		if err != nil {
			return nil, err
		}

		if ghostParent == nil {
			// TODO: Think about this case. stage() should not be called on directories
			//       anyways (only on files or files to ghosts)
			return nil, fmt.Errorf("The ghost is a root? Something is wrong...")
		}

		if err := ghostParent.RemoveChild(lkr, node); err != nil {
			return nil, err
		}

		// Act like there was no previous node.
		// New node will have a different Inode.
		file = nil
	} else if node != nil {
		var ok bool
		file, ok = node.(*n.File)
		if !ok {
			return nil, ie.ErrBadNode
		}
	}

	needRemove := false

	if file != nil {
		// We know this file already.
		log.WithFields(log.Fields{"file": repoPath}).Info("File exists; modifying.")
		needRemove = true

		if file.Content().Equal(info.Hash) {
			log.Debugf("Hash was not modified. Not doing any update.")
			return file, nil
		}
	} else {
		parent, err := mkdirParents(lkr, repoPath)
		if err != nil {
			return nil, err
		}

		// Create a new file at specified path:
		file, err = n.NewEmptyFile(parent, path.Base(repoPath), lkr.NextInode())
		if err != nil {
			return nil, err
		}
	}

	parentDir, err := n.ParentDirectory(lkr, file)
	if err != nil {
		return nil, err
	}

	if parentDir == nil {
		return nil, fmt.Errorf("%s has no parent yet (BUG)", repoPath)
	}

	if needRemove {
		// Remove the child before changing the hash:
		if err := parentDir.RemoveChild(lkr, file); err != nil {
			return nil, err
		}
	}

	file.SetSize(info.Size)
	file.SetModTime(time.Now())
	file.SetContent(lkr, info.Hash)
	file.SetKey(info.Key)

	// Add it again when the hash was changed.
	if err := parentDir.Add(lkr, file); err != nil {
		return nil, err
	}

	if err := lkr.StageNode(file); err != nil {
		return nil, err
	}

	return file, nil
}

func Log(lkr *Linker, fn func(cmt *n.Commit) error) error {
	curr, err := lkr.Status()
	if err != nil {
		return err
	}

	for curr != nil {
		if err := fn(curr); err != nil {
			return err
		}

		parent, err := curr.Parent(lkr)
		if err != nil {
			return err
		}

		if parent == nil {
			break
		}

		parentCmt, ok := parent.(*n.Commit)
		if !ok {
			return ie.ErrBadNode
		}

		curr = parentCmt
	}

	return nil
}
<commit_msg>catfs: core: apply same dest name fix as for Move() for Copy()<commit_after>package core

import (
	"errors"
	"fmt"
	"path"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	e "github.com/pkg/errors"
	ie "github.com/sahib/brig/catfs/errors"
	n "github.com/sahib/brig/catfs/nodes"
	h "github.com/sahib/brig/util/hashlib"
)

var (
	ErrIsGhost = errors.New("Is a ghost")
)

// mkdirParents takes the dirname of repoPath and makes sure all intermediate
// directories are created. The last directory will be returned.

// If any directory exist already, it will not be touched.
// You can also think of it as mkdir -p.
func mkdirParents(lkr *Linker, repoPath string) (*n.Directory, error) {
	repoPath = path.Clean(repoPath)

	elems := strings.Split(repoPath, "/")
	for idx := 0; idx < len(elems)-1; idx++ {
		dirname := strings.Join(elems[:idx+1], "/")
		if dirname == "" {
			dirname = "/"
		}

		dir, err := Mkdir(lkr, dirname, false)
		if err != nil {
			return nil, err
		}

		// Return it, if it's the last path component:
		if idx+1 == len(elems)-1 {
			return dir, nil
		}
	}

	return nil, fmt.Errorf("Empty path given")
}

// Mkdir creates the directory at repoPath and any intermediate directories if
// createParents is true. It will fail if there is already a file at `repoPath`
// and it is not a directory.
func Mkdir(lkr *Linker, repoPath string, createParents bool) (dir *n.Directory, err error) {
	dirname, basename := path.Split(repoPath)

	// Take special care of the root node:
	if basename == "" {
		return lkr.Root()
	}

	// Check if the parent exists:
	parent, err := lkr.LookupDirectory(dirname)
	if err != nil && !ie.IsNoSuchFileError(err) {
		return nil, e.Wrap(err, "dirname lookup failed")
	}

	log.Debugf("mkdir: %s", dirname)

	// If it's nil, we might need to create it:
	if parent == nil {
		if !createParents {
			return nil, ie.NoSuchFile(dirname)
		}

		parent, err = mkdirParents(lkr, repoPath)
		if err != nil {
			return nil, err
		}
	}

	child, err := parent.Child(lkr, basename)
	if err != nil {
		return nil, err
	}

	if child != nil {
		switch child.Type() {
		case n.NodeTypeDirectory:
			// Nothing to do really. Return the old child.
			return child.(*n.Directory), nil
		case n.NodeTypeFile:
			return nil, fmt.Errorf("`%s` exists and is a file", repoPath)
		case n.NodeTypeGhost:
			// Remove the ghost and continue with adding:
			if err := parent.RemoveChild(lkr, child); err != nil {
				return nil, err
			}
		default:
			return nil, ie.ErrBadNode
		}
	}

	// Make sure, NextInode() and StageNode is written in one batch.
	batch := lkr.kv.Batch()
	defer func() {
		if err != nil {
			batch.Rollback()
		} else {
			err = batch.Flush()
		}
	}()

	// Create it then!
	dir, err = n.NewEmptyDirectory(lkr, parent, basename, lkr.NextInode())
	if err != nil {
		return nil, err
	}

	if err := lkr.StageNode(dir); err != nil {
		return nil, e.Wrapf(err, "stage dir")
	}

	return dir, nil
}

// Remove removes a single node from a directory.
// `nd` is the node that shall be removed and may not be root.
// The parent directory is returned.
func Remove(lkr *Linker, nd n.ModNode, createGhost, force bool) (parentDir *n.Directory, ghost *n.Ghost, err error) {
	if !force && nd.Type() == n.NodeTypeGhost {
		return nil, nil, ErrIsGhost
	}

	parentDir, err = n.ParentDirectory(lkr, nd)
	if err != nil {
		return nil, nil, err
	}

	// We shouldn't delete the root directory
	// (only directory with a parent)
	if parentDir == nil {
		return nil, nil, fmt.Errorf("Refusing to delete /")
	}

	if err := parentDir.RemoveChild(lkr, nd); err != nil {
		return nil, nil, fmt.Errorf("Failed to remove child: %v", err)
	}

	lkr.MemIndexPurge(nd)

	// Make sure both StageNode() will be written in one batch:
	batch := lkr.kv.Batch()
	defer func() {
		if err != nil {
			batch.Rollback()
		} else {
			err = batch.Flush()
		}
	}()

	if err := lkr.StageNode(parentDir); err != nil {
		return nil, nil, err
	}

	if createGhost {
		ghost, err := n.MakeGhost(nd, lkr.NextInode())
		if err != nil {
			return nil, nil, err
		}

		parentDir.Add(lkr, ghost)
		if err != nil {
			return nil, nil, err
		}

		if err := lkr.StageNode(ghost); err != nil {
			return nil, nil, err
		}

		return parentDir, ghost, nil
	}

	return parentDir, nil, nil
}

// prepareParent tries to figure out the correct parent directory when attempting
// to move `nd` to `dstPath`. It also removes any nodes that are "in the way" if possible.
func prepareParent(lkr *Linker, nd n.ModNode, dstPath string) (*n.Directory, error) {
	// Check if the destination already exists:
	destNode, err := lkr.LookupModNode(dstPath)
	if err != nil && !ie.IsNoSuchFileError(err) {
		return nil, err
	}

	if destNode == nil {
		// No node at this place yet, attempt to look it up.
		return lkr.LookupDirectory(path.Dir(dstPath))
	}

	switch destNode.Type() {
	case n.NodeTypeDirectory:
		// Move inside of this directory.
		// Check if there is already a file
		destDir, ok := destNode.(*n.Directory)
		if !ok {
			return nil, ie.ErrBadNode
		}

		child, err := destDir.Child(lkr, nd.Name())
		if err != nil {
			return nil, err
		}

		// Oh, something is in there?
		if child != nil {
			if nd.Type() == n.NodeTypeFile {
				// TODO: more details
				return nil, fmt.Errorf("Cannot overwrite a directory with a file")
			}

			childDir, ok := child.(*n.Directory)
			if !ok {
				return nil, ie.ErrBadNode
			}

			if childDir.Size() > 0 {
				return nil, fmt.Errorf("Cannot move over: %s; directory is not empty!", child.Path())
			}

			// Okay, there is an empty directory. Let's remove it to
			// replace it with our source node.
			log.Warningf("Remove child dir: %v", childDir)
			if _, _, err := Remove(lkr, childDir, false, false); err != nil {
				return nil, err
			}
		}

		return destDir, nil
	case n.NodeTypeFile:
		log.Warningf("Remove file: %v", destNode.Path())
		parentDir, _, err := Remove(lkr, destNode, false, false)
		return parentDir, err
	case n.NodeTypeGhost:
		// It is already a ghost. Overwrite it and do not create a new one.
		log.Warningf("Remove ghost: %v", destNode.Path())
		parentDir, _, err := Remove(lkr, destNode, false, true)
		return parentDir, err
	default:
		return nil, ie.ErrBadNode
	}
}

func Copy(lkr *Linker, nd n.ModNode, dstPath string) (newNode n.ModNode, err error) {
	// Forbid moving a node inside of one of it's subdirectories.
	if nd.Path() == dstPath {
		return nil, fmt.Errorf("Source and Dest are the same file: %v", dstPath)
	}

	if strings.HasPrefix(path.Dir(dstPath), nd.Path()) {
		return nil, fmt.Errorf(
			"Cannot move `%s` into it's own subdir `%s`",
			nd.Path(),
			dstPath,
		)
	}

	// Make sure both StageNode() will be written in one batch:
	batch := lkr.kv.Batch()
	defer func() {
		if err != nil {
			batch.Rollback()
		} else {
			err = batch.Flush()
		}
	}()

	parentDir, err := prepareParent(lkr, nd, dstPath)
	if err != nil {
		return nil, e.Wrapf(err, "handle parent")
	}

	// We might copy something into a directory.
	// In this case, dstPath specifies the directory we move into,
	// not the file we moved to (which we need here)
	if parentDir.Path() == dstPath {
		dstPath = path.Join(parentDir.Path(), path.Base(nd.Path()))
	}

	// And add it to the right destination dir:
	newNode = nd.Copy(lkr.NextInode())

	newNode.SetName(path.Base(dstPath))
	newNode.NotifyMove(lkr, newNode.Path())

	if err := parentDir.Add(lkr, newNode); err != nil {
		return nil, e.Wrapf(err, "parent add")
	}

	if err := lkr.StageNode(newNode); err != nil {
		return nil, err
	}

	return newNode, nil
}

func Move(lkr *Linker, nd n.ModNode, dstPath string) (err error) {
	// Forbid moving a node inside of one of it's subdirectories.
	if nd.Path() == dstPath {
		return fmt.Errorf("Source and Dest are the same file: %v", dstPath)
	}

	if strings.HasPrefix(path.Dir(dstPath), nd.Path()) {
		return fmt.Errorf(
			"Cannot move `%s` into it's own subdir `%s`",
			nd.Path(),
			dstPath,
		)
	}

	// Make sure both StageNode() will be written in one batch:
	batch := lkr.kv.Batch()
	defer func() {
		if err != nil {
			batch.Rollback()
		} else {
			err = batch.Flush()
		}
	}()

	parentDir, err := prepareParent(lkr, nd, dstPath)
	if err != nil {
		return err
	}

	// Remove the old node:
	oldPath := nd.Path()
	_, ghost, err := Remove(lkr, nd, true, true)
	if err != nil {
		return e.Wrapf(err, "remove old")
	}

	if parentDir.Path() == dstPath {
		dstPath = path.Join(parentDir.Path(), path.Base(oldPath))
	}

	// The node needs to be told that it's path changed,
	// since it might need to change it's hash value now.
	if err := nd.NotifyMove(lkr, dstPath); err != nil {
		return e.Wrapf(err, "notify move")
	}

	// And add it to the right destination dir:
	if err := parentDir.Add(lkr, nd); err != nil {
		return e.Wrapf(err, "parent add")
	}

	err = n.Walk(lkr, nd, true, func(child n.Node) error {
		return e.Wrapf(lkr.StageNode(child), "stage node")
	})

	if err != nil {
		return err
	}

	if err := lkr.AddMoveMapping(nd, ghost); err != nil {
		return e.Wrapf(err, "add move mapping")
	}

	return err
}

// TODO: This struct sucks.
type NodeUpdate struct {
	Hash   h.Hash
	Size   uint64
	Author string
	Key    []byte
}

func Stage(lkr *Linker, repoPath string, info *NodeUpdate) (file *n.File, err error) {
	node, err := lkr.LookupNode(repoPath)
	if err != nil && !ie.IsNoSuchFileError(err) {
		return nil, err
	}

	batch := lkr.kv.Batch()
	defer func() {
		if err != nil {
			batch.Rollback()
		} else {
			err = batch.Flush()
		}
	}()

	if node != nil && node.Type() == n.NodeTypeGhost {
		ghostParent, err := n.ParentDirectory(lkr, node)
		if err != nil {
			return nil, err
		}

		if ghostParent == nil {
			// TODO: Think about this case. stage() should not be called on directories
			//       anyways (only on files or files to ghosts)
			return nil, fmt.Errorf("The ghost is a root? Something is wrong...")
		}

		if err := ghostParent.RemoveChild(lkr, node); err != nil {
			return nil, err
		}

		// Act like there was no previous node.
		// New node will have a different Inode.
		file = nil
	} else if node != nil {
		var ok bool
		file, ok = node.(*n.File)
		if !ok {
			return nil, ie.ErrBadNode
		}
	}

	needRemove := false

	if file != nil {
		// We know this file already.
		log.WithFields(log.Fields{"file": repoPath}).Info("File exists; modifying.")
		needRemove = true

		if file.Content().Equal(info.Hash) {
			log.Debugf("Hash was not modified. Not doing any update.")
			return file, nil
		}
	} else {
		parent, err := mkdirParents(lkr, repoPath)
		if err != nil {
			return nil, err
		}

		// Create a new file at specified path:
		file, err = n.NewEmptyFile(parent, path.Base(repoPath), lkr.NextInode())
		if err != nil {
			return nil, err
		}
	}

	parentDir, err := n.ParentDirectory(lkr, file)
	if err != nil {
		return nil, err
	}

	if parentDir == nil {
		return nil, fmt.Errorf("%s has no parent yet (BUG)", repoPath)
	}

	if needRemove {
		// Remove the child before changing the hash:
		if err := parentDir.RemoveChild(lkr, file); err != nil {
			return nil, err
		}
	}

	file.SetSize(info.Size)
	file.SetModTime(time.Now())
	file.SetContent(lkr, info.Hash)
	file.SetKey(info.Key)

	// Add it again when the hash was changed.
	if err := parentDir.Add(lkr, file); err != nil {
		return nil, err
	}

	if err := lkr.StageNode(file); err != nil {
		return nil, err
	}

	return file, nil
}

func Log(lkr *Linker, fn func(cmt *n.Commit) error) error {
	curr, err := lkr.Status()
	if err != nil {
		return err
	}

	for curr != nil {
		if err := fn(curr); err != nil {
			return err
		}

		parent, err := curr.Parent(lkr)
		if err != nil {
			return err
		}

		if parent == nil {
			break
		}

		parentCmt, ok := parent.(*n.Commit)
		if !ok {
			return ie.ErrBadNode
		}

		curr = parentCmt
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>

Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/

package resize

import (
	"image"
	"image/color"
	"math"
)

// restrict an input float32 to the range of uint16 values
func clampToUint16(x float32) (y uint16) {
	y = uint16(x)
	if x < 0 {
		y = 0
	} else if x > float32(0xfffe) {
		// "else if x > float32(0xffff)" will cause overflows!
		y = 0xffff
	}

	return
}

// describe a resampling filter
type filterModel struct {
	// resampling is done by convolution with a (scaled) kernel
	kernel func(float32) float32

	// instead of blurring an image before downscaling to avoid aliasing,
	// the filter is scaled by a factor which leads to a similar effect
	factor [2]float32

	// for optimized access to image points
	converter

	// temporaries used by Interpolate
	tempRow, tempCol []colorArray
}

func (f *filterModel) convolution1d(x float32, p []colorArray, factor float32) colorArray {
	var k float32
	var sum float32 = 0
	c := colorArray{0.0, 0.0, 0.0, 0.0}

	for j := range p {
		k = f.kernel((x - float32(j)) / factor)
		sum += k
		for i := range c {
			c[i] += p[j][i] * k
		}
	}

	// normalize values
	for i := range c {
		c[i] = c[i] / sum
	}

	return c
}

func (f *filterModel) Interpolate(x, y float32) color.RGBA64 {
	xf, yf := int(x)-len(f.tempRow)/2+1, int(y)-len(f.tempCol)/2+1
	x -= float32(xf)
	y -= float32(yf)

	for i := range f.tempCol {
		for j := range f.tempRow {
			f.tempRow[j] = f.at(xf+j, yf+i)
		}

		f.tempCol[i] = f.convolution1d(x, f.tempRow, f.factor[0])
	}

	c := f.convolution1d(y, f.tempCol, f.factor[1])
	return color.RGBA64{
		clampToUint16(c[0]),
		clampToUint16(c[1]),
		clampToUint16(c[2]),
		clampToUint16(c[3]),
	}
}

// createFilter tries to find an optimized converter for the given input image
// and initializes all filterModel members to their defaults
func createFilter(img image.Image, factor [2]float32, size int, kernel func(float32) float32) (f Filter) {
	sizeX := size * (int(math.Ceil(float64(factor[0]))))
	sizeY := size * (int(math.Ceil(float64(factor[1]))))

	switch img.(type) {
	default:
		f = &filterModel{
			kernel, factor,
			&genericConverter{img},
			make([]colorArray, sizeX), make([]colorArray, sizeY),
		}
	case *image.RGBA:
		f = &filterModel{
			kernel, factor,
			&rgbaConverter{img.(*image.RGBA)},
			make([]colorArray, sizeX), make([]colorArray, sizeY),
		}
	case *image.RGBA64:
		f = &filterModel{
			kernel, factor,
			&rgba64Converter{img.(*image.RGBA64)},
			make([]colorArray, sizeX), make([]colorArray, sizeY),
		}
	case *image.Gray:
		f = &filterModel{
			kernel, factor,
			&grayConverter{img.(*image.Gray)},
			make([]colorArray, sizeX), make([]colorArray, sizeY),
		}
	case *image.Gray16:
		f = &filterModel{
			kernel, factor,
			&gray16Converter{img.(*image.Gray16)},
			make([]colorArray, sizeX), make([]colorArray, sizeY),
		}
	case *image.YCbCr:
		f = &filterModel{
			kernel, factor,
			&ycbcrConverter{img.(*image.YCbCr)},
			make([]colorArray, sizeX), make([]colorArray, sizeY),
		}
	}

	return
}

// Return a filter kernel that performs nearly identically to the provided
// kernel, but generates and uses a precomputed table rather than executing
// the kernel for each evaluation. The table is generated with tableSize
// values that cover the kernal domain from -maxX to +maxX. The input kernel
// is assumed to be symmetrical around 0, so the table only includes values
// from 0 to maxX.
func tableKernel(kernel func(float32) float32, tableSize int,
	maxX float32) func(float32) float32 {

	// precompute an array of filter coefficients
	weights := make([]float32, tableSize+1)
	for i := range weights {
		weights[i] = kernel(maxX * float32(i) / float32(tableSize))
	}
	weights[tableSize] = 0.0

	return func(x float32) float32 {
		if x < 0.0 {
			x = -x
		}
		indf := x / maxX * float32(tableSize)
		ind := int(indf)
		if ind >= tableSize {
			return 0.0
		}
		return weights[ind] + (weights[ind+1]-weights[ind])*(indf-float32(ind))
	}
}

// Nearest-neighbor interpolation
func NearestNeighbor(img image.Image, factor [2]float32) Filter {
	return createFilter(img, factor, 2, func(x float32) (y float32) {
		if x >= -0.5 && x < 0.5 {
			y = 1
		} else {
			y = 0
		}

		return
	})
}

// Bilinear interpolation
func Bilinear(img image.Image, factor [2]float32) Filter {
	return createFilter(img, factor, 2, func(x float32) (y float32) {
		absX := float32(math.Abs(float64(x)))
		if absX <= 1 {
			y = 1 - absX
		} else {
			y = 0
		}

		return
	})
}

func splineKernel(B, C float32) func(float32) float32 {
	factorA := 2.0 - 1.5*B - C
	factorB := -3.0 + 2.0*B + C
	factorC := 1.0 - 1.0/3.0*B
	factorD := -B/6.0 - C
	factorE := B + 5.0*C
	factorF := -2.0*B - 8.0*C
	factorG := 4.0/3.0*B + 4.0*C
	return func(x float32) (y float32) {
		absX := float32(math.Abs(float64(x)))
		if absX <= 1 {
			y = absX*absX*(factorA*absX+factorB) + factorC
		} else if absX <= 2 {
			y = absX*(absX*(absX*factorD+factorE)+factorF) + factorG
		} else {
			y = 0
		}

		return
	}
}

// Bicubic interpolation (with cubic hermite spline)
func Bicubic(img image.Image, factor [2]float32) Filter {
	return createFilter(img, factor, 4, splineKernel(0, 0.5))
}

// Mitchell-Netravali interpolation
func MitchellNetravali(img image.Image, factor [2]float32) Filter {
	return createFilter(img, factor, 4, splineKernel(1.0/3.0, 1.0/3.0))
}

func lanczosKernel(a uint) func(float32) float32 {
	return func(x float32) (y float32) {
		if x > -float32(a) && x < float32(a) {
			y = float32(Sinc(float64(x))) * float32(Sinc(float64(x/float32(a))))
		} else {
			y = 0
		}

		return
	}
}

const lanczosTableSize = 300

// Lanczos interpolation (a=2)
func Lanczos2(img image.Image, factor [2]float32) Filter {
	return createFilter(img, factor, 4,
		tableKernel(lanczosKernel(2), lanczosTableSize, 2.0))
}

// Lanczos interpolation (a=3)
func Lanczos3(img image.Image, factor [2]float32) Filter {
	return createFilter(img, factor, 6,
		tableKernel(lanczosKernel(3), lanczosTableSize, 3.0))
}
<commit_msg>Move splineKernel function nearer to Lanczos definitions.<commit_after>/*
Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>

Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/

package resize

import (
	"image"
	"image/color"
	"math"
)

// restrict an input float32 to the range of uint16 values
func clampToUint16(x float32) (y uint16) {
	y = uint16(x)
	if x < 0 {
		y = 0
	} else if x > float32(0xfffe) {
		// "else if x > float32(0xffff)" will cause overflows!
		y = 0xffff
	}

	return
}

// describe a resampling filter
type filterModel struct {
	// resampling is done by convolution with a (scaled) kernel
	kernel func(float32) float32

	// instead of blurring an image before downscaling to avoid aliasing,
	// the filter is scaled by a factor which leads to a similar effect
	factor [2]float32

	// for optimized access to image points
	converter

	// temporaries used by Interpolate
	tempRow, tempCol []colorArray
}

func (f *filterModel) convolution1d(x float32, p []colorArray, factor float32) colorArray {
	var k float32
	var sum float32 = 0
	c := colorArray{0.0, 0.0, 0.0, 0.0}

	for j := range p {
		k = f.kernel((x - float32(j)) / factor)
		sum += k
		for i := range c {
			c[i] += p[j][i] * k
		}
	}

	// normalize values
	for i := range c {
		c[i] = c[i] / sum
	}

	return c
}

func (f *filterModel) Interpolate(x, y float32) color.RGBA64 {
	xf, yf := int(x)-len(f.tempRow)/2+1, int(y)-len(f.tempCol)/2+1
	x -= float32(xf)
	y -= float32(yf)

	for i := range f.tempCol {
		for j := range f.tempRow {
			f.tempRow[j] = f.at(xf+j, yf+i)
		}

		f.tempCol[i] = f.convolution1d(x, f.tempRow, f.factor[0])
	}

	c := f.convolution1d(y, f.tempCol, f.factor[1])
	return color.RGBA64{
		clampToUint16(c[0]),
		clampToUint16(c[1]),
		clampToUint16(c[2]),
		clampToUint16(c[3]),
	}
}

// createFilter tries to find an optimized converter for the given input image
// and initializes all filterModel members to their defaults
func createFilter(img image.Image, factor [2]float32, size int, kernel func(float32) float32) (f Filter) {
	sizeX := size * (int(math.Ceil(float64(factor[0]))))
	sizeY := size * (int(math.Ceil(float64(factor[1]))))

	switch img.(type) {
	default:
		f = &filterModel{
			kernel, factor,
			&genericConverter{img},
			make([]colorArray, sizeX), make([]colorArray, sizeY),
		}
	case *image.RGBA:
		f = &filterModel{
			kernel, factor,
			&rgbaConverter{img.(*image.RGBA)},
			make([]colorArray, sizeX), make([]colorArray, sizeY),
		}
	case *image.RGBA64:
		f = &filterModel{
			kernel, factor,
			&rgba64Converter{img.(*image.RGBA64)},
			make([]colorArray, sizeX), make([]colorArray, sizeY),
		}
	case *image.Gray:
		f = &filterModel{
			kernel, factor,
			&grayConverter{img.(*image.Gray)},
			make([]colorArray, sizeX), make([]colorArray, sizeY),
		}
	case *image.Gray16:
		f = &filterModel{
			kernel, factor,
			&gray16Converter{img.(*image.Gray16)},
			make([]colorArray, sizeX), make([]colorArray, sizeY),
		}
	case *image.YCbCr:
		f = &filterModel{
			kernel, factor,
			&ycbcrConverter{img.(*image.YCbCr)},
			make([]colorArray, sizeX), make([]colorArray, sizeY),
		}
	}

	return
}

// Return a filter kernel that performs nearly identically to the provided
// kernel, but generates and uses a precomputed table rather than executing
// the kernel for each evaluation. The table is generated with tableSize
// values that cover the kernal domain from -maxX to +maxX. The input kernel
// is assumed to be symmetrical around 0, so the table only includes values
// from 0 to maxX.
func tableKernel(kernel func(float32) float32, tableSize int,
	maxX float32) func(float32) float32 {

	// precompute an array of filter coefficients
	weights := make([]float32, tableSize+1)
	for i := range weights {
		weights[i] = kernel(maxX * float32(i) / float32(tableSize))
	}
	weights[tableSize] = 0.0

	return func(x float32) float32 {
		if x < 0.0 {
			x = -x
		}
		indf := x / maxX * float32(tableSize)
		ind := int(indf)
		if ind >= tableSize {
			return 0.0
		}
		return weights[ind] + (weights[ind+1]-weights[ind])*(indf-float32(ind))
	}
}

// Nearest-neighbor interpolation
func NearestNeighbor(img image.Image, factor [2]float32) Filter {
	return createFilter(img, factor, 2, func(x float32) (y float32) {
		if x >= -0.5 && x < 0.5 {
			y = 1
		} else {
			y = 0
		}

		return
	})
}

// Bilinear interpolation
func Bilinear(img image.Image, factor [2]float32) Filter {
	return createFilter(img, factor, 2, func(x float32) (y float32) {
		absX := float32(math.Abs(float64(x)))
		if absX <= 1 {
			y = 1 - absX
		} else {
			y = 0
		}

		return
	})
}

// Bicubic interpolation (with cubic hermite spline)
func Bicubic(img image.Image, factor [2]float32) Filter {
	return createFilter(img, factor, 4, splineKernel(0, 0.5))
}

// Mitchell-Netravali interpolation
func MitchellNetravali(img image.Image, factor [2]float32) Filter {
	return createFilter(img, factor, 4, splineKernel(1.0/3.0, 1.0/3.0))
}

func splineKernel(B, C float32) func(float32) float32 {
	const lanczosTableSize = 300

	factorA := 2.0 - 1.5*B - C
	factorB := -3.0 + 2.0*B + C
	factorC := 1.0 - 1.0/3.0*B
	factorD := -B/6.0 - C
	factorE := B + 5.0*C
	factorF := -2.0*B - 8.0*C
	factorG := 4.0/3.0*B + 4.0*C
	return func(x float32) (y float32) {
		absX := float32(math.Abs(float64(x)))
		if absX <= 1 {
			y = absX*absX*(factorA*absX+factorB) + factorC
		} else if absX <= 2 {
			y = absX*(absX*(absX*factorD+factorE)+factorF) + factorG
		} else {
			y = 0
		}

		return
	}
}

func lanczosKernel(a uint) func(float32) float32 {
	return func(x float32) (y float32) {
		if x > -float32(a) && x < float32(a) {
			y = float32(Sinc(float64(x))) * float32(Sinc(float64(x/float32(a))))
		} else {
			y = 0
		}

		return
	}
}

// Lanczos interpolation (a=2)
func Lanczos2(img image.Image, factor [2]float32) Filter {
	return createFilter(img, factor, 4,
		tableKernel(lanczosKernel(2), lanczosTableSize, 2.0))
}

// Lanczos interpolation (a=3)
func Lanczos3(img image.Image, factor [2]float32) Filter {
	return createFilter(img, factor, 6,
		tableKernel(lanczosKernel(3), lanczosTableSize, 3.0))
}
<|endoftext|>


<commit_before>package elements

import "errors"

type Constructor func() Element
type Factory map[string]Constructor

func NewFactory() Factory {
	return make(Factory)
}

func (self Factory) AddConstructor(key string, constructor Constructor) {
	self[key] = constructor
}

func (self Factory) Create(key string) (Element, error) {
	if constructor, ok := self[key]; ok {
		return constructor(), nil
	}

	// This is default constructor if defined
	if constructor, ok := self["*"]; ok {
		return constructor(), nil
	}
	return nil, errors.New("Wrong key " + key)
}
<commit_msg>Add some comments<commit_after>package elements

import "errors"

// Create an (empty) Element to unmarshall XML into
type Constructor func() Element

// Maintain a mapping between tag names and Constructors
type Factory map[string]Constructor

func NewFactory() Factory {
	return make(Factory)
}

func (self Factory) AddConstructor(key string, constructor Constructor) {
	self[key] = constructor
}

// Call a constructor for specified key or "*", if defined. Otherwise return an error
func (self Factory) Create(key string) (Element, error) {
	if constructor, ok := self[key]; ok {
		return constructor(), nil
	}

	// This is default constructor if defined
	if constructor, ok := self["*"]; ok {
		return constructor(), nil
	}
	return nil, errors.New("Wrong key " + key)
}
<|endoftext|>


<commit_before>package mailbox

import (
	"sync"
	"testing"
)

var (
	testSet   = getList(8192)
	testVal   int
	testBatch = getList(512)

	bufSize = 128
)

func TestMailbox(t *testing.T) {
	var wg sync.WaitGroup
	var cnt int
	wg.Add(2)
	mb := New(bufSize)

	go func() {
		mb.Listen(func(item interface{}) (end bool) {
			var (
				v  int
				ok bool
			)

			if v, ok = item.(int); !ok {
				panic("Whoa")
			}

			testVal = v
			cnt++
			return
		})

		wg.Done()
	}()

	go func() {
		for _, si := range testSet {
			mb.Send(si)
		}
		mb.Close()
		wg.Done()
	}()

	wg.Wait()
	if cnt != len(testSet) {
		t.Fatal("Errr cnt", cnt)
	}
}

func BenchmarkMailbox(b *testing.B) {
	var rwg sync.WaitGroup
	mb := New(bufSize)

	go func() {
		var (
			v  int
			ok bool
		)

		rwg.Add(1)
		mb.Listen(func(item interface{}) (end bool) {

			if v, ok = item.(int); !ok {
				panic("Whoa")
			}

			testVal = v
			return
		})
		rwg.Done()
	}()

	b.RunParallel(func(pb *testing.PB) {
		var i int
		for pb.Next() {
			mb.Send(i)
			i++
		}
	})

	mb.Close()
	rwg.Wait()

	b.ReportAllocs()
}

func BenchmarkChannel(b *testing.B) {
	var rwg sync.WaitGroup
	ch := make(chan interface{}, bufSize)

	go func() {
		var (
			v  int
			ok bool
		)

		rwg.Add(1)

		for item := range ch {
			if v, ok = item.(int); !ok {
				panic("Whoa")
			}

			testVal = v
		}

		rwg.Done()
	}()

	b.RunParallel(func(pb *testing.PB) {
		var i int
		for pb.Next() {
			ch <- i
			i++
		}
	})

	close(ch)
	rwg.Wait()

	b.ReportAllocs()
}

func BenchmarkBatchMailbox(b *testing.B) {
	var rwg sync.WaitGroup
	mb := New(bufSize)

	go func() {
		rwg.Add(1)
		mb.Listen(func(item interface{}) (end bool) {
			var (
				v  int
				ok bool
			)

			if v, ok = item.(int); !ok {
				panic("Whoa")
			}

			testVal = v
			return
		})
		rwg.Done()
	}()

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			mb.Batch(testBatch...)
		}
	})

	mb.Close()
	rwg.Wait()

	b.ReportAllocs()
}

func BenchmarkBatchChannel(b *testing.B) {
	var rwg sync.WaitGroup
	ch := make(chan interface{}, bufSize)

	go func() {
		rwg.Add(1)

		for item := range ch {
			var (
				v  int
				ok bool
			)

			if v, ok = item.(int); !ok {
				panic("Whoa")
			}

			testVal = v
		}

		rwg.Done()
	}()

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			for _, i := range testBatch {
				ch <- i
			}
		}
	})

	close(ch)
	rwg.Wait()

	b.ReportAllocs()
}

func getList(n int) (l []interface{}) {
	l = make([]interface{}, n)
	for i := range l {
		l[i] = i
	}

	return
}
<commit_msg>OCD cleanup for test variables<commit_after>package mailbox

import (
	"sync"
	"testing"
)

var (
	testVal int

	testBufSize = 128
	testSet     = getList(8192)
	testBatch   = getList(512)
)

func TestMailbox(t *testing.T) {
	var wg sync.WaitGroup
	var cnt int
	wg.Add(2)
	mb := New(testBufSize)

	go func() {
		mb.Listen(func(item interface{}) (end bool) {
			var (
				v  int
				ok bool
			)

			if v, ok = item.(int); !ok {
				panic("Whoa")
			}

			testVal = v
			cnt++
			return
		})

		wg.Done()
	}()

	go func() {
		for _, si := range testSet {
			mb.Send(si)
		}
		mb.Close()
		wg.Done()
	}()

	wg.Wait()
	if cnt != len(testSet) {
		t.Fatal("Errr cnt", cnt)
	}
}

func BenchmarkMailbox(b *testing.B) {
	var rwg sync.WaitGroup
	mb := New(testBufSize)

	go func() {
		var (
			v  int
			ok bool
		)

		rwg.Add(1)
		mb.Listen(func(item interface{}) (end bool) {

			if v, ok = item.(int); !ok {
				panic("Whoa")
			}

			testVal = v
			return
		})
		rwg.Done()
	}()

	b.RunParallel(func(pb *testing.PB) {
		var i int
		for pb.Next() {
			mb.Send(i)
			i++
		}
	})

	mb.Close()
	rwg.Wait()

	b.ReportAllocs()
}

func BenchmarkChannel(b *testing.B) {
	var rwg sync.WaitGroup
	ch := make(chan interface{}, testBufSize)

	go func() {
		var (
			v  int
			ok bool
		)

		rwg.Add(1)

		for item := range ch {
			if v, ok = item.(int); !ok {
				panic("Whoa")
			}

			testVal = v
		}

		rwg.Done()
	}()

	b.RunParallel(func(pb *testing.PB) {
		var i int
		for pb.Next() {
			ch <- i
			i++
		}
	})

	close(ch)
	rwg.Wait()

	b.ReportAllocs()
}

func BenchmarkBatchMailbox(b *testing.B) {
	var rwg sync.WaitGroup
	mb := New(testBufSize)

	go func() {
		rwg.Add(1)
		mb.Listen(func(item interface{}) (end bool) {
			var (
				v  int
				ok bool
			)

			if v, ok = item.(int); !ok {
				panic("Whoa")
			}

			testVal = v
			return
		})
		rwg.Done()
	}()

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			mb.Batch(testBatch...)
		}
	})

	mb.Close()
	rwg.Wait()

	b.ReportAllocs()
}

func BenchmarkBatchChannel(b *testing.B) {
	var rwg sync.WaitGroup
	ch := make(chan interface{}, testBufSize)

	go func() {
		rwg.Add(1)

		for item := range ch {
			var (
				v  int
				ok bool
			)

			if v, ok = item.(int); !ok {
				panic("Whoa")
			}

			testVal = v
		}

		rwg.Done()
	}()

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			for _, i := range testBatch {
				ch <- i
			}
		}
	})

	close(ch)
	rwg.Wait()

	b.ReportAllocs()
}

func getList(n int) (l []interface{}) {
	l = make([]interface{}, n)
	for i := range l {
		l[i] = i
	}

	return
}
<|endoftext|>