<commit_before>package service

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"log"
	"strings"
	"time"
)

// ErrCommentNotFound denotes that the comment was not found.
var ErrCommentNotFound = errors.New("comment not found")

// Comment model.
type Comment struct {
	ID         int64     `json:"id"`
	UserID     int64     `json:"-"`
	PostID     int64     `json:"-"`
	Content    string    `json:"content"`
	LikesCount int       `json:"likesCount"`
	CreatedAt  time.Time `json:"createdAt"`
	User       *User     `json:"user,omitempty"`
	Mine       bool      `json:"mine"`
	Liked      bool      `json:"liked"`
}

// CreateComment on a post.
func (s *Service) CreateComment(ctx context.Context, postID int64, content string) (Comment, error) {
	var c Comment
	uid, ok := ctx.Value(KeyAuthUserID).(int64)
	if !ok {
		return c, ErrUnauthenticated
	}

	content = strings.TrimSpace(content)
	if content == "" || len([]rune(content)) > 480 {
		return c, ErrInvalidContent
	}

	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return c, fmt.Errorf("could not begin tx: %v", err)
	}

	defer tx.Rollback()

	query := `
		INSERT INTO comments (user_id, post_id, content) VALUES ($1, $2, $3)
		RETURNING id, created_at`
	err = tx.QueryRowContext(ctx, query, uid, postID, content).Scan(&c.ID, &c.CreatedAt)
	if isForeignKeyViolation(err) {
		return c, ErrPostNotFound
	}

	if err != nil {
		return c, fmt.Errorf("could not insert comment: %v", err)
	}

	c.UserID = uid
	c.PostID = postID
	c.Content = content
	c.Mine = true

	query = `
		INSERT INTO post_subscriptions (user_id, post_id) VALUES ($1, $2)
		ON CONFLICT (user_id, post_id) DO NOTHING`
	if _, err = tx.ExecContext(ctx, query, uid, postID); err != nil {
		return c, fmt.Errorf("could not insert post subcription after comment: %v", err)
	}

	query = "UPDATE posts SET comments_count = comments_count + 1 WHERE id = $1"
	if _, err = tx.ExecContext(ctx, query, postID); err != nil {
		return c, fmt.Errorf("could not update and increment post comments count: %v", err)
	}

	if err = tx.Commit(); err != nil {
		return c, fmt.Errorf("could not commit to create comment: %v", err)
	}

	go s.commentCreated(c)

	return c, nil
}

func (s *Service) commentCreated(c Comment) {
	u, err := s.userByID(context.Background(), c.UserID)
	if err != nil {
		log.Printf("could not fetch comment user: %v\n", err)
		return
	}

	c.User = &u
	c.Mine = false

	go s.notifyComment(c)
	// TODO: broadcast comment.
}

// Comments from a post in descending order with backward pagination.
func (s *Service) Comments(ctx context.Context, postID int64, last int, before int64) ([]Comment, error) {
	uid, auth := ctx.Value(KeyAuthUserID).(int64)
	last = normalizePageSize(last)
	query, args, err := buildQuery(`
		SELECT comments.id, content, likes_count, created_at, username, avatar
		{{if .auth}}
		, comments.user_id = @uid AS mine
		, likes.user_id IS NOT NULL AS liked
		{{end}}
		FROM comments
		INNER JOIN users ON comments.user_id = users.id
		{{if .auth}}
		LEFT JOIN comment_likes AS likes
			ON likes.comment_id = comments.id AND likes.user_id = @uid
		{{end}}
		WHERE comments.post_id = @post_id
		{{if .before}}AND comments.id < @before{{end}}
		ORDER BY created_at DESC
		LIMIT @last`, map[string]interface{}{
		"auth":    auth,
		"uid":     uid,
		"post_id": postID,
		"before":  before,
		"last":    last,
	})
	if err != nil {
		return nil, fmt.Errorf("could not build comments sql query: %v", err)
	}

	rows, err := s.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("could not query select comments: %v", err)
	}

	defer rows.Close()

	cc := make([]Comment, 0, last)
	for rows.Next() {
		var c Comment
		var u User
		var avatar sql.NullString
		dest := []interface{}{&c.ID, &c.Content, &c.LikesCount, &c.CreatedAt, &u.Username, &avatar}
		if auth {
			dest = append(dest, &c.Mine, &c.Liked)
		}
		if err = rows.Scan(dest...); err != nil {
			return nil, fmt.Errorf("could not scan comment: %v", err)
		}

		if avatar.Valid {
			avatarURL := s.origin + "/img/avatars/" + avatar.String
			u.AvatarURL = &avatarURL
		}
		c.User = &u
		cc = append(cc, c)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("could not iterate comment rows: %v", err)
	}

	return cc, nil
}

// ToggleCommentLike 🖤
func (s *Service) ToggleCommentLike(ctx context.Context, commentID int64) (ToggleLikeOutput, error) {
	var out ToggleLikeOutput
	uid, ok := ctx.Value(KeyAuthUserID).(int64)
	if !ok {
		return out, ErrUnauthenticated
	}

	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return out, fmt.Errorf("could not begin tx: %v", err)
	}

	defer tx.Rollback()

	query := `
		SELECT EXISTS (
			SELECT 1 FROM comment_likes WHERE user_id = $1 AND comment_id = $2
		)`
	if err = tx.QueryRowContext(ctx, query, uid, commentID).Scan(&out.Liked); err != nil {
		return out, fmt.Errorf("could not query select comment like existence: %v", err)
	}

	if out.Liked {
		query = "DELETE FROM comment_likes WHERE user_id = $1 AND comment_id = $2"
		if _, err = tx.ExecContext(ctx, query, uid, commentID); err != nil {
			return out, fmt.Errorf("could not delete comment like: %v", err)
		}

		query = "UPDATE comments SET likes_count = likes_count - 1 WHERE id = $1 RETURNING likes_count"
		if err = tx.QueryRowContext(ctx, query, commentID).Scan(&out.LikesCount); err != nil {
			return out, fmt.Errorf("could not update and decrement comment likes count: %v", err)
		}
	} else {
		query = "INSERT INTO comment_likes (user_id, comment_id) VALUES ($1, $2)"
		_, err = tx.ExecContext(ctx, query, uid, commentID)
		if isForeignKeyViolation(err) {
			return out, ErrCommentNotFound
		}

		if err != nil {
			return out, fmt.Errorf("could not insert comment like: %v", err)
		}

		query = "UPDATE comments SET likes_count = likes_count + 1 WHERE id = $1 RETURNING likes_count"
		if err = tx.QueryRowContext(ctx, query, commentID).Scan(&out.LikesCount); err != nil {
			return out, fmt.Errorf("could not update and increment comment likes count: %v", err)
		}
	}

	if err = tx.Commit(); err != nil {
		return out, fmt.Errorf("could not commit to toggle comment like: %v", err)
	}

	out.Liked = !out.Liked

	return out, nil
}
<commit_msg>fix: error message<commit_after>package service

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"log"
	"strings"
	"time"
)

// ErrCommentNotFound denotes that the comment was not found.
var ErrCommentNotFound = errors.New("comment not found")

// Comment model.
type Comment struct {
	ID         int64     `json:"id"`
	UserID     int64     `json:"-"`
	PostID     int64     `json:"-"`
	Content    string    `json:"content"`
	LikesCount int       `json:"likesCount"`
	CreatedAt  time.Time `json:"createdAt"`
	User       *User     `json:"user,omitempty"`
	Mine       bool      `json:"mine"`
	Liked      bool      `json:"liked"`
}

// CreateComment on a post.
func (s *Service) CreateComment(ctx context.Context, postID int64, content string) (Comment, error) {
	var c Comment
	uid, ok := ctx.Value(KeyAuthUserID).(int64)
	if !ok {
		return c, ErrUnauthenticated
	}

	content = strings.TrimSpace(content)
	if content == "" || len([]rune(content)) > 480 {
		return c, ErrInvalidContent
	}

	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return c, fmt.Errorf("could not begin tx: %v", err)
	}

	defer tx.Rollback()

	query := `
		INSERT INTO comments (user_id, post_id, content) VALUES ($1, $2, $3)
		RETURNING id, created_at`
	err = tx.QueryRowContext(ctx, query, uid, postID, content).Scan(&c.ID, &c.CreatedAt)
	if isForeignKeyViolation(err) {
		return c, ErrPostNotFound
	}

	if err != nil {
		return c, fmt.Errorf("could not insert comment: %v", err)
	}

	c.UserID = uid
	c.PostID = postID
	c.Content = content
	c.Mine = true

	query = `
		INSERT INTO post_subscriptions (user_id, post_id) VALUES ($1, $2)
		ON CONFLICT (user_id, post_id) DO NOTHING`
	if _, err = tx.ExecContext(ctx, query, uid, postID); err != nil {
		return c, fmt.Errorf("could not insert post subcription after commenting: %v", err)
	}

	query = "UPDATE posts SET comments_count = comments_count + 1 WHERE id = $1"
	if _, err = tx.ExecContext(ctx, query, postID); err != nil {
		return c, fmt.Errorf("could not update and increment post comments count: %v", err)
	}

	if err = tx.Commit(); err != nil {
		return c, fmt.Errorf("could not commit to create comment: %v", err)
	}

	go s.commentCreated(c)

	return c, nil
}

func (s *Service) commentCreated(c Comment) {
	u, err := s.userByID(context.Background(), c.UserID)
	if err != nil {
		log.Printf("could not fetch comment user: %v\n", err)
		return
	}

	c.User = &u
	c.Mine = false

	go s.notifyComment(c)
	// TODO: broadcast comment.
}

// Comments from a post in descending order with backward pagination.
func (s *Service) Comments(ctx context.Context, postID int64, last int, before int64) ([]Comment, error) {
	uid, auth := ctx.Value(KeyAuthUserID).(int64)
	last = normalizePageSize(last)
	query, args, err := buildQuery(`
		SELECT comments.id, content, likes_count, created_at, username, avatar
		{{if .auth}}
		, comments.user_id = @uid AS mine
		, likes.user_id IS NOT NULL AS liked
		{{end}}
		FROM comments
		INNER JOIN users ON comments.user_id = users.id
		{{if .auth}}
		LEFT JOIN comment_likes AS likes
			ON likes.comment_id = comments.id AND likes.user_id = @uid
		{{end}}
		WHERE comments.post_id = @post_id
		{{if .before}}AND comments.id < @before{{end}}
		ORDER BY created_at DESC
		LIMIT @last`, map[string]interface{}{
		"auth":    auth,
		"uid":     uid,
		"post_id": postID,
		"before":  before,
		"last":    last,
	})
	if err != nil {
		return nil, fmt.Errorf("could not build comments sql query: %v", err)
	}

	rows, err := s.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("could not query select comments: %v", err)
	}

	defer rows.Close()

	cc := make([]Comment, 0, last)
	for rows.Next() {
		var c Comment
		var u User
		var avatar sql.NullString
		dest := []interface{}{&c.ID, &c.Content, &c.LikesCount, &c.CreatedAt, &u.Username, &avatar}
		if auth {
			dest = append(dest, &c.Mine, &c.Liked)
		}
		if err = rows.Scan(dest...); err != nil {
			return nil, fmt.Errorf("could not scan comment: %v", err)
		}

		if avatar.Valid {
			avatarURL := s.origin + "/img/avatars/" + avatar.String
			u.AvatarURL = &avatarURL
		}
		c.User = &u
		cc = append(cc, c)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("could not iterate comment rows: %v", err)
	}

	return cc, nil
}

// ToggleCommentLike 🖤
func (s *Service) ToggleCommentLike(ctx context.Context, commentID int64) (ToggleLikeOutput, error) {
	var out ToggleLikeOutput
	uid, ok := ctx.Value(KeyAuthUserID).(int64)
	if !ok {
		return out, ErrUnauthenticated
	}

	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return out, fmt.Errorf("could not begin tx: %v", err)
	}

	defer tx.Rollback()

	query := `
		SELECT EXISTS (
			SELECT 1 FROM comment_likes WHERE user_id = $1 AND comment_id = $2
		)`
	if err = tx.QueryRowContext(ctx, query, uid, commentID).Scan(&out.Liked); err != nil {
		return out, fmt.Errorf("could not query select comment like existence: %v", err)
	}

	if out.Liked {
		query = "DELETE FROM comment_likes WHERE user_id = $1 AND comment_id = $2"
		if _, err = tx.ExecContext(ctx, query, uid, commentID); err != nil {
			return out, fmt.Errorf("could not delete comment like: %v", err)
		}

		query = "UPDATE comments SET likes_count = likes_count - 1 WHERE id = $1 RETURNING likes_count"
		if err = tx.QueryRowContext(ctx, query, commentID).Scan(&out.LikesCount); err != nil {
			return out, fmt.Errorf("could not update and decrement comment likes count: %v", err)
		}
	} else {
		query = "INSERT INTO comment_likes (user_id, comment_id) VALUES ($1, $2)"
		_, err = tx.ExecContext(ctx, query, uid, commentID)
		if isForeignKeyViolation(err) {
			return out, ErrCommentNotFound
		}

		if err != nil {
			return out, fmt.Errorf("could not insert comment like: %v", err)
		}

		query = "UPDATE comments SET likes_count = likes_count + 1 WHERE id = $1 RETURNING likes_count"
		if err = tx.QueryRowContext(ctx, query, commentID).Scan(&out.LikesCount); err != nil {
			return out, fmt.Errorf("could not update and increment comment likes count: %v", err)
		}
	}

	if err = tx.Commit(); err != nil {
		return out, fmt.Errorf("could not commit to toggle comment like: %v", err)
	}

	out.Liked = !out.Liked

	return out, nil
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsEbsSnapshot() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsEbsSnapshotCreate,
		Read:   resourceAwsEbsSnapshotRead,
		Delete: resourceAwsEbsSnapshotDelete,

		Schema: map[string]*schema.Schema{
			"volume_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"description": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},
			"owner_id": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"owner_alias": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"encrypted": {
				Type:     schema.TypeBool,
				Computed: true,
			},
			"volume_size": {
				Type:     schema.TypeInt,
				Computed: true,
			},
			"kms_key_id": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"data_encryption_key_id": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"tags": {
				Type:     schema.TypeMap,
				Optional: true,
				ForceNew: true,
			},
		},
	}
}

func resourceAwsEbsSnapshotCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	request := &ec2.CreateSnapshotInput{
		VolumeId: aws.String(d.Get("volume_id").(string)),
	}
	if v, ok := d.GetOk("description"); ok {
		request.Description = aws.String(v.(string))
	}

	res, err := conn.CreateSnapshot(request)
	if err != nil {
		return err
	}

	d.SetId(*res.SnapshotId)

	err = resourceAwsEbsSnapshotWaitForAvailable(d.Id(), conn)
	if err != nil {
		return err
	}

	if err := setTags(conn, d); err != nil {
		log.Printf("[WARN] error setting tags: %s", err)
	}

	return resourceAwsEbsSnapshotRead(d, meta)
}

func resourceAwsEbsSnapshotRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	req := &ec2.DescribeSnapshotsInput{
		SnapshotIds: []*string{aws.String(d.Id())},
	}
	res, err := conn.DescribeSnapshots(req)
	if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSnapshotID.NotFound" {
		log.Printf("Snapshot %q Not found - removing from state", d.Id())
		d.SetId("")
		return nil
	}

	snapshot := res.Snapshots[0]

	d.Set("description", snapshot.Description)
	d.Set("owner_id", snapshot.OwnerId)
	d.Set("encrypted", snapshot.Encrypted)
	d.Set("owner_alias", snapshot.OwnerAlias)
	d.Set("volume_id", snapshot.VolumeId)
	d.Set("data_encryption_key_id", snapshot.DataEncryptionKeyId)
	d.Set("kms_keey_id", snapshot.KmsKeyId)
	d.Set("volume_size", snapshot.VolumeSize)

	if err := d.Set("tags", tagsToMap(snapshot.Tags)); err != nil {
		log.Printf("[WARN] error saving tags to state: %s", err)
	}

	return nil
}

func resourceAwsEbsSnapshotDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	return resource.Retry(5*time.Minute, func() *resource.RetryError {
		request := &ec2.DeleteSnapshotInput{
			SnapshotId: aws.String(d.Id()),
		}
		_, err := conn.DeleteSnapshot(request)
		if err == nil {
			return nil
		}

		ebsErr, ok := err.(awserr.Error)
		if ebsErr.Code() == "SnapshotInUse" {
			return resource.RetryableError(fmt.Errorf("EBS SnapshotInUse - trying again while it detaches"))
		}

		if !ok {
			return resource.NonRetryableError(err)
		}

		return resource.NonRetryableError(err)
	})
}

func resourceAwsEbsSnapshotWaitForAvailable(id string, conn *ec2.EC2) error {
	log.Printf("Waiting for Snapshot %s to become available...", id)

	req := &ec2.DescribeSnapshotsInput{
		SnapshotIds: []*string{aws.String(id)},
	}
	err := conn.WaitUntilSnapshotCompleted(req)
	return err
}
<commit_msg>Fixed name on resource_aws_ebs_snapshot.kms_key_id<commit_after>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsEbsSnapshot() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsEbsSnapshotCreate,
		Read:   resourceAwsEbsSnapshotRead,
		Delete: resourceAwsEbsSnapshotDelete,

		Schema: map[string]*schema.Schema{
			"volume_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"description": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},
			"owner_id": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"owner_alias": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"encrypted": {
				Type:     schema.TypeBool,
				Computed: true,
			},
			"volume_size": {
				Type:     schema.TypeInt,
				Computed: true,
			},
			"kms_key_id": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"data_encryption_key_id": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"tags": {
				Type:     schema.TypeMap,
				Optional: true,
				ForceNew: true,
			},
		},
	}
}

func resourceAwsEbsSnapshotCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	request := &ec2.CreateSnapshotInput{
		VolumeId: aws.String(d.Get("volume_id").(string)),
	}
	if v, ok := d.GetOk("description"); ok {
		request.Description = aws.String(v.(string))
	}

	res, err := conn.CreateSnapshot(request)
	if err != nil {
		return err
	}

	d.SetId(*res.SnapshotId)

	err = resourceAwsEbsSnapshotWaitForAvailable(d.Id(), conn)
	if err != nil {
		return err
	}

	if err := setTags(conn, d); err != nil {
		log.Printf("[WARN] error setting tags: %s", err)
	}

	return resourceAwsEbsSnapshotRead(d, meta)
}

func resourceAwsEbsSnapshotRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	req := &ec2.DescribeSnapshotsInput{
		SnapshotIds: []*string{aws.String(d.Id())},
	}
	res, err := conn.DescribeSnapshots(req)
	if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSnapshotID.NotFound" {
		log.Printf("Snapshot %q Not found - removing from state", d.Id())
		d.SetId("")
		return nil
	}

	snapshot := res.Snapshots[0]

	d.Set("description", snapshot.Description)
	d.Set("owner_id", snapshot.OwnerId)
	d.Set("encrypted", snapshot.Encrypted)
	d.Set("owner_alias", snapshot.OwnerAlias)
	d.Set("volume_id", snapshot.VolumeId)
	d.Set("data_encryption_key_id", snapshot.DataEncryptionKeyId)
	d.Set("kms_key_id", snapshot.KmsKeyId)
	d.Set("volume_size", snapshot.VolumeSize)

	if err := d.Set("tags", tagsToMap(snapshot.Tags)); err != nil {
		log.Printf("[WARN] error saving tags to state: %s", err)
	}

	return nil
}

func resourceAwsEbsSnapshotDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	return resource.Retry(5*time.Minute, func() *resource.RetryError {
		request := &ec2.DeleteSnapshotInput{
			SnapshotId: aws.String(d.Id()),
		}
		_, err := conn.DeleteSnapshot(request)
		if err == nil {
			return nil
		}

		ebsErr, ok := err.(awserr.Error)
		if ebsErr.Code() == "SnapshotInUse" {
			return resource.RetryableError(fmt.Errorf("EBS SnapshotInUse - trying again while it detaches"))
		}

		if !ok {
			return resource.NonRetryableError(err)
		}

		return resource.NonRetryableError(err)
	})
}

func resourceAwsEbsSnapshotWaitForAvailable(id string, conn *ec2.EC2) error {
	log.Printf("Waiting for Snapshot %s to become available...", id)

	req := &ec2.DescribeSnapshotsInput{
		SnapshotIds: []*string{aws.String(id)},
	}
	err := conn.WaitUntilSnapshotCompleted(req)
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"os"
	"time"

	"github.com/jonnenauha/obj-simplify/objectfile"
)

type Writer struct {
	obj *objectfile.OBJ
}

func (wr *Writer) WriteFile(path string) error {
	if fileExists(path) {
		if err := os.Remove(path); err != nil {
			return err
		}
	}
	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.ModePerm)
	if err != nil {
		return err
	}
	err = wr.WriteTo(f)
	if cErr := f.Close(); cErr != nil && err == nil {
		err = cErr
	}
	return err
}

func (wr *Writer) WriteTo(w io.Writer) error {
	ln := func() {
		fmt.Fprint(w, "\n")
	}
	writeLine := func(t objectfile.Type, value string, newline bool) {
		fmt.Fprintf(w, "%s %s\n", t, value)
		if newline {
			ln()
		}
	}
	writeLines := func(t objectfile.Type, values []string, newline bool) {
		for _, v := range values {
			writeLine(t, v, false)
		}
		if newline {
			ln()
		}
	}

	obj := wr.obj

	// leave a comment that signifies this tool was ran on the file
	writeLine(objectfile.Comment, fmt.Sprintf("Processed by %s v%s | %s | %s", ApplicationName, ApplicationVersion, time.Now().UTC().Format(time.RFC3339), ApplicationURL), true)

	// comments
	writeLines(objectfile.Comment, obj.Comments, true)

	// Materials (I think there is always just one, if this can change mid file, this needs to be adjusted and pos tracked during parsing)
	writeLines(objectfile.MtlLib, obj.MaterialLibraries, true)

	// geometry
	for ti, t := range []objectfile.Type{objectfile.Vertex, objectfile.Normal, objectfile.UV, objectfile.Param} {
		if slice := obj.Geometry.Get(t); len(slice) > 0 {
			if ti > 0 {
				ln()
			}
			writeLine(objectfile.Comment, fmt.Sprintf("%s [%d]", t.Name(), len(slice)), true)
			for _, value := range slice {
				writeLine(t, value.String(t), false)
			}
		}
	}
	ln()

	// objects: preserves the parsing order of g/o
	writeLine(objectfile.Comment, fmt.Sprintf("objects [%d]", len(obj.Objects)), true)
	for _, child := range obj.Objects {
		writeLine(child.Type, child.Name, false)
		// we dont skip writing material if it has already been declared as the
		// last material, the file is easier to read for humans with write on each
		// child, and this wont take many bytes in the total file size.
		if len(child.Material) > 0 {
			writeLine(objectfile.MtlUse, child.Material, false)
		}
		ln()
		for _, vd := range child.VertexData {
			if sgroup := vd.Meta(objectfile.SmoothingGroup); len(sgroup) > 0 {
				writeLine(objectfile.SmoothingGroup, sgroup, false)
			}
			writeLine(vd.Type, vd.String(), false)
		}
		ln()
	}

	return nil
}
<commit_msg>Skip writing o/g that have no face/line/point etc. declarations.<commit_after>package main

import (
	"fmt"
	"io"
	"os"
	"time"

	"github.com/jonnenauha/obj-simplify/objectfile"
)

type Writer struct {
	obj *objectfile.OBJ
}

func (wr *Writer) WriteFile(path string) error {
	if fileExists(path) {
		if err := os.Remove(path); err != nil {
			return err
		}
	}
	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.ModePerm)
	if err != nil {
		return err
	}
	err = wr.WriteTo(f)
	if cErr := f.Close(); cErr != nil && err == nil {
		err = cErr
	}
	return err
}

func (wr *Writer) WriteTo(w io.Writer) error {
	ln := func() {
		fmt.Fprint(w, "\n")
	}
	writeLine := func(t objectfile.Type, value string, newline bool) {
		fmt.Fprintf(w, "%s %s\n", t, value)
		if newline {
			ln()
		}
	}
	writeLines := func(t objectfile.Type, values []string, newline bool) {
		for _, v := range values {
			writeLine(t, v, false)
		}
		if newline {
			ln()
		}
	}

	obj := wr.obj

	// leave a comment that signifies this tool was ran on the file
	writeLine(objectfile.Comment, fmt.Sprintf("Processed by %s v%s | %s | %s", ApplicationName, ApplicationVersion, time.Now().UTC().Format(time.RFC3339), ApplicationURL), true)

	// comments
	writeLines(objectfile.Comment, obj.Comments, true)

	// Materials (I think there is always just one, if this can change mid file, this needs to be adjusted and pos tracked during parsing)
	writeLines(objectfile.MtlLib, obj.MaterialLibraries, true)

	// geometry
	for ti, t := range []objectfile.Type{objectfile.Vertex, objectfile.Normal, objectfile.UV, objectfile.Param} {
		if slice := obj.Geometry.Get(t); len(slice) > 0 {
			if ti > 0 {
				ln()
			}
			writeLine(objectfile.Comment, fmt.Sprintf("%s [%d]", t.Name(), len(slice)), true)
			for _, value := range slice {
				writeLine(t, value.String(t), false)
			}
		}
	}
	ln()

	// objects: preserves the parsing order of g/o
	writeLine(objectfile.Comment, fmt.Sprintf("objects [%d]", len(obj.Objects)), true)
	for _, child := range obj.Objects {
		// skip children that do not declare faces etc.
		if len(child.VertexData) == 0 {
			continue
		}
		writeLine(child.Type, child.Name, false)
		// we dont skip writing material if it has already been declared as the
		// last material, the file is easier to read for humans with write on each
		// child, and this wont take many bytes in the total file size.
		if len(child.Material) > 0 {
			writeLine(objectfile.MtlUse, child.Material, false)
		}
		ln()
		for _, vd := range child.VertexData {
			if sgroup := vd.Meta(objectfile.SmoothingGroup); len(sgroup) > 0 {
				writeLine(objectfile.SmoothingGroup, sgroup, false)
			}
			writeLine(vd.Type, vd.String(), false)
		}
		ln()
	}

	return nil
}
<|endoftext|>


<commit_before>//  Copyright (c) 2014 Couchbase, Inc.
//  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 main

import (
	"fmt"
	"io"
)

type PIndexImpl interface {
	Close() error
}

type ConsistencyParams struct {
	// A Level value of "" means stale is ok; "at_plus" means we need
	// consistency at least at or beyond the consistency vector but
	// not before.
	Level string `json:"level"`

	// Keyed by indexName.
	Vectors map[string]ConsistencyVector `json:"vectors"`

	// TODO: Can user specify certain partition UUID (like vbucket UUID)?
}

// Key is partition, value is seq.
type ConsistencyVector map[string]uint64

// ---------------------------------------------------------------

type PIndexImplType struct {
	Validate func(indexType, indexName, indexParams string) error

	New  func(indexType, indexParams, path string, restart func()) (PIndexImpl, Dest, error)
	Open func(indexType, path string, restart func()) (PIndexImpl, Dest, error)

	Count func(mgr *Manager, indexName, indexUUID string) (uint64, error)
	Query func(mgr *Manager, indexName, indexUUID string,
		req []byte, res io.Writer) error

	Description string
	StartSample interface{}
}

var pindexImplTypes = make(map[string]*PIndexImplType) // Keyed by indexType.

func RegisterPIndexImplType(indexType string, t *PIndexImplType) {
	pindexImplTypes[indexType] = t
}

func NewPIndexImpl(indexType, indexParams, path string, restart func()) (
	PIndexImpl, Dest, error) {
	t, exists := pindexImplTypes[indexType]
	if !exists || t == nil {
		return nil, nil, fmt.Errorf("error: NewPIndexImpl indexType: %s", indexType)
	}

	return t.New(indexType, indexParams, path, restart)
}

func OpenPIndexImpl(indexType, path string, restart func()) (PIndexImpl, Dest, error) {
	t, exists := pindexImplTypes[indexType]
	if !exists || t == nil {
		return nil, nil, fmt.Errorf("error: OpenPIndexImpl indexType: %s", indexType)
	}

	return t.Open(indexType, path, restart)
}

func PIndexImplTypeForIndex(cfg Cfg, indexName string) (*PIndexImplType, error) {
	indexDefs, _, err := CfgGetIndexDefs(cfg)
	if err != nil || indexDefs == nil {
		return nil, fmt.Errorf("could not get indexDefs, indexName: %s, err: %v",
			indexName, err)
	}
	indexDef := indexDefs.IndexDefs[indexName]
	if indexDef == nil {
		return nil, fmt.Errorf("no indexDef, indexName: %s", indexName)
	}
	pindexImplType := pindexImplTypes[indexDef.Type]
	if pindexImplType == nil {
		return nil, fmt.Errorf("no pindexImplType, indexName: %s, indexDef.Type: %s",
			indexName, indexDef.Type)
	}
	return pindexImplType, nil
}
<commit_msg>split long lines<commit_after>//  Copyright (c) 2014 Couchbase, Inc.
//  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 main

import (
	"fmt"
	"io"
)

type PIndexImpl interface {
	Close() error
}

type ConsistencyParams struct {
	// A Level value of "" means stale is ok; "at_plus" means we need
	// consistency at least at or beyond the consistency vector but
	// not before.
	Level string `json:"level"`

	// Keyed by indexName.
	Vectors map[string]ConsistencyVector `json:"vectors"`

	// TODO: Can user specify certain partition UUID (like vbucket UUID)?
}

// Key is partition, value is seq.
type ConsistencyVector map[string]uint64

// ---------------------------------------------------------------

type PIndexImplType struct {
	Validate func(indexType, indexName, indexParams string) error

	New func(indexType, indexParams, path string, restart func()) (
		PIndexImpl, Dest, error)

	Open func(indexType, path string, restart func()) (
		PIndexImpl, Dest, error)

	Count func(mgr *Manager, indexName, indexUUID string) (
		uint64, error)

	Query func(mgr *Manager, indexName, indexUUID string,
		req []byte, res io.Writer) error

	Description string
	StartSample interface{}
}

var pindexImplTypes = make(map[string]*PIndexImplType) // Keyed by indexType.

func RegisterPIndexImplType(indexType string, t *PIndexImplType) {
	pindexImplTypes[indexType] = t
}

func NewPIndexImpl(indexType, indexParams, path string, restart func()) (
	PIndexImpl, Dest, error) {
	t, exists := pindexImplTypes[indexType]
	if !exists || t == nil {
		return nil, nil, fmt.Errorf("error: NewPIndexImpl indexType: %s", indexType)
	}

	return t.New(indexType, indexParams, path, restart)
}

func OpenPIndexImpl(indexType, path string, restart func()) (PIndexImpl, Dest, error) {
	t, exists := pindexImplTypes[indexType]
	if !exists || t == nil {
		return nil, nil, fmt.Errorf("error: OpenPIndexImpl indexType: %s", indexType)
	}

	return t.Open(indexType, path, restart)
}

func PIndexImplTypeForIndex(cfg Cfg, indexName string) (*PIndexImplType, error) {
	indexDefs, _, err := CfgGetIndexDefs(cfg)
	if err != nil || indexDefs == nil {
		return nil, fmt.Errorf("could not get indexDefs, indexName: %s, err: %v",
			indexName, err)
	}
	indexDef := indexDefs.IndexDefs[indexName]
	if indexDef == nil {
		return nil, fmt.Errorf("no indexDef, indexName: %s", indexName)
	}
	pindexImplType := pindexImplTypes[indexDef.Type]
	if pindexImplType == nil {
		return nil, fmt.Errorf("no pindexImplType, indexName: %s, indexDef.Type: %s",
			indexName, indexDef.Type)
	}
	return pindexImplType, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build java android

package syncbase

import (
	"os"
	"path/filepath"
	"unsafe"

	"v.io/v23"
	"v.io/v23/options"
	"v.io/x/ref/lib/dispatcher"
	"v.io/x/ref/services/syncbase/server"
	"v.io/x/ref/services/syncbase/vsync"

	jrpc "v.io/x/jni/impl/google/rpc"
	jutil "v.io/x/jni/util"
	jcontext "v.io/x/jni/v23/context"
	jaccess "v.io/x/jni/v23/security/access"
)

// #include "jni.h"
import "C"

var (
	permissionsSign   = jutil.ClassSign("io.v.v23.security.access.Permissions")
	contextSign       = jutil.ClassSign("io.v.v23.context.VContext")
	storageEngineSign = jutil.ClassSign("io.v.impl.google.services.syncbase.SyncbaseServer$StorageEngine")
	serverSign        = jutil.ClassSign("io.v.v23.rpc.Server")

	jVRuntimeImplClass jutil.Class
)

// Init initializes the JNI code with the given Java environment.  This method
// must be invoked before any other method in this package and must be called
// from the main Java thread (e.g., On_Load()).
func Init(env jutil.Env) error {
	var err error
	jVRuntimeImplClass, err = jutil.JFindClass(env, "io/v/impl/google/rt/VRuntimeImpl")
	if err != nil {
		return err
	}
	return nil
}

//export Java_io_v_impl_google_services_syncbase_SyncbaseServer_nativeWithNewServer
func Java_io_v_impl_google_services_syncbase_SyncbaseServer_nativeWithNewServer(jenv *C.JNIEnv, jSyncbaseServerClass C.jclass, jContext C.jobject, jSyncbaseServerParams C.jobject) C.jobject {
	env := jutil.Env(uintptr(unsafe.Pointer(jenv)))
	jCtx := jutil.Object(uintptr(unsafe.Pointer(jContext)))
	jParams := jutil.Object(uintptr(unsafe.Pointer(jSyncbaseServerParams)))

	// Read and translate all of the server params.
	jPerms, err := jutil.CallObjectMethod(env, jParams, "getPermissions", nil, permissionsSign)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	perms, err := jaccess.GoPermissions(env, jPerms)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	name, err := jutil.CallStringMethod(env, jParams, "getName", nil)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	rootDir, err := jutil.CallStringMethod(env, jParams, "getStorageRootDir", nil)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	if rootDir == "" {
		rootDir = filepath.Join(os.TempDir(), "syncbaseserver")
		if err := os.Mkdir(rootDir, 0755); err != nil && !os.IsExist(err) {
			jutil.JThrowV(env, err)
			return nil
		}
	}
	jEngine, err := jutil.CallObjectMethod(env, jParams, "getStorageEngine", nil, storageEngineSign)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	engine, err := GoStorageEngine(env, jEngine)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	ctx, cancel, err := jcontext.GoContext(env, jCtx)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}

	// Create the rpc server before the service so that connections are shared between
	// clients in the service and the rpc server. (i.e. connections are shared if the
	// context returned from WithNewDispatchingServer is used for client calls).
	d := dispatcher.NewDispatcherWrapper()
	ctx, s, err := v23.WithNewDispatchingServer(ctx, name, d, options.ChannelTimeout(vsync.NeighborConnectionTimeout))
	if err != nil {
		cancel()
		jutil.JThrowV(env, err)
		return nil
	}

	service, err := server.NewService(ctx, server.ServiceOptions{
		Perms:   perms,
		RootDir: rootDir,
		Engine:  engine,
	})
	if err != nil {
		cancel()
		jutil.JThrowV(env, err)
		return nil
	}
	// Set the dispatcher in the dispatcher wrapper for the server to start responding
	// to incoming rpcs.
	d.SetDispatcher(server.NewDispatcher(service))

	if err := service.AddNames(ctx, s); err != nil {
		cancel()
		jutil.JThrowV(env, err)
		return nil
	}
	jNewCtx, err := jcontext.JavaContext(env, ctx, cancel)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	jServer, err := jrpc.JavaServer(env, s)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	// Attach a server to the new context.
	jServerAttCtx, err := jutil.CallStaticObjectMethod(env, jVRuntimeImplClass, "withServer", []jutil.Sign{contextSign, serverSign}, contextSign, jNewCtx, jServer)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	return C.jobject(unsafe.Pointer(jServerAttCtx))
}
<commit_msg>Syncbase Discovery:  Expose the invites API via JNI / Java.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build java android

package syncbase

import (
	"os"
	"path/filepath"
	"unsafe"

	"v.io/v23"
	"v.io/v23/options"
	wire "v.io/v23/services/syncbase"
	"v.io/x/ref/lib/dispatcher"
	"v.io/x/ref/services/syncbase/discovery"
	"v.io/x/ref/services/syncbase/server"
	"v.io/x/ref/services/syncbase/vsync"

	jrpc "v.io/x/jni/impl/google/rpc"
	jutil "v.io/x/jni/util"
	jcontext "v.io/x/jni/v23/context"
	jaccess "v.io/x/jni/v23/security/access"
)

// #include "jni.h"
import "C"

var (
	permissionsSign   = jutil.ClassSign("io.v.v23.security.access.Permissions")
	contextSign       = jutil.ClassSign("io.v.v23.context.VContext")
	storageEngineSign = jutil.ClassSign("io.v.impl.google.services.syncbase.SyncbaseServer$StorageEngine")
	serverSign        = jutil.ClassSign("io.v.v23.rpc.Server")
	idSign            = jutil.ClassSign("io.v.v23.services.syncbase.Id")
	inviteSign        = jutil.ClassSign("io.v.v23.syncbase.Invite")

	jVRuntimeImplClass jutil.Class
	jInviteClass       jutil.Class
)

// Init initializes the JNI code with the given Java environment.  This method
// must be invoked before any other method in this package and must be called
// from the main Java thread (e.g., On_Load()).
func Init(env jutil.Env) error {
	var err error
	jVRuntimeImplClass, err = jutil.JFindClass(env, "io/v/impl/google/rt/VRuntimeImpl")
	if err != nil {
		return err
	}
	jInviteClass, err = jutil.JFindClass(env, "io/v/v23/syncbase/Invite")
	if err != nil {
		return err
	}
	return nil
}

//export Java_io_v_impl_google_services_syncbase_SyncbaseServer_nativeWithNewServer
func Java_io_v_impl_google_services_syncbase_SyncbaseServer_nativeWithNewServer(jenv *C.JNIEnv, jSyncbaseServerClass C.jclass, jContext C.jobject, jSyncbaseServerParams C.jobject) C.jobject {
	env := jutil.Env(uintptr(unsafe.Pointer(jenv)))
	jCtx := jutil.Object(uintptr(unsafe.Pointer(jContext)))
	jParams := jutil.Object(uintptr(unsafe.Pointer(jSyncbaseServerParams)))

	// Read and translate all of the server params.
	jPerms, err := jutil.CallObjectMethod(env, jParams, "getPermissions", nil, permissionsSign)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	perms, err := jaccess.GoPermissions(env, jPerms)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	name, err := jutil.CallStringMethod(env, jParams, "getName", nil)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	rootDir, err := jutil.CallStringMethod(env, jParams, "getStorageRootDir", nil)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	if rootDir == "" {
		rootDir = filepath.Join(os.TempDir(), "syncbaseserver")
		if err := os.Mkdir(rootDir, 0755); err != nil && !os.IsExist(err) {
			jutil.JThrowV(env, err)
			return nil
		}
	}
	jEngine, err := jutil.CallObjectMethod(env, jParams, "getStorageEngine", nil, storageEngineSign)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	engine, err := GoStorageEngine(env, jEngine)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	ctx, cancel, err := jcontext.GoContext(env, jCtx)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}

	// Create the rpc server before the service so that connections are shared between
	// clients in the service and the rpc server. (i.e. connections are shared if the
	// context returned from WithNewDispatchingServer is used for client calls).
	d := dispatcher.NewDispatcherWrapper()
	ctx, s, err := v23.WithNewDispatchingServer(ctx, name, d, options.ChannelTimeout(vsync.NeighborConnectionTimeout))
	if err != nil {
		cancel()
		jutil.JThrowV(env, err)
		return nil
	}

	service, err := server.NewService(ctx, server.ServiceOptions{
		Perms:   perms,
		RootDir: rootDir,
		Engine:  engine,
	})
	if err != nil {
		cancel()
		jutil.JThrowV(env, err)
		return nil
	}
	// Set the dispatcher in the dispatcher wrapper for the server to start responding
	// to incoming rpcs.
	d.SetDispatcher(server.NewDispatcher(service))

	if err := service.AddNames(ctx, s); err != nil {
		cancel()
		jutil.JThrowV(env, err)
		return nil
	}
	jNewCtx, err := jcontext.JavaContext(env, ctx, cancel)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	jServer, err := jrpc.JavaServer(env, s)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	// Attach a server to the new context.
	jServerAttCtx, err := jutil.CallStaticObjectMethod(env, jVRuntimeImplClass, "withServer", []jutil.Sign{contextSign, serverSign}, contextSign, jNewCtx, jServer)
	if err != nil {
		jutil.JThrowV(env, err)
		return nil
	}
	return C.jobject(unsafe.Pointer(jServerAttCtx))
}

//export Java_io_v_v23_syncbase_DatabaseImpl_nativeListenForInvites
func Java_io_v_v23_syncbase_DatabaseImpl_nativeListenForInvites(jenv *C.JNIEnv, jDatabase C.jobject, jContext C.jobject, jInviteHandler C.jobject) {
	env := jutil.Env(uintptr(unsafe.Pointer(jenv)))
	database := jutil.Object(uintptr(unsafe.Pointer(jDatabase)))
	handler := jutil.Object(uintptr(unsafe.Pointer(jInviteHandler)))

	ctx, _, err := jcontext.GoContext(env, jutil.Object(uintptr(unsafe.Pointer(jContext))))
	if err != nil {
		jutil.JThrowV(env, err)
		return
	}

	jDbId, err := jutil.CallObjectMethod(env, database, "id", nil, idSign)
	if err != nil {
		jutil.JThrowV(env, err)
		return
	}
	dbName, err := jutil.CallStringMethod(env, jDbId, "getName", nil)
	if err != nil {
		jutil.JThrowV(env, err)
		return
	}
	dbBlessing, err := jutil.CallStringMethod(env, jDbId, "getBlessing", nil)
	if err != nil {
		jutil.JThrowV(env, err)
		return
	}

	dbId := wire.Id{Name: dbName, Blessing: dbBlessing}
	// Note: There is no need to use a buffered channel here.  ListenForInvites
	// spawns a goroutine for this listener that acts as an infinite buffer so we can
	// process invites at our own pace.
	ch := make(chan discovery.Invite)
	if err := discovery.ListenForInvites(ctx, dbId, ch); err != nil {
		jutil.JThrowV(env, err)
		return
	}

	handler = jutil.NewGlobalRef(env, handler)
	go func() {
		for invite := range ch {
			if err := handleInvite(invite, handler); err != nil {
				// TODO(mattr): We should cancel the stream and return an error to
				// the user here.
				ctx.Errorf("couldn't call invite handler: %v", err)
			}
		}
		env, free := jutil.GetEnv()
		jutil.DeleteGlobalRef(env, handler)
		free()
	}()
}

func handleInvite(invite discovery.Invite, handler jutil.Object) error {
	env, free := jutil.GetEnv()
	defer free()
	jInvite, err := jutil.NewObject(env, jInviteClass,
		[]jutil.Sign{jutil.StringSign, jutil.StringSign},
		invite.Syncgroup.Blessing, invite.Syncgroup.Name)
	if err != nil {
		return err
	}
	return jutil.CallVoidMethod(env, handler, "handleInvite",
		[]jutil.Sign{inviteSign}, jInvite)
}
<|endoftext|>


<commit_before>package wallet

import (
	"fmt"
	"time"

	eth "github.com/OpenBazaar/go-ethwallet/wallet"
	"github.com/OpenBazaar/multiwallet"
	"github.com/OpenBazaar/multiwallet/bitcoin"
	"github.com/OpenBazaar/multiwallet/bitcoincash"
	"github.com/OpenBazaar/multiwallet/cache"
	mwConfig "github.com/OpenBazaar/multiwallet/config"
	"github.com/OpenBazaar/multiwallet/litecoin"
	"github.com/OpenBazaar/multiwallet/zcash"
	"github.com/OpenBazaar/openbazaar-go/repo"
	"github.com/OpenBazaar/openbazaar-go/repo/db"
	"github.com/OpenBazaar/openbazaar-go/schema"
	"github.com/OpenBazaar/wallet-interface"
	"github.com/btcsuite/btcd/chaincfg"
	"github.com/op/go-logging"
	"golang.org/x/net/proxy"
)

type WalletConfig struct {
	ConfigFile           *schema.WalletsConfig
	RepoPath             string
	Logger               logging.Backend
	DB                   *db.DB
	Mnemonic             string
	WalletCreationDate   time.Time
	Params               *chaincfg.Params
	Proxy                proxy.Dialer
	DisableExchangeRates bool
}

// Build a new multiwallet using values from the config file
// If any of the four standard coins are missing from the config file
// we will load it with default values.
func NewMultiWallet(cfg *WalletConfig) (multiwallet.MultiWallet, error) {
	// Create a default config with all coins
	enableAPIWallet := make(map[wallet.CoinType]*schema.CoinConfig)
	if cfg.ConfigFile.BTC != nil && cfg.ConfigFile.BTC.Type == "API" {
		enableAPIWallet[wallet.Bitcoin] = cfg.ConfigFile.BTC
	}
	if cfg.ConfigFile.BCH != nil && cfg.ConfigFile.BCH.Type == "API" {
		enableAPIWallet[wallet.BitcoinCash] = cfg.ConfigFile.BCH
	}
	if cfg.ConfigFile.ZEC != nil && cfg.ConfigFile.ZEC.Type == "API" {
		enableAPIWallet[wallet.Zcash] = cfg.ConfigFile.ZEC
	}
	if cfg.ConfigFile.LTC != nil && cfg.ConfigFile.LTC.Type == "API" {
		enableAPIWallet[wallet.Litecoin] = cfg.ConfigFile.LTC
	}
	enableAPIWallet[wallet.Ethereum] = nil

	// For each coin we want to override the default database with our own sqlite db
	// We'll only override the default settings if the coin exists in the config file
	var newMultiwallet = make(multiwallet.MultiWallet)
	for coin, coinConfig := range enableAPIWallet {
		if coinConfig != nil {
			actualCoin, newWallet, err := createWallet(coin, coinConfig, cfg)
			if err != nil {
				var logger = logging.MustGetLogger("NewMultiwallet")
				logger.SetBackend(logging.AddModuleLevel(cfg.Logger))
				logger.Errorf("failed creating wallet for %s: %s", actualCoin, err)
				continue
			}
			newMultiwallet[actualCoin] = newWallet
		}
	}

	return newMultiwallet, nil
}

func createWallet(coin wallet.CoinType, coinConfigOverrides *schema.CoinConfig, cfg *WalletConfig) (wallet.CoinType, wallet.Wallet, error) {
	var (
		actualCoin wallet.CoinType
		testnet    = cfg.Params.Name != chaincfg.MainNetParams.Name
		coinConfig = prepareCoinConfig(coin, coinConfigOverrides, cfg)
	)

	switch coin {
	case wallet.Bitcoin:
		if testnet {
			actualCoin = wallet.TestnetBitcoin
		} else {
			actualCoin = wallet.Bitcoin
		}
		w, err := bitcoin.NewBitcoinWallet(*coinConfig, cfg.Mnemonic, cfg.Params, cfg.Proxy, cache.NewMockCacher(), cfg.DisableExchangeRates)
		if err != nil {
			return actualCoin, nil, err
		}
		return actualCoin, w, nil
	case wallet.BitcoinCash:
		if testnet {
			actualCoin = wallet.TestnetBitcoinCash
		} else {
			actualCoin = wallet.BitcoinCash
		}
		w, err := bitcoincash.NewBitcoinCashWallet(*coinConfig, cfg.Mnemonic, cfg.Params, cfg.Proxy, cache.NewMockCacher(), cfg.DisableExchangeRates)
		if err != nil {
			return actualCoin, nil, err
		}
		return actualCoin, w, nil
	case wallet.Litecoin:
		if testnet {
			actualCoin = wallet.TestnetLitecoin
		} else {
			actualCoin = wallet.Litecoin
		}
		w, err := litecoin.NewLitecoinWallet(*coinConfig, cfg.Mnemonic, cfg.Params, cfg.Proxy, cache.NewMockCacher(), cfg.DisableExchangeRates)
		if err != nil {
			return actualCoin, nil, err
		}
		return actualCoin, w, nil
	case wallet.Zcash:
		if testnet {
			actualCoin = wallet.TestnetZcash
		} else {
			actualCoin = wallet.Zcash
		}
		w, err := zcash.NewZCashWallet(*coinConfig, cfg.Mnemonic, cfg.Params, cfg.Proxy, cache.NewMockCacher(), cfg.DisableExchangeRates)
		if err != nil {
			return actualCoin, nil, err
		}
		return actualCoin, w, nil
	case wallet.Ethereum:
		actualCoin = wallet.Ethereum
		w, err := eth.NewEthereumWallet(*coinConfig, cfg.Mnemonic, cfg.Proxy)
		if err != nil {
			return actualCoin, nil, err
		}
		return actualCoin, w, nil
	}
	return wallet.CoinType(4294967295), nil, fmt.Errorf("unable to create wallet for unknown coin %s", coin)
}

func prepareCoinConfig(coin wallet.CoinType, override *schema.CoinConfig, walletConfig *WalletConfig) *mwConfig.CoinConfig {
	var (
		defaultCoinOptions      map[string]interface{}
		defaultConfig           *schema.CoinConfig
		overrideWalletEndpoints []string
		defaultWalletEndpoints  []string

		defaultConfigSet = schema.DefaultWalletsConfig()
		testnet          = walletConfig.Params.Name != chaincfg.MainNetParams.Name
	)

	switch coin {
	case wallet.Bitcoin:
		defaultConfig = defaultConfigSet.BTC
	case wallet.BitcoinCash:
		defaultConfig = defaultConfigSet.BCH
	case wallet.Litecoin:
		defaultConfig = defaultConfigSet.LTC
	case wallet.Zcash:
		defaultConfig = defaultConfigSet.ZEC
	case wallet.Ethereum:
		defaultConfig = defaultConfigSet.ETH
		defaultCoinOptions = schema.EthereumDefaultOptions()
	}

	if testnet {
		overrideWalletEndpoints = override.APITestnetPool
		defaultWalletEndpoints = defaultConfig.APITestnetPool
	} else {
		overrideWalletEndpoints = override.APIPool
		defaultWalletEndpoints = defaultConfig.APIPool
	}

	var preparedConfig = &mwConfig.CoinConfig{
		ClientAPIs: overrideWalletEndpoints,
		CoinType:   coin,
		DB:         CreateWalletDB(walletConfig.DB, coin),
		FeeAPI:     override.FeeAPI,
		HighFee:    override.HighFeeDefault,
		LowFee:     override.LowFeeDefault,
		MaxFee:     override.MaxFee,
		MediumFee:  override.MediumFeeDefault,
		Options:    override.WalletOptions,
	}

	if preparedConfig.HighFee == 0 {
		preparedConfig.HighFee = defaultConfig.HighFeeDefault
	}
	if preparedConfig.MediumFee == 0 {
		preparedConfig.MediumFee = defaultConfig.MediumFeeDefault
	}
	if preparedConfig.LowFee == 0 {
		preparedConfig.LowFee = defaultConfig.LowFeeDefault
	}
	if preparedConfig.MaxFee == 0 {
		preparedConfig.MaxFee = defaultConfig.MaxFee
	}
	if preparedConfig.ClientAPIs == nil || len(preparedConfig.ClientAPIs) == 0 {
		preparedConfig.ClientAPIs = defaultWalletEndpoints
	}
	if preparedConfig.FeeAPI == "" {
		preparedConfig.FeeAPI = "https://btc.fees.openbazaar.org"
	}
	if len(preparedConfig.Options) == 0 {
		preparedConfig.Options = defaultCoinOptions
	}

	return preparedConfig
}

type WalletDatastore struct {
	keys           repo.KeyStore
	stxos          repo.SpentTransactionOutputStore
	txns           repo.TransactionStore
	utxos          repo.UnspentTransactionOutputStore
	watchedScripts repo.WatchedScriptStore
}

func (d *WalletDatastore) Keys() wallet.Keys {
	return d.keys
}
func (d *WalletDatastore) Stxos() wallet.Stxos {
	return d.stxos
}
func (d *WalletDatastore) Txns() wallet.Txns {
	return d.txns
}
func (d *WalletDatastore) Utxos() wallet.Utxos {
	return d.utxos
}
func (d *WalletDatastore) WatchedScripts() wallet.WatchedScripts {
	return d.watchedScripts
}

func CreateWalletDB(database *db.DB, coinType wallet.CoinType) *WalletDatastore {
	return &WalletDatastore{
		keys:           db.NewKeyStore(database.SqlDB, database.Lock, coinType),
		utxos:          db.NewUnspentTransactionStore(database.SqlDB, database.Lock, coinType),
		stxos:          db.NewSpentTransactionStore(database.SqlDB, database.Lock, coinType),
		txns:           db.NewTransactionStore(database.SqlDB, database.Lock, coinType),
		watchedScripts: db.NewWatchedScriptStore(database.SqlDB, database.Lock, coinType),
	}
}
<commit_msg>[#1183] wallet.NewMultiWallet can create SPV wallets for BTC and BCH<commit_after>package wallet

import (
	"errors"
	"fmt"
	"net"
	"net/url"
	"os"
	"path"
	"strings"
	"time"

	eth "github.com/OpenBazaar/go-ethwallet/wallet"
	"github.com/OpenBazaar/multiwallet"
	"github.com/OpenBazaar/multiwallet/bitcoin"
	"github.com/OpenBazaar/multiwallet/bitcoincash"
	"github.com/OpenBazaar/multiwallet/cache"
	mwConfig "github.com/OpenBazaar/multiwallet/config"
	"github.com/OpenBazaar/multiwallet/litecoin"
	"github.com/OpenBazaar/multiwallet/zcash"
	"github.com/OpenBazaar/openbazaar-go/repo"
	"github.com/OpenBazaar/openbazaar-go/repo/db"
	"github.com/OpenBazaar/openbazaar-go/schema"
	"github.com/OpenBazaar/spvwallet"
	"github.com/OpenBazaar/wallet-interface"
	"github.com/btcsuite/btcd/chaincfg"
	"github.com/op/go-logging"
	"golang.org/x/net/proxy"
)

const InvalidCoinType wallet.CoinType = wallet.CoinType(^uint32(0))

type WalletConfig struct {
	ConfigFile           *schema.WalletsConfig
	RepoPath             string
	Logger               logging.Backend
	DB                   *db.DB
	Mnemonic             string
	WalletCreationDate   time.Time
	Params               *chaincfg.Params
	Proxy                proxy.Dialer
	DisableExchangeRates bool
}

var ErrTrustedPeerRequired = errors.New("trusted peer required in spv wallet config during regtest use")

// NewMultiWallet returns a functional set of wallets using the provided WalletConfig.
// The value of schema.WalletsConfig.<COIN>.Type must be "API" or will be ignored. BTC
// and BCH can also use the "SPV" Type.
func NewMultiWallet(cfg *WalletConfig) (multiwallet.MultiWallet, error) {
	var (
		logger          = logging.MustGetLogger("NewMultiwallet")
		enableAPIWallet = make(map[wallet.CoinType]*schema.CoinConfig)
		enableSPVWallet = make(map[wallet.CoinType]*schema.CoinConfig)
	)
	logger.SetBackend(logging.AddModuleLevel(cfg.Logger))

	if cfg.ConfigFile.BTC != nil {
		switch cfg.ConfigFile.BTC.Type {
		case schema.WalletTypeAPI:
			enableAPIWallet[wallet.Bitcoin] = cfg.ConfigFile.BTC
		case schema.WalletTypeSPV:
			enableSPVWallet[wallet.Bitcoin] = cfg.ConfigFile.BTC
		}
	}
	if cfg.ConfigFile.BCH != nil {
		switch cfg.ConfigFile.BCH.Type {
		case schema.WalletTypeAPI:
			enableAPIWallet[wallet.BitcoinCash] = cfg.ConfigFile.BCH
		case schema.WalletTypeSPV:
			enableSPVWallet[wallet.BitcoinCash] = cfg.ConfigFile.BCH
		}
	}
	if cfg.ConfigFile.ZEC != nil && cfg.ConfigFile.ZEC.Type == "API" {
		enableAPIWallet[wallet.Zcash] = cfg.ConfigFile.ZEC
	}
	if cfg.ConfigFile.LTC != nil && cfg.ConfigFile.LTC.Type == "API" {
		enableAPIWallet[wallet.Litecoin] = cfg.ConfigFile.LTC
	}
	enableAPIWallet[wallet.Ethereum] = nil

	var newMultiwallet = make(multiwallet.MultiWallet)
	for coin, coinConfig := range enableAPIWallet {
		if coinConfig != nil {
			actualCoin, newWallet, err := createAPIWallet(coin, coinConfig, cfg)
			if err != nil {
				logger.Errorf("failed creating wallet for %s: %s", actualCoin, err)
				continue
			}
			newMultiwallet[actualCoin] = newWallet
		}
	}

	for coin, coinConfig := range enableSPVWallet {
		actualCoin, newWallet, err := createSPVWallet(coin, coinConfig, cfg)
		if err != nil {
			logger.Errorf("failed creating wallet for %s: %s", actualCoin, err)
			continue
		}
		newMultiwallet[actualCoin] = newWallet
	}

	return newMultiwallet, nil
}

func createAPIWallet(coin wallet.CoinType, coinConfigOverrides *schema.CoinConfig, cfg *WalletConfig) (wallet.CoinType, wallet.Wallet, error) {
	var (
		actualCoin wallet.CoinType
		testnet    = cfg.Params.Name != chaincfg.MainNetParams.Name
		coinConfig = prepareAPICoinConfig(coin, coinConfigOverrides, cfg)
	)

	switch coin {
	case wallet.Bitcoin:
		if testnet {
			actualCoin = wallet.TestnetBitcoin
		} else {
			actualCoin = wallet.Bitcoin
		}
		w, err := bitcoin.NewBitcoinWallet(*coinConfig, cfg.Mnemonic, cfg.Params, cfg.Proxy, cache.NewMockCacher(), cfg.DisableExchangeRates)
		if err != nil {
			return InvalidCoinType, nil, err
		}
		return actualCoin, w, nil
	case wallet.BitcoinCash:
		if testnet {
			actualCoin = wallet.TestnetBitcoinCash
		} else {
			actualCoin = wallet.BitcoinCash
		}
		w, err := bitcoincash.NewBitcoinCashWallet(*coinConfig, cfg.Mnemonic, cfg.Params, cfg.Proxy, cache.NewMockCacher(), cfg.DisableExchangeRates)
		if err != nil {
			return InvalidCoinType, nil, err
		}
		return actualCoin, w, nil
	case wallet.Litecoin:
		if testnet {
			actualCoin = wallet.TestnetLitecoin
		} else {
			actualCoin = wallet.Litecoin
		}
		w, err := litecoin.NewLitecoinWallet(*coinConfig, cfg.Mnemonic, cfg.Params, cfg.Proxy, cache.NewMockCacher(), cfg.DisableExchangeRates)
		if err != nil {
			return InvalidCoinType, nil, err
		}
		return actualCoin, w, nil
	case wallet.Zcash:
		if testnet {
			actualCoin = wallet.TestnetZcash
		} else {
			actualCoin = wallet.Zcash
		}
		w, err := zcash.NewZCashWallet(*coinConfig, cfg.Mnemonic, cfg.Params, cfg.Proxy, cache.NewMockCacher(), cfg.DisableExchangeRates)
		if err != nil {
			return InvalidCoinType, nil, err
		}
		return actualCoin, w, nil
	case wallet.Ethereum:
		actualCoin = wallet.Ethereum
		w, err := eth.NewEthereumWallet(*coinConfig, cfg.Mnemonic, cfg.Proxy)
		if err != nil {
			return InvalidCoinType, nil, err
		}
		return actualCoin, w, nil
	}
	return InvalidCoinType, nil, fmt.Errorf("unable to create wallet for unknown coin %s", coin)
}

func createSPVWallet(coin wallet.CoinType, coinConfigOverrides *schema.CoinConfig, cfg *WalletConfig) (wallet.CoinType, wallet.Wallet, error) {
	var (
		actualCoin         wallet.CoinType
		notMainnet         = cfg.Params.Name != chaincfg.MainNetParams.Name
		usingRegnet        = cfg.Params.Name == chaincfg.RegressionNetParams.Name
		missingTrustedPeer = coinConfigOverrides.TrustedPeer == ""
	)

	if usingRegnet && missingTrustedPeer {
		return InvalidCoinType, nil, ErrTrustedPeerRequired
	}

	coinConfig, err := prepareSPVCoinConfig(coin, coinConfigOverrides, cfg)
	if err != nil {
		return InvalidCoinType, nil, err
	}

	switch coin {
	case wallet.Bitcoin:
		if notMainnet {
			actualCoin = wallet.TestnetBitcoin
		} else {
			actualCoin = wallet.Bitcoin
		}
	case wallet.BitcoinCash:
		if notMainnet {
			actualCoin = wallet.TestnetBitcoinCash
		} else {
			actualCoin = wallet.BitcoinCash
		}
	}
	newSPVWallet, err := spvwallet.NewSPVWallet(coinConfig)
	if err != nil {
		return InvalidCoinType, nil, err
	}
	return actualCoin, newSPVWallet, nil
}

func ensureWalletRepoPath(repoPath string, coin wallet.CoinType, devGUID string) (string, error) {
	var (
		walletPathName = walletPath(coin, devGUID)
		walletRepoPath = path.Join(repoPath, "wallets", walletPathName)
	)
	if err := os.MkdirAll(walletRepoPath, os.ModePerm); err != nil {
		return "", err
	}
	return walletRepoPath, nil
}

func walletPath(coin wallet.CoinType, devGUID string) string {
	var (
		raw         = fmt.Sprintf("%s.%s", coin.String(), devGUID)
		lowerCoin   = strings.ToLower(raw)
		trimmedCoin = strings.Replace(lowerCoin, " ", "", -1)
	)
	return trimmedCoin
}

func prepareSPVCoinConfig(coin wallet.CoinType, override *schema.CoinConfig, walletConfig *WalletConfig) (*spvwallet.Config, error) {
	var (
		defaultConfig    *schema.CoinConfig
		defaultConfigSet = schema.DefaultWalletsConfig()
	)

	switch coin {
	case wallet.Bitcoin:
		defaultConfig = defaultConfigSet.BTC
	case wallet.BitcoinCash:
		defaultConfig = defaultConfigSet.BCH
	}

	trustedPeer, err := net.ResolveTCPAddr("tcp", override.TrustedPeer)
	if err != nil {
		return nil, fmt.Errorf("resolving tcp address %s: %s", override.TrustedPeer, err.Error())
	}
	feeAPI, err := url.Parse(override.FeeAPI)
	if err != nil {
		return nil, fmt.Errorf("parsing fee api: %s", err.Error())
	}
	walletRepoPath, err := ensureWalletRepoPath(walletConfig.RepoPath, coin, "ob1")
	if err != nil {
		return nil, fmt.Errorf("creating wallet repository: %s", err.Error())
	}
	preparedConfig := &spvwallet.Config{
		Mnemonic:             walletConfig.Mnemonic,
		Params:               walletConfig.Params,
		MaxFee:               uint64(override.MaxFee),
		LowFee:               uint64(override.LowFeeDefault),
		MediumFee:            uint64(override.MediumFeeDefault),
		HighFee:              uint64(override.HighFeeDefault),
		FeeAPI:               *feeAPI,
		RepoPath:             walletRepoPath,
		CreationDate:         walletConfig.WalletCreationDate,
		DB:                   CreateWalletDB(walletConfig.DB, coin),
		UserAgent:            "OpenBazaar",
		TrustedPeer:          trustedPeer,
		Proxy:                walletConfig.Proxy,
		Logger:               walletConfig.Logger,
		DisableExchangeRates: walletConfig.DisableExchangeRates,
	}
	if preparedConfig.HighFee == 0 {
		preparedConfig.HighFee = defaultConfig.HighFeeDefault
	}
	if preparedConfig.MediumFee == 0 {
		preparedConfig.MediumFee = defaultConfig.MediumFeeDefault
	}
	if preparedConfig.LowFee == 0 {
		preparedConfig.LowFee = defaultConfig.LowFeeDefault
	}
	if preparedConfig.MaxFee == 0 {
		preparedConfig.MaxFee = defaultConfig.MaxFee
	}
	return preparedConfig, nil
}

func prepareAPICoinConfig(coin wallet.CoinType, override *schema.CoinConfig, walletConfig *WalletConfig) *mwConfig.CoinConfig {
	var (
		defaultCoinOptions      map[string]interface{}
		defaultConfig           *schema.CoinConfig
		overrideWalletEndpoints []string
		defaultWalletEndpoints  []string

		defaultConfigSet = schema.DefaultWalletsConfig()
		testnet          = walletConfig.Params.Name != chaincfg.MainNetParams.Name
	)

	switch coin {
	case wallet.Bitcoin:
		defaultConfig = defaultConfigSet.BTC
	case wallet.BitcoinCash:
		defaultConfig = defaultConfigSet.BCH
	case wallet.Litecoin:
		defaultConfig = defaultConfigSet.LTC
	case wallet.Zcash:
		defaultConfig = defaultConfigSet.ZEC
	case wallet.Ethereum:
		defaultConfig = defaultConfigSet.ETH
		defaultCoinOptions = schema.EthereumDefaultOptions()
	}

	if testnet {
		overrideWalletEndpoints = override.APITestnetPool
		defaultWalletEndpoints = defaultConfig.APITestnetPool
	} else {
		overrideWalletEndpoints = override.APIPool
		defaultWalletEndpoints = defaultConfig.APIPool
	}

	var preparedConfig = &mwConfig.CoinConfig{
		ClientAPIs: overrideWalletEndpoints,
		CoinType:   coin,
		DB:         CreateWalletDB(walletConfig.DB, coin),
		FeeAPI:     override.FeeAPI,
		HighFee:    override.HighFeeDefault,
		LowFee:     override.LowFeeDefault,
		MaxFee:     override.MaxFee,
		MediumFee:  override.MediumFeeDefault,
		Options:    override.WalletOptions,
	}

	if preparedConfig.HighFee == 0 {
		preparedConfig.HighFee = defaultConfig.HighFeeDefault
	}
	if preparedConfig.MediumFee == 0 {
		preparedConfig.MediumFee = defaultConfig.MediumFeeDefault
	}
	if preparedConfig.LowFee == 0 {
		preparedConfig.LowFee = defaultConfig.LowFeeDefault
	}
	if preparedConfig.MaxFee == 0 {
		preparedConfig.MaxFee = defaultConfig.MaxFee
	}
	if len(preparedConfig.ClientAPIs) == 0 {
		preparedConfig.ClientAPIs = defaultWalletEndpoints
	}
	if preparedConfig.FeeAPI == "" {
		preparedConfig.FeeAPI = "https://btc.fees.openbazaar.org"
	}
	if len(preparedConfig.Options) == 0 {
		preparedConfig.Options = defaultCoinOptions
	}

	return preparedConfig
}

type WalletDatastore struct {
	keys           repo.KeyStore
	stxos          repo.SpentTransactionOutputStore
	txns           repo.TransactionStore
	utxos          repo.UnspentTransactionOutputStore
	watchedScripts repo.WatchedScriptStore
}

func (d *WalletDatastore) Keys() wallet.Keys {
	return d.keys
}
func (d *WalletDatastore) Stxos() wallet.Stxos {
	return d.stxos
}
func (d *WalletDatastore) Txns() wallet.Txns {
	return d.txns
}
func (d *WalletDatastore) Utxos() wallet.Utxos {
	return d.utxos
}
func (d *WalletDatastore) WatchedScripts() wallet.WatchedScripts {
	return d.watchedScripts
}

func CreateWalletDB(database *db.DB, coinType wallet.CoinType) *WalletDatastore {
	return &WalletDatastore{
		keys:           db.NewKeyStore(database.SqlDB, database.Lock, coinType),
		utxos:          db.NewUnspentTransactionStore(database.SqlDB, database.Lock, coinType),
		stxos:          db.NewSpentTransactionStore(database.SqlDB, database.Lock, coinType),
		txns:           db.NewTransactionStore(database.SqlDB, database.Lock, coinType),
		watchedScripts: db.NewWatchedScriptStore(database.SqlDB, database.Lock, coinType),
	}
}
<|endoftext|>


<commit_before>package wallet

import (
    "bytes"
    "crypto/elliptic"
    "encoding/gob"
    "fmt"
    "io/ioutil"
    "log"
    "os"
)

const walletFile = "wallet_%s.dat"

type Wallets struct {
    Wallets map[string]*Wallet
}

func NewWallets(nodeID string) (*Wallets, error) {
    wallets := Wallets{}
    wallets.Wallets = make(map[string]*Wallet)

    err := wallets.LoadFromFile(nodeID)

    return &wallets, err
}

// GetAddresses returns an array of addresses stored in the wallet file
func (ws *Wallets) GetAddresses() []string {
    var addresses []string

    for address := range ws.Wallets {
        addresses = append(addressesm address)
    }

    return addresses
}

// Loads wallets from a file
func (ws *Wallets) LoadFromFile(nodeID string) error {
    walletFile := fmt.Sprintf(walletFile, nodeID)
    if _, err := os.Stat(walletFile); os.IsNotExist(err) {
        return err
    }

    fileContent, err := ioutil.ReadFile(walletFile)
    if err != nil {
        log.Panic(err)
    }

    var wallets Wallets
    gob.Register(elliptic.P256())
    decoder := gob.NewDecoder(bytes.NewReader(fileContent))
    err = decoder.Decode(&wallets)
    if err != nil {
        log.Panic(err)
    }

    ws.Wallets = wallets.Wallets

    return nil
}

// Saves wallets to a file
func (ws Wallets) SaveToFile(nodeID string) {
    var content bytes.Buffer
    walletFile := fmt.Sprintf(walletFile, nodeID)

    gob.Register(elliptic.P256())

    encoder := gob.NewEncoder(&content)
    err := encoder.Encode(ws)
    if err != nil {
        log.Panic(err)
    }

    err = ioutil.WriteFile(walletFile, content.Bytes(), 0644)
    if err != nil {
        log.Panic(err)
    }
}
<commit_msg>Update wallets.go<commit_after>package wallet

import (
    "bytes"
    "crypto/elliptic"
    "encoding/gob"
    "fmt"
    "io/ioutil"
    "log"
    "os"
)

const walletFile = "wallet_%s.dat"

type Wallets struct {
    Wallets map[string]*Wallet
}

func NewWallets(nodeID string) (*Wallets, error) {
    wallets := Wallets{}
    wallets.Wallets = make(map[string]*Wallet)

    err := wallets.LoadFromFile(nodeID)

    return &wallets, err
}

// CreateWallet adds a Wallet to Wallets
func (ws *Wallets) CreateWallet() string {
	wallet := NewWallet()
	address := fmt.Sprintf("%s", wallet.GetAddress())

	ws.Wallets[address] = wallet

	return address
}

// GetAddresses returns an array of addresses stored in the wallet file
func (ws *Wallets) GetAddresses() []string {
    var addresses []string

    for address := range ws.Wallets {
        addresses = append(addressesm address)
    }

    return addresses
}

// Loads wallets from a file
func (ws *Wallets) LoadFromFile(nodeID string) error {
    walletFile := fmt.Sprintf(walletFile, nodeID)
    if _, err := os.Stat(walletFile); os.IsNotExist(err) {
        return err
    }

    fileContent, err := ioutil.ReadFile(walletFile)
    if err != nil {
        log.Panic(err)
    }

    var wallets Wallets
    gob.Register(elliptic.P256())
    decoder := gob.NewDecoder(bytes.NewReader(fileContent))
    err = decoder.Decode(&wallets)
    if err != nil {
        log.Panic(err)
    }

    ws.Wallets = wallets.Wallets

    return nil
}

// Saves wallets to a file
func (ws Wallets) SaveToFile(nodeID string) {
    var content bytes.Buffer
    walletFile := fmt.Sprintf(walletFile, nodeID)

    gob.Register(elliptic.P256())

    encoder := gob.NewEncoder(&content)
    err := encoder.Encode(ws)
    if err != nil {
        log.Panic(err)
    }

    err = ioutil.WriteFile(walletFile, content.Bytes(), 0644)
    if err != nil {
        log.Panic(err)
    }
}
<|endoftext|>


<commit_before>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"fmt"
	"github.com/getgauge/gauge/gauge_messages"
	"github.com/getgauge/gauge/logger"
	"runtime"
	"strconv"
	"time"
)

type parallelSpecExecution struct {
	manifest                 *manifest
	specifications           []*specification
	pluginHandler            *pluginHandler
	currentExecutionInfo     *gauge_messages.ExecutionInfo
	runner                   *testRunner
	aggregateResult          *suiteResult
	numberOfExecutionStreams int
	writer                   executionLogger
}

type streamExecError struct {
	specsSkipped []string
	message      string
}

func (s streamExecError) Error() string {
	return fmt.Sprintf("The following specifications are not executed: %s. Reason: %s", s.specsSkipped, s.message)
}

type specCollection struct {
	specs []*specification
}

type parallelInfo struct {
	inParallel      bool
	numberOfStreams int
}

func (self *parallelInfo) isValid() bool {
	if self.numberOfStreams < 1 {
		logger.Log.Error("Invalid input(%s) to --n flag", strconv.Itoa(self.numberOfStreams))
		return false
	}
	return true
}

func (e *parallelSpecExecution) start() *suiteResult {
	startTime := time.Now()

	specCollections := e.distributeSpecs(e.numberOfExecutionStreams)
	suiteResultChannel := make(chan *suiteResult, len(specCollections))
	for i, specCollection := range specCollections {
		go e.startSpecsExecution(specCollection, suiteResultChannel, nil, newParallelExecutionConsoleWriter(i+1))
	}
	e.writer.Info("Executing in %s parallel streams.", strconv.Itoa(len(specCollections)))
	suiteResults := make([]*suiteResult, 0)
	for _, _ = range specCollections {
		suiteResults = append(suiteResults, <-suiteResultChannel)
	}

	e.aggregateResult = e.aggregateResults(suiteResults)
	e.aggregateResult.executionTime = int64(time.Since(startTime) / 1e6)
	return e.aggregateResult
}

func (e *parallelSpecExecution) startSpecsExecution(specCollection *specCollection, suiteResults chan *suiteResult, runner *testRunner, writer executionLogger) {
	var err error
	runner, err = startRunnerAndMakeConnection(e.manifest, writer)
	if err != nil {
		e.writer.Error("Failed: " + err.Error())
		e.writer.Debug("Skipping %s specifications", strconv.Itoa(len(specCollection.specs)))
		specNames := make([]string, 0)
		for _, spec := range specCollection.specs {
			specNames = append(specNames, spec.fileName)
		}
		suiteResults <- &suiteResult{unhandledErrors: []error{streamExecError{specsSkipped: specNames, message: fmt.Sprintf("Failed to start runner. %s", err.Error())}}}
		return
	}
	e.startSpecsExecutionWithRunner(specCollection, suiteResults, runner, writer)
}

func (e *parallelSpecExecution) startSpecsExecutionWithRunner(specCollection *specCollection, suiteResults chan *suiteResult, runner *testRunner, writer executionLogger) {
	execution := newExecution(e.manifest, specCollection.specs, runner, e.pluginHandler, &parallelInfo{inParallel: false}, writer)
	result := execution.start()
	runner.kill(e.writer)
	suiteResults <- result
}

func (e *parallelSpecExecution) distributeSpecs(distributions int) []*specCollection {
	if distributions > len(e.specifications) {
		distributions = len(e.specifications)
	}
	specCollections := make([]*specCollection, distributions)
	for i := 0; i < len(e.specifications); i++ {
		mod := i % distributions
		if specCollections[mod] == nil {
			specCollections[mod] = &specCollection{specs: make([]*specification, 0)}
		}
		specCollections[mod].specs = append(specCollections[mod].specs, e.specifications[i])
	}
	return specCollections
}

func (e *parallelSpecExecution) finish() {
	message := &gauge_messages.Message{MessageType: gauge_messages.Message_SuiteExecutionResult.Enum(),
		SuiteExecutionResult: &gauge_messages.SuiteExecutionResult{SuiteResult: convertToProtoSuiteResult(e.aggregateResult)}}
	e.pluginHandler.notifyPlugins(message)
	e.pluginHandler.gracefullyKillPlugins()
}

func (e *parallelSpecExecution) aggregateResults(suiteResults []*suiteResult) *suiteResult {
	aggregateResult := &suiteResult{isFailed: false, specResults: make([]*specResult, 0)}
	for _, result := range suiteResults {
		aggregateResult.executionTime += result.executionTime
		aggregateResult.specsFailedCount += result.specsFailedCount
		aggregateResult.specResults = append(aggregateResult.specResults, result.specResults...)
		if result.isFailed {
			aggregateResult.isFailed = true
		}
		if result.preSuite != nil {
			aggregateResult.preSuite = result.preSuite
		}
		if result.postSuite != nil {
			aggregateResult.postSuite = result.postSuite
		}
		if result.unhandledErrors != nil {
			aggregateResult.unhandledErrors = append(aggregateResult.unhandledErrors, result.unhandledErrors...)
		}
	}
	return aggregateResult
}

func numberOfCores() int {
	return runtime.NumCPU()
}
<commit_msg>Moving getSpecNames method into specCollection<commit_after>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"fmt"
	"github.com/getgauge/gauge/gauge_messages"
	"github.com/getgauge/gauge/logger"
	"runtime"
	"strconv"
	"time"
)

type parallelSpecExecution struct {
	manifest                 *manifest
	specifications           []*specification
	pluginHandler            *pluginHandler
	currentExecutionInfo     *gauge_messages.ExecutionInfo
	runner                   *testRunner
	aggregateResult          *suiteResult
	numberOfExecutionStreams int
	writer                   executionLogger
}

type streamExecError struct {
	specsSkipped []string
	message      string
}

func (s streamExecError) Error() string {
	return fmt.Sprintf("The following specifications are not executed: %s. Reason: %s", s.specsSkipped, s.message)
}

type specCollection struct {
	specs []*specification
}

type parallelInfo struct {
	inParallel      bool
	numberOfStreams int
}

func (self *parallelInfo) isValid() bool {
	if self.numberOfStreams < 1 {
		logger.Log.Error("Invalid input(%s) to --n flag", strconv.Itoa(self.numberOfStreams))
		return false
	}
	return true
}

func (e *parallelSpecExecution) start() *suiteResult {
	startTime := time.Now()

	specCollections := e.distributeSpecs(e.numberOfExecutionStreams)
	suiteResultChannel := make(chan *suiteResult, len(specCollections))
	for i, specCollection := range specCollections {
		go e.startSpecsExecution(specCollection, suiteResultChannel, nil, newParallelExecutionConsoleWriter(i+1))
	}
	e.writer.Info("Executing in %s parallel streams.", strconv.Itoa(len(specCollections)))
	suiteResults := make([]*suiteResult, 0)
	for _, _ = range specCollections {
		suiteResults = append(suiteResults, <-suiteResultChannel)
	}

	e.aggregateResult = e.aggregateResults(suiteResults)
	e.aggregateResult.executionTime = int64(time.Since(startTime) / 1e6)
	return e.aggregateResult
}

func (e *parallelSpecExecution) startSpecsExecution(specCollection *specCollection, suiteResults chan *suiteResult, runner *testRunner, writer executionLogger) {
	var err error
	runner, err = startRunnerAndMakeConnection(e.manifest, writer)
	if err != nil {
		e.writer.Error("Failed: " + err.Error())
		e.writer.Debug("Skipping %s specifications", strconv.Itoa(len(specCollection.specs)))
		suiteResults <- &suiteResult{unhandledErrors: []error{streamExecError{specsSkipped: specCollection.specNames(), message: fmt.Sprintf("Failed to start runner. %s", err.Error())}}}
		return
	}
	e.startSpecsExecutionWithRunner(specCollection, suiteResults, runner, writer)
}

func (e *parallelSpecExecution) startSpecsExecutionWithRunner(specCollection *specCollection, suiteResults chan *suiteResult, runner *testRunner, writer executionLogger) {
	execution := newExecution(e.manifest, specCollection.specs, runner, e.pluginHandler, &parallelInfo{inParallel: false}, writer)
	result := execution.start()
	runner.kill(e.writer)
	suiteResults <- result
}

func (e *parallelSpecExecution) distributeSpecs(distributions int) []*specCollection {
	if distributions > len(e.specifications) {
		distributions = len(e.specifications)
	}
	specCollections := make([]*specCollection, distributions)
	for i := 0; i < len(e.specifications); i++ {
		mod := i % distributions
		if specCollections[mod] == nil {
			specCollections[mod] = &specCollection{specs: make([]*specification, 0)}
		}
		specCollections[mod].specs = append(specCollections[mod].specs, e.specifications[i])
	}
	return specCollections
}

func (e *parallelSpecExecution) finish() {
	message := &gauge_messages.Message{MessageType: gauge_messages.Message_SuiteExecutionResult.Enum(),
		SuiteExecutionResult: &gauge_messages.SuiteExecutionResult{SuiteResult: convertToProtoSuiteResult(e.aggregateResult)}}
	e.pluginHandler.notifyPlugins(message)
	e.pluginHandler.gracefullyKillPlugins()
}

func (e *parallelSpecExecution) aggregateResults(suiteResults []*suiteResult) *suiteResult {
	aggregateResult := &suiteResult{isFailed: false, specResults: make([]*specResult, 0)}
	for _, result := range suiteResults {
		aggregateResult.executionTime += result.executionTime
		aggregateResult.specsFailedCount += result.specsFailedCount
		aggregateResult.specResults = append(aggregateResult.specResults, result.specResults...)
		if result.isFailed {
			aggregateResult.isFailed = true
		}
		if result.preSuite != nil {
			aggregateResult.preSuite = result.preSuite
		}
		if result.postSuite != nil {
			aggregateResult.postSuite = result.postSuite
		}
		if result.unhandledErrors != nil {
			aggregateResult.unhandledErrors = append(aggregateResult.unhandledErrors, result.unhandledErrors...)
		}
	}
	return aggregateResult
}

func numberOfCores() int {
	return runtime.NumCPU()
}

func (s *specCollection) specNames() []string {
	specNames := make([]string, 0)
	for _, spec := range s.specs {
		specNames = append(specNames, spec.fileName)
	}
	return specNames
}
<|endoftext|>


<commit_before>package pixy

import (
	"crypto/rand"
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/mailgun/kafka-pixy/Godeps/_workspace/src/github.com/mailgun/log"
	"github.com/mailgun/kafka-pixy/Godeps/_workspace/src/github.com/mailgun/sarama"
	"github.com/mailgun/kafka-pixy/Godeps/_workspace/src/github.com/wvanbergen/kazoo-go"
)

type Config struct {
	// A unix domain socket address that the service should listen at.
	UnixAddr string
	// A TCP address that the service should listen at.
	TCPAddr string
	// A unique id that identifies this particular Kafka-Pixy instance in both
	// Kafka and ZooKeeper.
	ClientID string

	Kafka struct {
		// A list of seed Kafka peers in the form "<host>:<port>" that the
		// service will try to connect to to resolve the cluster topology.
		SeedPeers []string
	}
	ZooKeeper struct {
		// A list of seed ZooKeeper peers in the form "<host>:<port>" that the
		// service will try to connect to to resolve the cluster topology.
		SeedPeers []string
		// The root directory where Kafka keeps all its znodes.
		Chroot string
	}
	Producer struct {
		// The period of time that a proxy should allow to `sarama.Producer` to
		// submit buffered messages to Kafka. It should be large enough to avoid
		// event loss when shutdown is performed during Kafka leader election.
		ShutdownTimeout time.Duration
		// DeadMessageCh is a channel to dump undelivered messages into. It is
		// used in testing only.
		DeadMessageCh chan<- *sarama.ProducerMessage
	}
	Consumer struct {
		// A consume request will wait at most this long until a message from
		// the specified group/topic becomes available. This timeout is
		// necessary to account for consumer rebalancing that happens whenever
		// a new consumer joins a group or subscribes to a topic.
		LongPollingTimeout time.Duration
		// The period of time that a proxy should keep registration with a
		// consumer group or subscription for a topic in the absence of requests
		// to the aforementioned consumer group or topic.
		RegistrationTimeout time.Duration
		// If a request to a KafkaBroker fails for any reason then the proxy
		// should wait this long before retrying.
		BackOffTimeout time.Duration
		// A consumer should wait this long after it gets notification that a
		// consumer joined/left its consumer group before it should rebalance.
		RebalanceDelay time.Duration
	}
	// All buffered channels created by the service will have this size.
	ChannelBufferSize int
	// testing sections contains parameters that are used in tests only.
	testing struct {
		// If this channel is not `nil` then exclusive consumers will use it to
		// notify when they fetch the very first message.
		firstMessageFetchedCh chan *exclusiveConsumer
	}
}

func NewConfig() *Config {
	config := &Config{}
	config.ClientID = newClientID()
	config.Producer.ShutdownTimeout = 30 * time.Second
	config.Consumer.LongPollingTimeout = 3 * time.Second
	config.Consumer.RegistrationTimeout = 20 * time.Second
	config.Consumer.BackOffTimeout = 500 * time.Millisecond
	config.Consumer.RebalanceDelay = 250 * time.Millisecond
	config.ChannelBufferSize = 256
	return config
}

// saramaConfig generates a `Shopify/sarama` library config.
func (c *Config) saramaConfig() *sarama.Config {
	saramaConfig := sarama.NewConfig()
	saramaConfig.ClientID = c.ClientID
	saramaConfig.Producer.RequiredAcks = sarama.WaitForAll
	saramaConfig.Producer.Return.Successes = true
	saramaConfig.Producer.Return.Errors = true
	saramaConfig.Producer.Compression = sarama.CompressionSnappy
	saramaConfig.Producer.Retry.Backoff = time.Second
	saramaConfig.Producer.Flush.Frequency = 500 * time.Millisecond
	saramaConfig.Producer.Flush.Bytes = 1024 * 1024
	saramaConfig.Consumer.Offsets.CommitInterval = 50 * time.Millisecond
	saramaConfig.ChannelBufferSize = c.ChannelBufferSize
	return saramaConfig
}

// saramaConfig generates a `wvanbergen/kazoo-go` library config.
func (c *Config) kazooConfig() *kazoo.Config {
	kazooConfig := kazoo.NewConfig()
	kazooConfig.Chroot = c.ZooKeeper.Chroot
	kazooConfig.Timeout = 1 * time.Second
	return kazooConfig
}

type Service struct {
	producer   *GracefulProducer
	consumer   *SmartConsumer
	admin      *Admin
	unixServer *HTTPAPIServer
	tcpServer  *HTTPAPIServer
	quitCh     chan struct{}
	wg         sync.WaitGroup
}

func SpawnService(config *Config) (*Service, error) {
	producer, err := SpawnGracefulProducer(config)
	if err != nil {
		return nil, fmt.Errorf("failed to spawn producer, err=(%s)", err)
	}
	consumer, err := SpawnSmartConsumer(config)
	if err != nil {
		return nil, fmt.Errorf("failed to spawn consumer, err=(%s)", err)
	}
	admin, err := SpawnAdmin(config)
	if err != nil {
		return nil, fmt.Errorf("failed to spawn admin, err=(%s)", err)
	}
	unixServer, err := NewHTTPAPIServer(NetworkUnix, config.UnixAddr, producer, consumer, admin)
	if err != nil {
		producer.Stop()
		return nil, fmt.Errorf("failed to start Unix socket based HTTP API, err=(%s)", err)
	}
	var tcpServer *HTTPAPIServer
	if config.TCPAddr != "" {
		tcpServer, err = NewHTTPAPIServer(NetworkTCP, config.TCPAddr, producer, consumer, admin)
		if err != nil {
			producer.Stop()
			return nil, fmt.Errorf("failed to start TCP socket based HTTP API, err=(%s)", err)
		}
	}
	s := &Service{
		producer:   producer,
		consumer:   consumer,
		admin:      admin,
		unixServer: unixServer,
		tcpServer:  tcpServer,
		quitCh:     make(chan struct{}),
	}
	spawn(&s.wg, s.supervisor)
	return s, nil
}

func (s *Service) Stop() {
	close(s.quitCh)
	s.wg.Wait()
}

// supervisor takes care of the service graceful shutdown.
func (s *Service) supervisor() {
	defer sarama.RootCID.NewChild("supervisor").LogScope()()
	var tcpServerErrorCh <-chan error

	s.unixServer.Start()
	if s.tcpServer != nil {
		s.tcpServer.Start()
		tcpServerErrorCh = s.tcpServer.ErrorCh()
	}
	// Block to wait for quit signal or an API server crash.
	select {
	case <-s.quitCh:
	case err, ok := <-s.unixServer.ErrorCh():
		if ok {
			log.Errorf("Unix socket based HTTP API crashed, err=(%s)", err)
		}
	case err, ok := <-tcpServerErrorCh:
		if ok {
			log.Errorf("TCP socket based HTTP API crashed, err=(%s)", err)
		}
	}
	// Initiate stop of all API servers.
	s.unixServer.AsyncStop()
	if s.tcpServer != nil {
		s.tcpServer.AsyncStop()
	}
	// Wait until all API servers are stopped.
	<-s.unixServer.ErrorCh()
	if s.tcpServer != nil {
		<-s.tcpServer.ErrorCh()
	}
	// There are no more requests in flight at this point so it is safe to stop
	// all Kafka clients.
	var wg sync.WaitGroup
	spawn(&wg, s.producer.Stop)
	spawn(&wg, s.consumer.Stop)
	spawn(&wg, s.admin.Stop)
	wg.Wait()
}

// newClientID creates a unique id that identifies this particular Kafka-Pixy
// in both Kafka and ZooKeeper.
func newClientID() string {
	hostname, err := os.Hostname()
	if err != nil {
		ip, err := getIP()
		if err != nil {
			buffer := make([]byte, 8)
			_, _ = rand.Read(buffer)
			hostname = fmt.Sprintf("%X", buffer)

		} else {
			hostname = ip.String()
		}
	}
	timestamp := time.Now().UTC().Format(time.RFC3339)
	return fmt.Sprintf("pixy_%s_%d_%s", hostname, os.Getpid(), timestamp)
}
<commit_msg>Make waiting for HTTP API to stop more explicit<commit_after>package pixy

import (
	"crypto/rand"
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/mailgun/kafka-pixy/Godeps/_workspace/src/github.com/mailgun/log"
	"github.com/mailgun/kafka-pixy/Godeps/_workspace/src/github.com/mailgun/sarama"
	"github.com/mailgun/kafka-pixy/Godeps/_workspace/src/github.com/wvanbergen/kazoo-go"
)

type Config struct {
	// A unix domain socket address that the service should listen at.
	UnixAddr string
	// A TCP address that the service should listen at.
	TCPAddr string
	// A unique id that identifies this particular Kafka-Pixy instance in both
	// Kafka and ZooKeeper.
	ClientID string

	Kafka struct {
		// A list of seed Kafka peers in the form "<host>:<port>" that the
		// service will try to connect to to resolve the cluster topology.
		SeedPeers []string
	}
	ZooKeeper struct {
		// A list of seed ZooKeeper peers in the form "<host>:<port>" that the
		// service will try to connect to to resolve the cluster topology.
		SeedPeers []string
		// The root directory where Kafka keeps all its znodes.
		Chroot string
	}
	Producer struct {
		// The period of time that a proxy should allow to `sarama.Producer` to
		// submit buffered messages to Kafka. It should be large enough to avoid
		// event loss when shutdown is performed during Kafka leader election.
		ShutdownTimeout time.Duration
		// DeadMessageCh is a channel to dump undelivered messages into. It is
		// used in testing only.
		DeadMessageCh chan<- *sarama.ProducerMessage
	}
	Consumer struct {
		// A consume request will wait at most this long until a message from
		// the specified group/topic becomes available. This timeout is
		// necessary to account for consumer rebalancing that happens whenever
		// a new consumer joins a group or subscribes to a topic.
		LongPollingTimeout time.Duration
		// The period of time that a proxy should keep registration with a
		// consumer group or subscription for a topic in the absence of requests
		// to the aforementioned consumer group or topic.
		RegistrationTimeout time.Duration
		// If a request to a KafkaBroker fails for any reason then the proxy
		// should wait this long before retrying.
		BackOffTimeout time.Duration
		// A consumer should wait this long after it gets notification that a
		// consumer joined/left its consumer group before it should rebalance.
		RebalanceDelay time.Duration
	}
	// All buffered channels created by the service will have this size.
	ChannelBufferSize int
	// testing sections contains parameters that are used in tests only.
	testing struct {
		// If this channel is not `nil` then exclusive consumers will use it to
		// notify when they fetch the very first message.
		firstMessageFetchedCh chan *exclusiveConsumer
	}
}

func NewConfig() *Config {
	config := &Config{}
	config.ClientID = newClientID()
	config.Producer.ShutdownTimeout = 30 * time.Second
	config.Consumer.LongPollingTimeout = 3 * time.Second
	config.Consumer.RegistrationTimeout = 20 * time.Second
	config.Consumer.BackOffTimeout = 500 * time.Millisecond
	config.Consumer.RebalanceDelay = 250 * time.Millisecond
	config.ChannelBufferSize = 256
	return config
}

// saramaConfig generates a `Shopify/sarama` library config.
func (c *Config) saramaConfig() *sarama.Config {
	saramaConfig := sarama.NewConfig()
	saramaConfig.ClientID = c.ClientID
	saramaConfig.Producer.RequiredAcks = sarama.WaitForAll
	saramaConfig.Producer.Return.Successes = true
	saramaConfig.Producer.Return.Errors = true
	saramaConfig.Producer.Compression = sarama.CompressionSnappy
	saramaConfig.Producer.Retry.Backoff = time.Second
	saramaConfig.Producer.Flush.Frequency = 500 * time.Millisecond
	saramaConfig.Producer.Flush.Bytes = 1024 * 1024
	saramaConfig.Consumer.Offsets.CommitInterval = 50 * time.Millisecond
	saramaConfig.ChannelBufferSize = c.ChannelBufferSize
	return saramaConfig
}

// saramaConfig generates a `wvanbergen/kazoo-go` library config.
func (c *Config) kazooConfig() *kazoo.Config {
	kazooConfig := kazoo.NewConfig()
	kazooConfig.Chroot = c.ZooKeeper.Chroot
	kazooConfig.Timeout = 1 * time.Second
	return kazooConfig
}

type Service struct {
	producer   *GracefulProducer
	consumer   *SmartConsumer
	admin      *Admin
	unixServer *HTTPAPIServer
	tcpServer  *HTTPAPIServer
	quitCh     chan struct{}
	wg         sync.WaitGroup
}

func SpawnService(config *Config) (*Service, error) {
	producer, err := SpawnGracefulProducer(config)
	if err != nil {
		return nil, fmt.Errorf("failed to spawn producer, err=(%s)", err)
	}
	consumer, err := SpawnSmartConsumer(config)
	if err != nil {
		return nil, fmt.Errorf("failed to spawn consumer, err=(%s)", err)
	}
	admin, err := SpawnAdmin(config)
	if err != nil {
		return nil, fmt.Errorf("failed to spawn admin, err=(%s)", err)
	}
	unixServer, err := NewHTTPAPIServer(NetworkUnix, config.UnixAddr, producer, consumer, admin)
	if err != nil {
		producer.Stop()
		return nil, fmt.Errorf("failed to start Unix socket based HTTP API, err=(%s)", err)
	}
	var tcpServer *HTTPAPIServer
	if config.TCPAddr != "" {
		tcpServer, err = NewHTTPAPIServer(NetworkTCP, config.TCPAddr, producer, consumer, admin)
		if err != nil {
			producer.Stop()
			return nil, fmt.Errorf("failed to start TCP socket based HTTP API, err=(%s)", err)
		}
	}
	s := &Service{
		producer:   producer,
		consumer:   consumer,
		admin:      admin,
		unixServer: unixServer,
		tcpServer:  tcpServer,
		quitCh:     make(chan struct{}),
	}
	spawn(&s.wg, s.supervisor)
	return s, nil
}

func (s *Service) Stop() {
	close(s.quitCh)
	s.wg.Wait()
}

// supervisor takes care of the service graceful shutdown.
func (s *Service) supervisor() {
	defer sarama.RootCID.NewChild("supervisor").LogScope()()
	var tcpServerErrorCh <-chan error

	s.unixServer.Start()
	if s.tcpServer != nil {
		s.tcpServer.Start()
		tcpServerErrorCh = s.tcpServer.ErrorCh()
	}
	// Block to wait for quit signal or an API server crash.
	select {
	case <-s.quitCh:
	case err, ok := <-s.unixServer.ErrorCh():
		if ok {
			log.Errorf("Unix socket based HTTP API crashed, err=(%s)", err)
		}
	case err, ok := <-tcpServerErrorCh:
		if ok {
			log.Errorf("TCP socket based HTTP API crashed, err=(%s)", err)
		}
	}
	// Initiate stop of all API servers.
	s.unixServer.AsyncStop()
	if s.tcpServer != nil {
		s.tcpServer.AsyncStop()
	}
	// Wait until all API servers are stopped.
	for range s.unixServer.ErrorCh() {
		// Drain the errors channel until it is closed.
	}
	if s.tcpServer != nil {
		for range s.tcpServer.ErrorCh() {
			// Drain the errors channel until it is closed.
		}
	}
	// There are no more requests in flight at this point so it is safe to stop
	// all Kafka clients.
	var wg sync.WaitGroup
	spawn(&wg, s.producer.Stop)
	spawn(&wg, s.consumer.Stop)
	spawn(&wg, s.admin.Stop)
	wg.Wait()
}

// newClientID creates a unique id that identifies this particular Kafka-Pixy
// in both Kafka and ZooKeeper.
func newClientID() string {
	hostname, err := os.Hostname()
	if err != nil {
		ip, err := getIP()
		if err != nil {
			buffer := make([]byte, 8)
			_, _ = rand.Read(buffer)
			hostname = fmt.Sprintf("%X", buffer)

		} else {
			hostname = ip.String()
		}
	}
	timestamp := time.Now().UTC().Format(time.RFC3339)
	return fmt.Sprintf("pixy_%s_%d_%s", hostname, os.Getpid(), timestamp)
}
<|endoftext|>