<commit_before>package queue

import (
	"bytes"
	"context"
	"encoding/gob"
	"log"
	"sync"
	"time"

	xContext "golang.org/x/net/context"

	"github.com/google/go-github/github"
	"github.com/pkg/errors"

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

	"cloud.google.com/go/pubsub"
)

func init() {
	// List of all types that could be added to the queue
	gob.Register(&github.PullRequestEvent{})
	gob.Register(&github.PushEvent{})
}

const (
	// version should be changed each time the message format changes in an
	// incompatible way. This will then cause new subscribers to listen on the
	// new topic.
	version          = "1"
	defaultSubName   = "worker"
	defaultTopicName = "gopherci-ci"
)

// GCPPubSubQueue is a queue using Google Compute Platform's PubSub product.
type GCPPubSubQueue struct {
	topic        *pubsub.Topic
	subscription *pubsub.Subscription
}

var cxnTimeout = 15 * time.Second

// NewGCPPubSubQueue creates connects to Google Pub/Sub with a topic and
// subscriber in a one-to-one architecture.
func NewGCPPubSubQueue(ctx context.Context, projectID, topicName string) (*GCPPubSubQueue, error) {
	q := &GCPPubSubQueue{}

	if projectID == "" {
		return nil, errors.New("projectID must not be empty")
	}

	// create a context with a timeout for exclusive use of connection setup to
	// ensure connnection setup doesn't block and can fail early.
	cxnCtx, cancel := context.WithTimeout(ctx, cxnTimeout)
	defer cancel()

	client, err := pubsub.NewClient(cxnCtx, projectID)
	if err != nil {
		return nil, errors.Wrap(err, "NewGCPPubSubQueue: could not create client")
	}

	if topicName == "" {
		topicName = defaultTopicName
	}
	topicName += "-v" + version

	log.Printf("NewGCPPubSubQueue: creating topic %q", topicName)
	q.topic, err = client.CreateTopic(cxnCtx, topicName)
	if code := grpc.Code(err); code != codes.OK && code != codes.AlreadyExists {
		return nil, errors.Wrap(err, "NewGCPPubSubQueue: could not create topic")
	}

	subName := topicName + "-" + defaultSubName

	log.Printf("NewGCPPubSubQueue: creating subscription %q", subName)
	q.subscription, err = client.CreateSubscription(cxnCtx, subName, q.topic, 0, nil)
	if code := grpc.Code(err); code != codes.OK && code != codes.AlreadyExists {
		return nil, errors.Wrap(err, "NewGCPPubSubQueue: could not create subscription")
	}

	q.subscription.ReceiveSettings.MaxOutstandingMessages = 1 // limit concurrency

	return q, nil
}

// Wait waits for messages on queuePush and adds them to the Pub/Sub queue.
// Upon receiving messages from Pub/Sub, f is invoked with the message. Wait
// is non-blocking, increments wg for each routine started, and when context
// is closed will mark the wg as done as routines are shutdown.
func (q GCPPubSubQueue) Wait(ctx context.Context, wg *sync.WaitGroup, queuePush <-chan interface{}, f func(interface{})) {
	// Routine to add jobs to the GCP Pub/Sub Queue
	wg.Add(1)
	go func() {
		for {
			select {
			case <-ctx.Done():
				log.Println("GCPPubSubQueue: job waiter exiting")
				q.topic.Stop()
				wg.Done()
				return
			case job := <-queuePush:
				log.Println("GCPPubSubQueue: job waiter got message, queuing...")
				q.queue(ctx, job)
			}
		}
	}()

	// Routine to listen for jobs and process one at a time
	wg.Add(1)
	go func() {
		q.receive(ctx, f)
		log.Println("GCPPubSubQueue: job receiver exiting")
		wg.Done()
	}()
}

// queue adds a message to the queue.
func (q *GCPPubSubQueue) queue(ctx context.Context, job interface{}) error {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(container{job}); err != nil {
		return errors.Wrap(err, "GCPPubSubQueue: could not gob encode job")
	}

	var (
		msg         = &pubsub.Message{Data: buf.Bytes()}
		maxAttempts = 3
		msgID       string
		err         error
	)
	for i := 1; i <= maxAttempts; i++ {
		res := q.topic.Publish(ctx, msg)
		msgID, err = res.Get(ctx)
		if err == nil {
			break
		}
		log.Printf("GCPPubSubQueue: failed publishing message attempt %v of %v, error: %v", i, maxAttempts, err)
		time.Sleep(time.Duration(i) * time.Second)
	}
	if err != nil {
		return errors.Wrap(err, "GCPPubSubQueue: could not publish job")
	}
	log.Println("GCPPubSubQueue: published a message with a message ID:", msgID)

	return nil
}

type container struct {
	Job interface{}
}

// receive calls sub.Receive, which blocks forever waiting for new jobs.
func (q *GCPPubSubQueue) receive(ctx context.Context, f func(interface{})) {
	err := q.subscription.Receive(ctx, func(ctx xContext.Context, msg *pubsub.Message) {
		log.Printf("GCPPubSubQueue: processing ID %v, published at %v", msg.ID, msg.PublishTime)

		// Acknowledge the job now, anything else that could fail by this instance
		// will probably fail for others.
		msg.Ack()
		log.Printf("GCPPubSubQueue: ack'd ID %v", msg.ID)

		reader := bytes.NewReader(msg.Data)
		dec := gob.NewDecoder(reader)

		var job container
		if err := dec.Decode(&job); err != nil {
			log.Println("GCPPubSubQueue: could not decode job:", err)
			return
		}
		log.Printf("GCPPubSubQueue: process ID %v", msg.ID)

		f(job.Job)
	})
	if err != nil && err != context.Canceled {
		log.Printf("GCPPubSubQueue: could not receive on subscription: %v", err)
	}
}

// delete deletes the topic and subcriptions, used to cleanup unit tests.
func (q *GCPPubSubQueue) delete(ctx context.Context) {
	itr := q.topic.Subscriptions(ctx)
	for {
		sub, err := itr.Next()
		if err != nil {
			break
		}
		err = sub.Delete(ctx)
		if err != nil {
			log.Println("GCPPubSubQueue: delete subscription error:", err)
		}
	}
	err := q.topic.Delete(ctx)
	if err != nil {
		log.Println("GCPPubSubQueue: delete topic error:", err)
	}
}
<commit_msg>Change GCP Pub/Sub CreateSubscription to fix breaking change<commit_after>package queue

import (
	"bytes"
	"context"
	"encoding/gob"
	"log"
	"sync"
	"time"

	xContext "golang.org/x/net/context"

	"github.com/google/go-github/github"
	"github.com/pkg/errors"

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

	"cloud.google.com/go/pubsub"
)

func init() {
	// List of all types that could be added to the queue
	gob.Register(&github.PullRequestEvent{})
	gob.Register(&github.PushEvent{})
}

const (
	// version should be changed each time the message format changes in an
	// incompatible way. This will then cause new subscribers to listen on the
	// new topic.
	version          = "1"
	defaultSubName   = "worker"
	defaultTopicName = "gopherci-ci"
)

// GCPPubSubQueue is a queue using Google Compute Platform's PubSub product.
type GCPPubSubQueue struct {
	topic        *pubsub.Topic
	subscription *pubsub.Subscription
}

var cxnTimeout = 15 * time.Second

// NewGCPPubSubQueue creates connects to Google Pub/Sub with a topic and
// subscriber in a one-to-one architecture.
func NewGCPPubSubQueue(ctx context.Context, projectID, topicName string) (*GCPPubSubQueue, error) {
	q := &GCPPubSubQueue{}

	if projectID == "" {
		return nil, errors.New("projectID must not be empty")
	}

	// create a context with a timeout for exclusive use of connection setup to
	// ensure connnection setup doesn't block and can fail early.
	cxnCtx, cancel := context.WithTimeout(ctx, cxnTimeout)
	defer cancel()

	client, err := pubsub.NewClient(cxnCtx, projectID)
	if err != nil {
		return nil, errors.Wrap(err, "NewGCPPubSubQueue: could not create client")
	}

	if topicName == "" {
		topicName = defaultTopicName
	}
	topicName += "-v" + version

	log.Printf("NewGCPPubSubQueue: creating topic %q", topicName)
	q.topic, err = client.CreateTopic(cxnCtx, topicName)
	if code := grpc.Code(err); code != codes.OK && code != codes.AlreadyExists {
		return nil, errors.Wrap(err, "NewGCPPubSubQueue: could not create topic")
	}

	subName := topicName + "-" + defaultSubName

	log.Printf("NewGCPPubSubQueue: creating subscription %q", subName)
	q.subscription, err = client.CreateSubscription(cxnCtx, subName, pubsub.SubscriptionConfig{
		Topic:       q.topic,
		AckDeadline: 0,
	})
	if code := grpc.Code(err); code != codes.OK && code != codes.AlreadyExists {
		return nil, errors.Wrap(err, "NewGCPPubSubQueue: could not create subscription")
	}

	q.subscription.ReceiveSettings.MaxOutstandingMessages = 1 // limit concurrency

	return q, nil
}

// Wait waits for messages on queuePush and adds them to the Pub/Sub queue.
// Upon receiving messages from Pub/Sub, f is invoked with the message. Wait
// is non-blocking, increments wg for each routine started, and when context
// is closed will mark the wg as done as routines are shutdown.
func (q GCPPubSubQueue) Wait(ctx context.Context, wg *sync.WaitGroup, queuePush <-chan interface{}, f func(interface{})) {
	// Routine to add jobs to the GCP Pub/Sub Queue
	wg.Add(1)
	go func() {
		for {
			select {
			case <-ctx.Done():
				log.Println("GCPPubSubQueue: job waiter exiting")
				q.topic.Stop()
				wg.Done()
				return
			case job := <-queuePush:
				log.Println("GCPPubSubQueue: job waiter got message, queuing...")
				q.queue(ctx, job)
			}
		}
	}()

	// Routine to listen for jobs and process one at a time
	wg.Add(1)
	go func() {
		q.receive(ctx, f)
		log.Println("GCPPubSubQueue: job receiver exiting")
		wg.Done()
	}()
}

// queue adds a message to the queue.
func (q *GCPPubSubQueue) queue(ctx context.Context, job interface{}) error {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(container{job}); err != nil {
		return errors.Wrap(err, "GCPPubSubQueue: could not gob encode job")
	}

	var (
		msg         = &pubsub.Message{Data: buf.Bytes()}
		maxAttempts = 3
		msgID       string
		err         error
	)
	for i := 1; i <= maxAttempts; i++ {
		res := q.topic.Publish(ctx, msg)
		msgID, err = res.Get(ctx)
		if err == nil {
			break
		}
		log.Printf("GCPPubSubQueue: failed publishing message attempt %v of %v, error: %v", i, maxAttempts, err)
		time.Sleep(time.Duration(i) * time.Second)
	}
	if err != nil {
		return errors.Wrap(err, "GCPPubSubQueue: could not publish job")
	}
	log.Println("GCPPubSubQueue: published a message with a message ID:", msgID)

	return nil
}

type container struct {
	Job interface{}
}

// receive calls sub.Receive, which blocks forever waiting for new jobs.
func (q *GCPPubSubQueue) receive(ctx context.Context, f func(interface{})) {
	err := q.subscription.Receive(ctx, func(ctx xContext.Context, msg *pubsub.Message) {
		log.Printf("GCPPubSubQueue: processing ID %v, published at %v", msg.ID, msg.PublishTime)

		// Acknowledge the job now, anything else that could fail by this instance
		// will probably fail for others.
		msg.Ack()
		log.Printf("GCPPubSubQueue: ack'd ID %v", msg.ID)

		reader := bytes.NewReader(msg.Data)
		dec := gob.NewDecoder(reader)

		var job container
		if err := dec.Decode(&job); err != nil {
			log.Println("GCPPubSubQueue: could not decode job:", err)
			return
		}
		log.Printf("GCPPubSubQueue: process ID %v", msg.ID)

		f(job.Job)
	})
	if err != nil && err != context.Canceled {
		log.Printf("GCPPubSubQueue: could not receive on subscription: %v", err)
	}
}

// delete deletes the topic and subcriptions, used to cleanup unit tests.
func (q *GCPPubSubQueue) delete(ctx context.Context) {
	itr := q.topic.Subscriptions(ctx)
	for {
		sub, err := itr.Next()
		if err != nil {
			break
		}
		err = sub.Delete(ctx)
		if err != nil {
			log.Println("GCPPubSubQueue: delete subscription error:", err)
		}
	}
	err := q.topic.Delete(ctx)
	if err != nil {
		log.Println("GCPPubSubQueue: delete topic error:", err)
	}
}
<|endoftext|>


<commit_before>package testhelpers

// TODO when applying this patch to Hydra 2.x, delete this file and move its contents to ory/x/requirex

import (
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func RequireEqualDuration(t *testing.T, expected time.Duration, actual time.Duration, precision time.Duration) {
	delta := expected - actual
	if delta < 0 {
		delta = -delta
	}
	require.Less(t, delta, precision, fmt.Sprintf("expected %s; got %s", expected, actual))
}

func RequireEqualTime(t *testing.T, expected time.Time, actual time.Time, precision time.Duration) {
	delta := expected.Sub(actual)
	if delta < 0 {
		delta = -delta
	}
	require.Less(t, delta, precision, fmt.Sprintf(
		"expected %s; got %s",
		expected.Format(time.RFC3339Nano),
		actual.Format(time.RFC3339Nano),
	))
}
<commit_msg>chore: delete unused code<commit_after><|endoftext|>


<commit_before>package db

import (
	"context"
	"database/sql"
	"fmt"
	"os"
	"strconv"

	"github.com/rs/zerolog"

	"github.com/gilcrest/go-API-template/errors"
	"github.com/gomodule/redigo/redis"
	"github.com/rs/zerolog/log"

	_ "github.com/lib/pq" // pq driver calls for blank identifier
)

// name defines database name
type name int

const (
	// AppDB represents main application database
	AppDB name = iota

	// LogDB represents http logging database
	LogDB
)

// Datastore struct stores common environment related items
type Datastore struct {
	mainDB  *sql.DB
	logDB   *sql.DB
	cacheDB *redis.Pool
}

// NewDatastore initializes the datastore struct
// NOTE: I have chosen to use the same database for logging as
// my "main" app database. I'd recommend having a separate db and
// would have a separate method to start that connection pool up and
// pass it, but since this is just an example....
func NewDatastore() (*Datastore, error) {
	const op errors.Op = "db.NewDatastore"

	// Get a mainDB object (PostgreSQL)
	mdb, err := newMainDB()
	if err != nil {
		log.Error().Err(err).Msg("Error returned from newMainDB")
		return nil, err
	}

	// Get a Redis Pool from redigo client
	cDB := newCacheDb()

	// For now, store mainDB object as mainDB and logDB as they are
	// currently the same. cacheDB is Redis
	return &Datastore{mainDB: mdb, logDB: mdb, cacheDB: cDB}, nil
}

// NewMainDB returns an open database handle of 0 or more underlying connections
func newMainDB() (*sql.DB, error) {
	const op errors.Op = "db.newMainDB"

	// Get Database connection credentials from environment variables
	dbName := os.Getenv("PG_DBNAME_TEST")
	dbUser := os.Getenv("PG_USERNAME_TEST")
	dbPassword := os.Getenv("PG_PASSWORD_TEST")
	dbHost := os.Getenv("PG_HOST_TEST")
	dbPort, err := strconv.Atoi(os.Getenv("PG_PORT_TEST"))
	if err != nil {
		log.Error().Err(err).Msg("Unable to complete string to int conversion for dbPort")
		return nil, err
	}

	// Craft string for database connection
	dbinfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", dbHost, dbPort, dbUser, dbPassword, dbName)

	// Open the postgres database using the postgres driver (pq)
	// func Open(driverName, dataSourceName string) (*DB, error)
	db, err := sql.Open("postgres", dbinfo)
	if err != nil {
		log.Error().Err(err).Msg("Error returned from sql.Open")
		return nil, err
	}

	if err = db.Ping(); err != nil {
		log.Error().Err(err).Msg("Error returned from db.Ping")
		return nil, err
	}
	return db, nil
}

// NewMainDB returns an pool of redis connections from
// which an application can get a new connection
func newCacheDb() *redis.Pool {
	const op errors.Op = "db.newCacheDb"
	return &redis.Pool{
		// Maximum number of idle connections in the pool.
		MaxIdle: 80,
		// max number of connections
		MaxActive: 12000,
		// Dial is an application supplied function for creating and
		// configuring a connection.
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", ":6379")
			if err != nil {
				panic(err.Error())
			}
			return c, err
		},
	}
}

// RedisConn gets a connection from ds.cacheDB redis cache
func (ds Datastore) RedisConn() (redis.Conn, error) {
	const op errors.Op = "db.RedisConn"

	conn := ds.cacheDB.Get()

	err := conn.Err()
	if err != nil {
		return nil, err
	}
	return conn, nil
}

// BeginTx begins a *sql.Tx for the given db
func (ds Datastore) BeginTx(ctx context.Context, opts *sql.TxOptions, n name) (*sql.Tx, error) {
	const op errors.Op = "db.Datastore.BeginTx"

	switch n {
	case AppDB:
		// Calls the BeginTx method of the mainDb opened database
		mtx, err := ds.mainDB.BeginTx(ctx, opts)
		if err != nil {
			return nil, errors.E(op, err)
		}

		return mtx, nil
	case LogDB:
		// Calls the BeginTx method of the mogDB opened database
		ltx, err := ds.logDB.BeginTx(ctx, opts)
		if err != nil {
			return nil, errors.E(op, err)
		}

		return ltx, nil
	default:
		return nil, errors.E(op, "Unexpected Database Name")
	}
}

// FinalizeTx will attempt to commit or rollback the db transaction
// If the commit is successful, no error will be nil
// If the commit is not successful, a rollback will be attempted and
// the error will not be nil
func FinalizeTx(ctx context.Context, log zerolog.Logger, tx *sql.Tx, commit bool) error {

	if commit {
		err := tx.Commit()
		if err != nil {
			return err
		}
	} else {
		err := tx.Rollback()
		if err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>remove finalizetx<commit_after>package db

import (
	"context"
	"database/sql"
	"fmt"
	"os"
	"strconv"

	"github.com/gilcrest/go-API-template/errors"
	"github.com/gomodule/redigo/redis"
	"github.com/rs/zerolog/log"

	_ "github.com/lib/pq" // pq driver calls for blank identifier
)

// name defines database name
type name int

const (
	// AppDB represents main application database
	AppDB name = iota

	// LogDB represents http logging database
	LogDB
)

// Datastore struct stores common environment related items
type Datastore struct {
	mainDB  *sql.DB
	logDB   *sql.DB
	cacheDB *redis.Pool
}

// NewDatastore initializes the datastore struct
// NOTE: I have chosen to use the same database for logging as
// my "main" app database. I'd recommend having a separate db and
// would have a separate method to start that connection pool up and
// pass it, but since this is just an example....
func NewDatastore() (*Datastore, error) {
	const op errors.Op = "db.NewDatastore"

	// Get a mainDB object (PostgreSQL)
	mdb, err := newMainDB()
	if err != nil {
		log.Error().Err(err).Msg("Error returned from newMainDB")
		return nil, err
	}

	// Get a Redis Pool from redigo client
	cDB := newCacheDb()

	// For now, store mainDB object as mainDB and logDB as they are
	// currently the same. cacheDB is Redis
	return &Datastore{mainDB: mdb, logDB: mdb, cacheDB: cDB}, nil
}

// NewMainDB returns an open database handle of 0 or more underlying connections
func newMainDB() (*sql.DB, error) {
	const op errors.Op = "db.newMainDB"

	// Get Database connection credentials from environment variables
	dbName := os.Getenv("PG_DBNAME_TEST")
	dbUser := os.Getenv("PG_USERNAME_TEST")
	dbPassword := os.Getenv("PG_PASSWORD_TEST")
	dbHost := os.Getenv("PG_HOST_TEST")
	dbPort, err := strconv.Atoi(os.Getenv("PG_PORT_TEST"))
	if err != nil {
		log.Error().Err(err).Msg("Unable to complete string to int conversion for dbPort")
		return nil, err
	}

	// Craft string for database connection
	dbinfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", dbHost, dbPort, dbUser, dbPassword, dbName)

	// Open the postgres database using the postgres driver (pq)
	// func Open(driverName, dataSourceName string) (*DB, error)
	db, err := sql.Open("postgres", dbinfo)
	if err != nil {
		log.Error().Err(err).Msg("Error returned from sql.Open")
		return nil, err
	}

	if err = db.Ping(); err != nil {
		log.Error().Err(err).Msg("Error returned from db.Ping")
		return nil, err
	}
	return db, nil
}

// NewMainDB returns an pool of redis connections from
// which an application can get a new connection
func newCacheDb() *redis.Pool {
	const op errors.Op = "db.newCacheDb"
	return &redis.Pool{
		// Maximum number of idle connections in the pool.
		MaxIdle: 80,
		// max number of connections
		MaxActive: 12000,
		// Dial is an application supplied function for creating and
		// configuring a connection.
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", ":6379")
			if err != nil {
				panic(err.Error())
			}
			return c, err
		},
	}
}

// RedisConn gets a connection from ds.cacheDB redis cache
func (ds Datastore) RedisConn() (redis.Conn, error) {
	const op errors.Op = "db.RedisConn"

	conn := ds.cacheDB.Get()

	err := conn.Err()
	if err != nil {
		return nil, err
	}
	return conn, nil
}

// BeginTx begins a *sql.Tx for the given db
func (ds Datastore) BeginTx(ctx context.Context, opts *sql.TxOptions, n name) (*sql.Tx, error) {
	const op errors.Op = "db.Datastore.BeginTx"

	switch n {
	case AppDB:
		// Calls the BeginTx method of the mainDb opened database
		mtx, err := ds.mainDB.BeginTx(ctx, opts)
		if err != nil {
			return nil, errors.E(op, err)
		}

		return mtx, nil
	case LogDB:
		// Calls the BeginTx method of the mogDB opened database
		ltx, err := ds.logDB.BeginTx(ctx, opts)
		if err != nil {
			return nil, errors.E(op, err)
		}

		return ltx, nil
	default:
		return nil, errors.E(op, "Unexpected Database Name")
	}
}
<|endoftext|>


<commit_before>package db

import (
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

const (
	TRADB      = ".trago.db"
	bytes      = "abcdefghijklmnopqrstuvwxyz1234567890"
	currentDir = "./"
)

type TraDb struct {
	ReplicaId  string
	VersionVec map[string]int
	Files      map[string]FileState
}

type FileState struct {
	Size    int
	MTime   int64
	Version int
	Replica string
	// TODO: use a hash as well
}

func Parse(data string) (TraDb, error) {
	tradb := TraDb{}
	versionVector := make(map[string]int)

	tradb.Files = make(map[string]FileState)

	for _, line := range strings.Split(data, "\n") {
		line = strings.TrimSpace(line)

		if strings.HasPrefix(line, "#") {
			continue
		}

		fields := strings.Fields(line)
		if len(fields) == 0 {
			continue
		}

		switch fields[0] {
		case "file": // file name size mtime replica:version
			if len(fields) != 5 {
				continue
			}

			size, err := strconv.Atoi(fields[2])
			if err != nil {
				return tradb, err
			}

			mtime, err := strconv.ParseInt(fields[3], 10, 64)
			if err != nil {
				return tradb, err
			}

			pair := strings.Split(fields[4], ":")
			replicaId := pair[0]
			ver, err := strconv.Atoi(pair[1])
			if err != nil {
				return tradb, err
			}

			tradb.Files[fields[1]] = FileState{size, mtime, ver, replicaId}
		case "version": // version r1:v1 r2:v2 ...
			for _, entry := range fields[1:] {
				pair := strings.Split(entry, ":") // replica:version pair

				v, err := strconv.Atoi(pair[1])
				if err != nil {
					return tradb, err
				}

				versionVector[pair[0]] = v
			}
			tradb.VersionVec = versionVector

		case "replica": // replica replica-id
			if len(fields) != 2 {
				continue
			}
			tradb.ReplicaId = fields[1]
		}
	}

	return tradb, nil
}

func ParseFile() (TraDb, error) {
	tradb := TraDb{}

	dbfile, err := os.Open(TRADB)
	if os.IsNotExist(err) {
		log.Println("didn't find .trago.db")
		tradb = *New()
		tradb.Write()

		return tradb, nil
	} else if err != nil {
		return tradb, err
	}

	defer dbfile.Close()

	bs, err := ioutil.ReadFile(TRADB)
	if err != nil {
		return TraDb{}, err
	}

	return Parse(string(bs))
}

func New() *TraDb {
	replicaId := make([]byte, 16)
	versionVector := make(map[string]int)

	rand.Seed(time.Now().UTC().UnixNano())
	for i, _ := range replicaId {
		replicaId[i] = bytes[rand.Intn(len(bytes))]
	}
	versionVector[string(replicaId)] = 1

	files, err := ioutil.ReadDir(currentDir)
	checkError(err)

	filemap := make(map[string]FileState)
	for _, file := range files {
		if file.IsDir() {
			continue // ignore directories for now
		}
		fs := FileState{
			Size:    int(file.Size()),
			MTime:   file.ModTime().UnixNano(),
			Version: 1,
			Replica: string(replicaId),
		}
		filemap[file.Name()] = fs
	}

	return &TraDb{string(replicaId), versionVector, filemap}
}

func (tradb *TraDb) Write() error {
	var pairs []string

	for replicaId, version := range tradb.VersionVec {
		entry := strings.Join([]string{replicaId, strconv.Itoa(version)}, ":")
		pairs = append(pairs, entry)
	}

	versionVector := strings.Join(pairs, " ")

	preamble := fmt.Sprintf(
		"replica %s\nversion %s\n# files\n",
		tradb.ReplicaId,
		versionVector,
	)

	fileEntries := make([]string, len(tradb.Files))

	i := 0
	for filename, info := range tradb.Files {
		fileEntries[i] = fmt.Sprintf(
			"file %s %d %d %s:%d",
			filename,
			info.Size,
			info.MTime,
			info.Replica,
			info.Version,
		)
		i = i + 1
	}

	entryString := strings.Join(fileEntries, "\n")
	dataToWrite := []byte(preamble + entryString)

	err := ioutil.WriteFile(TRADB, dataToWrite, 0644)
	return err
}

func (db *TraDb) Update() error {
	files, err := ioutil.ReadDir(currentDir)
	if err != nil {
		return err
	}

	for _, file := range files {
		if file.IsDir() {
			continue
		}

		filename := file.Name()
		dbRecord := db.Files[filename]
		if dbRecord.MTime == 0 {
			log.Printf("found a new file: %s\n", filename)
		} else if dbRecord.MTime < file.ModTime().UnixNano() {
			log.Printf("found an updated file: %s\n", filename)
			dbRecord.MTime = file.ModTime().UnixNano()
			dbRecord.Version = db.VersionVec[db.ReplicaId]
		} else {
			log.Printf("file unchanged: %s\n", filename)
		}
	}

	db.VersionVec[db.ReplicaId] += 1
	return nil
}

func (local *TraDb) Compare(remote *TraDb) {
	remoteFiles := remote.Files

	for file, state := range local.Files {
		remoteState := remoteFiles[file]

		if remoteState.Version == 0 { // file not present on server
			// TODO: download only if we have a more "recent" copy
			continue
		}

		if isFileChanged(state, remoteState) {
			if local.VersionVec[remoteState.Replica] >= remoteState.Version {
				continue // we already know about changes on remote
			} else if remote.VersionVec[state.Replica] >= state.Version {
				log.Printf("downloading: %s\n", file)
				continue
			} else {
				log.Printf("conflict: %s\n", file)
			}
		}
	}
}

func isFileChanged(fs1 FileState, fs2 FileState) bool {
	if fs1.MTime != fs2.MTime || fs1.Size != fs2.Size {
		return false
	}
	return true
}

func checkError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>use utc<commit_after>package db

import (
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

const (
	TRADB      = ".trago.db"
	bytes      = "abcdefghijklmnopqrstuvwxyz1234567890"
	currentDir = "./"
)

type TraDb struct {
	ReplicaId  string
	VersionVec map[string]int
	Files      map[string]FileState
}

type FileState struct {
	Size    int
	MTime   int64
	Version int
	Replica string
	// TODO: use a hash as well
}

func Parse(data string) (TraDb, error) {
	tradb := TraDb{}
	versionVector := make(map[string]int)

	tradb.Files = make(map[string]FileState)

	for _, line := range strings.Split(data, "\n") {
		line = strings.TrimSpace(line)

		if strings.HasPrefix(line, "#") {
			continue
		}

		fields := strings.Fields(line)
		if len(fields) == 0 {
			continue
		}

		switch fields[0] {
		case "file": // file name size mtime replica:version
			if len(fields) != 5 {
				continue
			}

			size, err := strconv.Atoi(fields[2])
			if err != nil {
				return tradb, err
			}

			mtime, err := strconv.ParseInt(fields[3], 10, 64)
			if err != nil {
				return tradb, err
			}

			pair := strings.Split(fields[4], ":")
			replicaId := pair[0]
			ver, err := strconv.Atoi(pair[1])
			if err != nil {
				return tradb, err
			}

			tradb.Files[fields[1]] = FileState{size, mtime, ver, replicaId}
		case "version": // version r1:v1 r2:v2 ...
			for _, entry := range fields[1:] {
				pair := strings.Split(entry, ":") // replica:version pair

				v, err := strconv.Atoi(pair[1])
				if err != nil {
					return tradb, err
				}

				versionVector[pair[0]] = v
			}
			tradb.VersionVec = versionVector

		case "replica": // replica replica-id
			if len(fields) != 2 {
				continue
			}
			tradb.ReplicaId = fields[1]
		}
	}

	return tradb, nil
}

func ParseFile() (TraDb, error) {
	tradb := TraDb{}

	dbfile, err := os.Open(TRADB)
	if os.IsNotExist(err) {
		log.Println("didn't find .trago.db")
		tradb = *New()
		tradb.Write()

		return tradb, nil
	} else if err != nil {
		return tradb, err
	}

	defer dbfile.Close()

	bs, err := ioutil.ReadFile(TRADB)
	if err != nil {
		return TraDb{}, err
	}

	return Parse(string(bs))
}

func New() *TraDb {
	replicaId := make([]byte, 16)
	versionVector := make(map[string]int)

	rand.Seed(time.Now().UTC().UnixNano())
	for i, _ := range replicaId {
		replicaId[i] = bytes[rand.Intn(len(bytes))]
	}
	versionVector[string(replicaId)] = 1

	files, err := ioutil.ReadDir(currentDir)
	checkError(err)

	filemap := make(map[string]FileState)
	for _, file := range files {
		if file.IsDir() {
			continue // ignore directories for now
		}
		fs := FileState{
			Size:    int(file.Size()),
			MTime:   file.ModTime().UTC().UnixNano(),
			Version: 1,
			Replica: string(replicaId),
		}
		filemap[file.Name()] = fs
	}

	return &TraDb{string(replicaId), versionVector, filemap}
}

func (tradb *TraDb) Write() error {
	var pairs []string

	for replicaId, version := range tradb.VersionVec {
		entry := strings.Join([]string{replicaId, strconv.Itoa(version)}, ":")
		pairs = append(pairs, entry)
	}

	versionVector := strings.Join(pairs, " ")

	preamble := fmt.Sprintf(
		"replica %s\nversion %s\n# files\n",
		tradb.ReplicaId,
		versionVector,
	)

	fileEntries := make([]string, len(tradb.Files))

	i := 0
	for filename, info := range tradb.Files {
		fileEntries[i] = fmt.Sprintf(
			"file %s %d %d %s:%d",
			filename,
			info.Size,
			info.MTime,
			info.Replica,
			info.Version,
		)
		i = i + 1
	}

	entryString := strings.Join(fileEntries, "\n")
	dataToWrite := []byte(preamble + entryString)

	err := ioutil.WriteFile(TRADB, dataToWrite, 0644)
	return err
}

func (db *TraDb) Update() error {
	files, err := ioutil.ReadDir(currentDir)
	if err != nil {
		return err
	}

	for _, file := range files {
		if file.IsDir() {
			continue
		}

		filename := file.Name()
		dbRecord := db.Files[filename]
		if dbRecord.MTime == 0 {
			log.Printf("found a new file: %s\n", filename)
		} else if dbRecord.MTime < file.ModTime().UTC().UnixNano() {
			log.Printf("found an updated file: %s\n", filename)
			dbRecord.MTime = file.ModTime().UTC().UnixNano()
			dbRecord.Version = db.VersionVec[db.ReplicaId]
		} else {
			log.Printf("file unchanged: %s\n", filename)
		}
	}

	db.VersionVec[db.ReplicaId] += 1
	return nil
}

func (local *TraDb) Compare(remote *TraDb) {
	remoteFiles := remote.Files

	for file, state := range local.Files {
		remoteState := remoteFiles[file]

		if remoteState.Version == 0 { // file not present on server
			// TODO: download only if we have a more "recent" copy
			continue
		}

		if isFileChanged(state, remoteState) {
			if local.VersionVec[remoteState.Replica] >= remoteState.Version {
				continue // we already know about changes on remote
			} else if remote.VersionVec[state.Replica] >= state.Version {
				log.Printf("downloading: %s\n", file)
				continue
			} else {
				log.Printf("conflict: %s\n", file)
			}
		}
	}
}

func isFileChanged(fs1 FileState, fs2 FileState) bool {
	if fs1.MTime != fs2.MTime || fs1.Size != fs2.Size {
		return false
	}
	return true
}

func checkError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package db

import (
	"database/sql"

	"github.com/go-sql-driver/mysql"
	"github.com/pkg/errors"
)

const driverName = `mysql`

var _db *sql.DB // global database connection

// Init database connection
func Init(c *mysql.Config) (err error) {
	if c == nil {
		c = &mysql.Config{
			User:      "root",
			Passwd:    "password",
			Net:       "tcp",
			Addr:      "127.0.0.1:3306",
			DBName:    "apidb",
			ParseTime: true,
		}
	}

	dsn := c.FormatDSN()

	_db, err = sql.Open(driverName, dsn)
	if err != nil {
		return errors.Wrap(err, `connecting database`)
	}
	return nil
}

// BeginTx returns a transaction
func BeginTx() (*sql.Tx, error) {
	if _db == nil {
		return nil, errors.New(`database connection has not been initialized`)
	}
	tx, err := _db.Begin()
	if err != nil {
		return nil, errors.Wrap(err, `begining transaction`)
	}
	return tx, nil
}
<commit_msg>remove: default password<commit_after>package db

import (
	"database/sql"

	"github.com/go-sql-driver/mysql"
	"github.com/pkg/errors"
)

const driverName = `mysql`

var _db *sql.DB // global database connection

// Init database connection
func Init(c *mysql.Config) (err error) {
	if c == nil {
		c = &mysql.Config{
			User:      "root",
			Net:       "tcp",
			Addr:      "127.0.0.1:3306",
			DBName:    "apidb",
			ParseTime: true,
		}
	}

	dsn := c.FormatDSN()

	_db, err = sql.Open(driverName, dsn)
	if err != nil {
		return errors.Wrap(err, `connecting database`)
	}
	return nil
}

// BeginTx returns a transaction
func BeginTx() (*sql.Tx, error) {
	if _db == nil {
		return nil, errors.New(`database connection has not been initialized`)
	}
	tx, err := _db.Begin()
	if err != nil {
		return nil, errors.Wrap(err, `begining transaction`)
	}
	return tx, nil
}
<|endoftext|>


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

package ipv6_test

import (
	"net"
	"testing"
)

func isLinkLocalUnicast(ip net.IP) bool {
	return ip.To4() == nil && ip.To16() != nil && ip.IsLinkLocalUnicast()
}

func loopbackInterface() *net.Interface {
	ift, err := net.Interfaces()
	if err != nil {
		return nil
	}
	for _, ifi := range ift {
		if ifi.Flags&net.FlagLoopback == 0 || ifi.Flags&net.FlagUp == 0 {
			continue
		}
		ifat, err := ifi.Addrs()
		if err != nil {
			continue
		}
		for _, ifa := range ifat {
			switch ifa := ifa.(type) {
			case *net.IPAddr:
				if isLinkLocalUnicast(ifa.IP) {
					return &ifi
				}
			case *net.IPNet:
				if isLinkLocalUnicast(ifa.IP) {
					return &ifi
				}
			}
		}
	}
	return nil
}

func isMulticastAvailable(ifi *net.Interface) (net.IP, bool) {
	if ifi == nil || ifi.Flags&net.FlagUp == 0 || ifi.Flags&net.FlagMulticast == 0 {
		return nil, false
	}
	ifat, err := ifi.Addrs()
	if err != nil {
		return nil, false
	}
	for _, ifa := range ifat {
		switch ifa := ifa.(type) {
		case *net.IPAddr:
			if isLinkLocalUnicast(ifa.IP) {
				return ifa.IP, true
			}
		case *net.IPNet:
			if isLinkLocalUnicast(ifa.IP) {
				return ifa.IP, true
			}
		}
	}
	return nil, false
}

func connector(t *testing.T, network, addr string, done chan<- bool) {
	defer func() { done <- true }()

	c, err := net.Dial(network, addr)
	if err != nil {
		t.Errorf("net.Dial failed: %v", err)
		return
	}
	c.Close()
}

func acceptor(t *testing.T, ln net.Listener, done chan<- bool) {
	defer func() { done <- true }()

	c, err := ln.Accept()
	if err != nil {
		t.Errorf("net.Listener.Accept failed: %v", err)
		return
	}
	c.Close()
}

func transponder(t *testing.T, ln net.Listener, done chan<- bool) {
	defer func() { done <- true }()

	c, err := ln.Accept()
	if err != nil {
		t.Errorf("net.Listener.Accept failed: %v", err)
		return
	}
	defer c.Close()

	b := make([]byte, 128)
	n, err := c.Read(b)
	if err != nil {
		t.Errorf("net.Conn.Read failed: %v", err)
		return
	}
	if _, err := c.Write(b[:n]); err != nil {
		t.Errorf("net.Conn.Write failed: %v", err)
		return
	}
}
<commit_msg>go.net/ipv6: remove unused test code<commit_after>// Copyright 2013 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ipv6_test

import (
	"net"
	"testing"
)

func isLinkLocalUnicast(ip net.IP) bool {
	return ip.To4() == nil && ip.To16() != nil && ip.IsLinkLocalUnicast()
}

func loopbackInterface() *net.Interface {
	ift, err := net.Interfaces()
	if err != nil {
		return nil
	}
	for _, ifi := range ift {
		if ifi.Flags&net.FlagLoopback == 0 || ifi.Flags&net.FlagUp == 0 {
			continue
		}
		ifat, err := ifi.Addrs()
		if err != nil {
			continue
		}
		for _, ifa := range ifat {
			switch ifa := ifa.(type) {
			case *net.IPAddr:
				if isLinkLocalUnicast(ifa.IP) {
					return &ifi
				}
			case *net.IPNet:
				if isLinkLocalUnicast(ifa.IP) {
					return &ifi
				}
			}
		}
	}
	return nil
}

func isMulticastAvailable(ifi *net.Interface) (net.IP, bool) {
	if ifi == nil || ifi.Flags&net.FlagUp == 0 || ifi.Flags&net.FlagMulticast == 0 {
		return nil, false
	}
	ifat, err := ifi.Addrs()
	if err != nil {
		return nil, false
	}
	for _, ifa := range ifat {
		switch ifa := ifa.(type) {
		case *net.IPAddr:
			if isLinkLocalUnicast(ifa.IP) {
				return ifa.IP, true
			}
		case *net.IPNet:
			if isLinkLocalUnicast(ifa.IP) {
				return ifa.IP, true
			}
		}
	}
	return nil, false
}

func connector(t *testing.T, network, addr string, done chan<- bool) {
	defer func() { done <- true }()

	c, err := net.Dial(network, addr)
	if err != nil {
		t.Errorf("net.Dial failed: %v", err)
		return
	}
	c.Close()
}

func acceptor(t *testing.T, ln net.Listener, done chan<- bool) {
	defer func() { done <- true }()

	c, err := ln.Accept()
	if err != nil {
		t.Errorf("net.Listener.Accept failed: %v", err)
		return
	}
	c.Close()
}
<|endoftext|>


<commit_before>// Copyright 2015 Matthew Collins
//
// 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 render

import (
	"math"
	"sort"
	"strings"

	"github.com/thinkofdeath/steven/platform"
	"github.com/thinkofdeath/steven/platform/gl"
	"github.com/thinkofdeath/steven/type/direction"
	"github.com/thinkofdeath/steven/type/vmath"
)

var (
	chunkProgram  gl.Program
	shaderChunk   *chunkShader
	chunkProgramT gl.Program
	shaderChunkT  *chunkShader

	lastWidth, lastHeight int = -1, -1
	perspectiveMatrix         = vmath.NewMatrix4()
	cameraMatrix              = vmath.NewMatrix4()

	syncChan = make(chan func(), 500)

	glTextures []gl.Texture
)

// Start starts the renderer
func Start(debug bool) {
	if debug {
		gl.Enable(gl.DebugOutput)
		gl.DebugLog()
	}

	gl.ClearColor(0.0, 1.0, 1.0, 1.0)
	gl.Enable(gl.DepthTest)
	gl.Enable(gl.CullFaceFlag)
	gl.CullFace(gl.Back)
	gl.FrontFace(gl.ClockWise)

	chunkProgram = CreateProgram(vertex, fragment)
	shaderChunk = &chunkShader{}
	InitStruct(shaderChunk, chunkProgram)

	chunkProgramT = CreateProgram(vertex, strings.Replace(fragment, "#version 150", "#version 150\n#define alpha", 1))
	shaderChunkT = &chunkShader{}
	InitStruct(shaderChunkT, chunkProgramT)

	textureLock.Lock()
	for _, tex := range textures {
		glTextures = append(glTextures, createTexture(glTexture{
			Data:  tex.Buffer,
			Width: atlasSize, Height: atlasSize,
			Format: gl.RGBA,
		}))
	}
	textureLock.Unlock()

	gl.BlendFunc(gl.SrcAlpha, gl.OneMinusSrcAlpha)
}

var (
	textureIds    []int
	frameID       uint
	nearestBuffer *ChunkBuffer
	viewVector    vmath.Vector3
)

// Draw draws a single frame
func Draw(delta float64) {
	tickAnimatedTextures(delta)
	frameID++
sync:
	for {
		select {
		case f := <-syncChan:
			f()
		default:
			break sync
		}
	}

	width, height := platform.Size()
	// Only update the viewport if the window was resized
	if lastHeight != height || lastWidth != width {
		lastWidth = width
		lastHeight = height

		perspectiveMatrix.Identity()
		perspectiveMatrix.Perspective(
			(math.Pi/180)*90,
			float32(width)/float32(height),
			0.1,
			10000.0,
		)
		gl.Viewport(0, 0, width, height)
	}
	// Only update the texture ids if we have new
	// textures
	if len(textureIds) != len(glTextures) {
		textureIds = make([]int, len(glTextures))
		for i, tex := range glTextures {
			tex.Bind(gl.Texture2D)
			gl.ActiveTexture(i)
			textureIds[i] = i
		}
	}

	gl.Clear(gl.ColorBufferBit | gl.DepthBufferBit)

	chunkProgram.Use()

	cameraMatrix.Identity()
	// +1.62 for the players height.
	// TODO(Think) Change this?
	cameraMatrix.Translate(float32(Camera.X), float32(Camera.Y+1.62), float32(-Camera.Z))
	cameraMatrix.RotateY(float32(Camera.Yaw))
	cameraMatrix.RotateX(float32(Camera.Pitch))
	cameraMatrix.Scale(-1.0, 1.0, 1.0)

	shaderChunk.PerspectiveMatrix.Matrix4(perspectiveMatrix)
	shaderChunk.CameraMatrix.Matrix4(cameraMatrix)
	shaderChunk.Textures.IntV(textureIds...)

	nearestBuffer = buffers[position{
		X: int(Camera.X) >> 4,
		Y: int(Camera.Y) >> 4,
		Z: int(Camera.Z) >> 4,
	}]
	if nearestBuffer == nil {
		distance := math.MaxFloat64
		for _, chunk := range buffers {
			dx := Camera.X - float64((chunk.X<<4)+8)
			dy := Camera.Y - float64((chunk.Y<<4)+8)
			dz := Camera.Z - float64((chunk.Z<<4)+8)
			dist := dx*dx + dy*dy + dz*dz
			if nearestBuffer == nil || dist < distance {
				nearestBuffer = chunk
				distance = dist
			}
		}
	}

	viewVector.X = float32(math.Cos(float64(Camera.Yaw-math.Pi/2)) * -math.Cos(float64(Camera.Pitch)))
	viewVector.Z = -float32(math.Sin(float64(Camera.Yaw-math.Pi/2)) * -math.Cos(float64(Camera.Pitch)))
	viewVector.Y = -float32(math.Sin(float64(Camera.Pitch)))

	for _, dir := range direction.Values {
		validDirs[dir] = viewVector.Dot(dir.AsVector()) > -0.8
	}

	airVisitMap = make(map[position]struct{})
	renderOrder = renderOrder[:0]
	if nearestBuffer != nil {
		renderBuffer(nearestBuffer, nearestBuffer.position, direction.Invalid)
	}

	chunkProgramT.Use()
	shaderChunkT.PerspectiveMatrix.Matrix4(perspectiveMatrix)
	shaderChunkT.CameraMatrix.Matrix4(cameraMatrix)
	shaderChunkT.Textures.IntV(textureIds...)
	sort.Sort(renderOrder)

	gl.Enable(gl.Blend)
	for _, pos := range renderOrder {
		chunk := buffers[pos]
		if chunk != nil && chunk.countT > 0 {
			shaderChunkT.Offset.Float3(float32(chunk.X), float32(chunk.Y), float32(chunk.Z))

			chunk.arrayT.Bind()
			gl.DrawArrays(gl.Triangles, 0, chunk.countT)
		}
	}
	gl.Disable(gl.Blend)
}

var (
	airVisitMap = make(map[position]struct{})
	renderOrder transList
	validDirs   = make([]bool, len(direction.Values))
)

type renderRequest struct {
	chunk *ChunkBuffer
	pos   position
	from  direction.Type
	dist  int
}

func renderBuffer(chunk *ChunkBuffer, pos position, from direction.Type) {
	queue := []renderRequest{
		{chunk, pos, from, 1},
	}
itQueue:
	for len(queue) > 0 {
		req := queue[0]
		queue = queue[1:]
		chunk, pos, from = req.chunk, req.pos, req.from
		v := vmath.Vector3{
			float32((pos.X<<4)+8) - float32(Camera.X),
			float32((pos.Y<<4)+8) - float32(Camera.Y),
			float32((pos.Z<<4)+8) - float32(Camera.Z),
		}
		if (v.LengthSquared() > 40*40 && v.Dot(viewVector) < 0) || req.dist > 16 {
			continue itQueue
		}
		if chunk == nil {
			// Handle empty sections in columns
			if pos.Y >= 0 && pos.Y <= 15 {
				col := positionC{pos.X, pos.Z}
				if _, ok := airVisitMap[pos]; !ok && bufferColumns[col] > 0 {
					airVisitMap[pos] = struct{}{}
					renderOrder = append(renderOrder, pos)
					for _, dir := range direction.Values {
						if dir != from && validDirs[dir] {
							ox, oy, oz := dir.Offset()
							pos := position{pos.X + ox, pos.Y + oy, pos.Z + oz}
							queue = append(queue, renderRequest{buffers[pos], pos, dir.Opposite(), req.dist + 1})
						}
					}

				}
			}
			continue itQueue
		}
		if chunk.renderedOn == frameID {
			continue itQueue
		}
		chunk.renderedOn = frameID
		renderOrder = append(renderOrder, pos)

		if chunk.count > 0 {
			shaderChunk.Offset.Float3(float32(chunk.X), float32(chunk.Y), float32(chunk.Z))

			chunk.array.Bind()
			gl.DrawArrays(gl.Triangles, 0, chunk.count)
		}

		for _, dir := range direction.Values {
			if dir != from && (from == direction.Invalid || (chunk.IsVisible(from, dir) && validDirs[dir])) {
				ox, oy, oz := dir.Offset()
				pos := position{pos.X + ox, pos.Y + oy, pos.Z + oz}
				queue = append(queue, renderRequest{buffers[pos], pos, dir.Opposite(), req.dist + 1})
			}
		}
	}
}

// Sync runs the passed function on the next frame on the same goroutine
// as the renderer.
func Sync(f func()) {
	syncChan <- f
}
<commit_msg>render: remove the chunk sorting, it isn't needed<commit_after>// Copyright 2015 Matthew Collins
//
// 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 render

import (
	"math"
	"sort"
	"strings"

	"github.com/thinkofdeath/steven/platform"
	"github.com/thinkofdeath/steven/platform/gl"
	"github.com/thinkofdeath/steven/type/direction"
	"github.com/thinkofdeath/steven/type/vmath"
)

var (
	chunkProgram  gl.Program
	shaderChunk   *chunkShader
	chunkProgramT gl.Program
	shaderChunkT  *chunkShader

	lastWidth, lastHeight int = -1, -1
	perspectiveMatrix         = vmath.NewMatrix4()
	cameraMatrix              = vmath.NewMatrix4()

	syncChan = make(chan func(), 500)

	glTextures []gl.Texture
)

// Start starts the renderer
func Start(debug bool) {
	if debug {
		gl.Enable(gl.DebugOutput)
		gl.DebugLog()
	}

	gl.ClearColor(0.0, 1.0, 1.0, 1.0)
	gl.Enable(gl.DepthTest)
	gl.Enable(gl.CullFaceFlag)
	gl.CullFace(gl.Back)
	gl.FrontFace(gl.ClockWise)

	chunkProgram = CreateProgram(vertex, fragment)
	shaderChunk = &chunkShader{}
	InitStruct(shaderChunk, chunkProgram)

	chunkProgramT = CreateProgram(vertex, strings.Replace(fragment, "#version 150", "#version 150\n#define alpha", 1))
	shaderChunkT = &chunkShader{}
	InitStruct(shaderChunkT, chunkProgramT)

	textureLock.Lock()
	for _, tex := range textures {
		glTextures = append(glTextures, createTexture(glTexture{
			Data:  tex.Buffer,
			Width: atlasSize, Height: atlasSize,
			Format: gl.RGBA,
		}))
	}
	textureLock.Unlock()

	gl.BlendFunc(gl.SrcAlpha, gl.OneMinusSrcAlpha)
}

var (
	textureIds    []int
	frameID       uint
	nearestBuffer *ChunkBuffer
	viewVector    vmath.Vector3
)

// Draw draws a single frame
func Draw(delta float64) {
	tickAnimatedTextures(delta)
	frameID++
sync:
	for {
		select {
		case f := <-syncChan:
			f()
		default:
			break sync
		}
	}

	width, height := platform.Size()
	// Only update the viewport if the window was resized
	if lastHeight != height || lastWidth != width {
		lastWidth = width
		lastHeight = height

		perspectiveMatrix.Identity()
		perspectiveMatrix.Perspective(
			(math.Pi/180)*90,
			float32(width)/float32(height),
			0.1,
			10000.0,
		)
		gl.Viewport(0, 0, width, height)
	}
	// Only update the texture ids if we have new
	// textures
	if len(textureIds) != len(glTextures) {
		textureIds = make([]int, len(glTextures))
		for i, tex := range glTextures {
			tex.Bind(gl.Texture2D)
			gl.ActiveTexture(i)
			textureIds[i] = i
		}
	}

	gl.Clear(gl.ColorBufferBit | gl.DepthBufferBit)

	chunkProgram.Use()

	cameraMatrix.Identity()
	// +1.62 for the players height.
	// TODO(Think) Change this?
	cameraMatrix.Translate(float32(Camera.X), float32(Camera.Y+1.62), float32(-Camera.Z))
	cameraMatrix.RotateY(float32(Camera.Yaw))
	cameraMatrix.RotateX(float32(Camera.Pitch))
	cameraMatrix.Scale(-1.0, 1.0, 1.0)

	shaderChunk.PerspectiveMatrix.Matrix4(perspectiveMatrix)
	shaderChunk.CameraMatrix.Matrix4(cameraMatrix)
	shaderChunk.Textures.IntV(textureIds...)

	chunkPos := position{
		X: int(Camera.X) >> 4,
		Y: int(Camera.Y) >> 4,
		Z: int(Camera.Z) >> 4,
	}
	nearestBuffer = buffers[chunkPos]

	viewVector.X = float32(math.Cos(float64(Camera.Yaw-math.Pi/2)) * -math.Cos(float64(Camera.Pitch)))
	viewVector.Z = -float32(math.Sin(float64(Camera.Yaw-math.Pi/2)) * -math.Cos(float64(Camera.Pitch)))
	viewVector.Y = -float32(math.Sin(float64(Camera.Pitch)))

	for _, dir := range direction.Values {
		validDirs[dir] = viewVector.Dot(dir.AsVector()) > -0.8
	}

	airVisitMap = make(map[position]struct{})
	renderOrder = renderOrder[:0]
	renderBuffer(nearestBuffer, chunkPos, direction.Invalid)

	chunkProgramT.Use()
	shaderChunkT.PerspectiveMatrix.Matrix4(perspectiveMatrix)
	shaderChunkT.CameraMatrix.Matrix4(cameraMatrix)
	shaderChunkT.Textures.IntV(textureIds...)
	sort.Sort(renderOrder)

	gl.Enable(gl.Blend)
	for _, pos := range renderOrder {
		chunk := buffers[pos]
		if chunk != nil && chunk.countT > 0 {
			shaderChunkT.Offset.Float3(float32(chunk.X), float32(chunk.Y), float32(chunk.Z))

			chunk.arrayT.Bind()
			gl.DrawArrays(gl.Triangles, 0, chunk.countT)
		}
	}
	gl.Disable(gl.Blend)
}

var (
	airVisitMap = make(map[position]struct{})
	renderOrder transList
	validDirs   = make([]bool, len(direction.Values))
)

type renderRequest struct {
	chunk *ChunkBuffer
	pos   position
	from  direction.Type
	dist  int
}

func renderBuffer(chunk *ChunkBuffer, pos position, from direction.Type) {
	queue := []renderRequest{
		{chunk, pos, from, 1},
	}
itQueue:
	for len(queue) > 0 {
		req := queue[0]
		queue = queue[1:]
		chunk, pos, from = req.chunk, req.pos, req.from
		v := vmath.Vector3{
			float32((pos.X<<4)+8) - float32(Camera.X),
			float32((pos.Y<<4)+8) - float32(Camera.Y),
			float32((pos.Z<<4)+8) - float32(Camera.Z),
		}
		if (v.LengthSquared() > 40*40 && v.Dot(viewVector) < 0) || req.dist > 16 {
			continue itQueue
		}
		if chunk == nil {
			// Handle empty sections in columns
			if pos.Y >= 0 && pos.Y <= 15 {
				col := positionC{pos.X, pos.Z}
				if _, ok := airVisitMap[pos]; !ok && bufferColumns[col] > 0 {
					airVisitMap[pos] = struct{}{}
					renderOrder = append(renderOrder, pos)
					for _, dir := range direction.Values {
						if dir != from && (from == direction.Invalid || validDirs[dir]) {
							ox, oy, oz := dir.Offset()
							pos := position{pos.X + ox, pos.Y + oy, pos.Z + oz}
							queue = append(queue, renderRequest{buffers[pos], pos, dir.Opposite(), req.dist + 1})
						}
					}

				}
			}
			continue itQueue
		}
		if chunk.renderedOn == frameID {
			continue itQueue
		}
		chunk.renderedOn = frameID
		renderOrder = append(renderOrder, pos)

		if chunk.count > 0 {
			shaderChunk.Offset.Float3(float32(chunk.X), float32(chunk.Y), float32(chunk.Z))

			chunk.array.Bind()
			gl.DrawArrays(gl.Triangles, 0, chunk.count)
		}

		for _, dir := range direction.Values {
			if dir != from && (from == direction.Invalid || (chunk.IsVisible(from, dir) && validDirs[dir])) {
				ox, oy, oz := dir.Offset()
				pos := position{pos.X + ox, pos.Y + oy, pos.Z + oz}
				queue = append(queue, renderRequest{buffers[pos], pos, dir.Opposite(), req.dist + 1})
			}
		}
	}
}

// Sync runs the passed function on the next frame on the same goroutine
// as the renderer.
func Sync(f func()) {
	syncChan <- f
}
<|endoftext|>


<commit_before>// Copyright 2015 Matthew Collins
//
// 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 render

import (
	"math"
	"sort"
	"strings"

	"github.com/thinkofdeath/steven/render/gl"
	"github.com/thinkofdeath/steven/type/direction"
	"github.com/thinkofdeath/steven/type/vmath"
)

var (
	chunkProgram  gl.Program
	shaderChunk   *chunkShader
	chunkProgramT gl.Program
	shaderChunkT  *chunkShader
	lineProgram   gl.Program
	shaderLine    *lineShader

	lastWidth, lastHeight int = -1, -1
	perspectiveMatrix         = vmath.NewMatrix4()
	cameraMatrix              = vmath.NewMatrix4()

	syncChan = make(chan func(), 500)

	glTexture    gl.Texture
	textureDepth int
)

// Start starts the renderer
func Start() {
	gl.ClearColor(122.0/255.0, 165.0/255.0, 247.0/255.0, 1.0)
	gl.Enable(gl.DepthTest)
	gl.Enable(gl.CullFaceFlag)
	gl.CullFace(gl.Back)
	gl.FrontFace(gl.ClockWise)

	chunkProgram = CreateProgram(vertex, fragment)
	shaderChunk = &chunkShader{}
	InitStruct(shaderChunk, chunkProgram)

	chunkProgramT = CreateProgram(vertex, strings.Replace(fragment, "#version 150", "#version 150\n#define alpha", 1))
	shaderChunkT = &chunkShader{}
	InitStruct(shaderChunkT, chunkProgramT)

	textureLock.Lock()
	glTexture = gl.CreateTexture()
	glTexture.Bind(gl.Texture2DArray)
	textureDepth = len(textures)
	glTexture.Image3D(0, AtlasSize, AtlasSize, len(textures), gl.RGBA, gl.UnsignedByte, make([]byte, AtlasSize*AtlasSize*len(textures)*4))
	glTexture.Parameter(gl.TextureMagFilter, gl.Nearest)
	glTexture.Parameter(gl.TextureMinFilter, gl.Linear)
	glTexture.Parameter(gl.TextureWrapS, gl.ClampToEdge)
	glTexture.Parameter(gl.TextureWrapT, gl.ClampToEdge)
	for i, tex := range textures {
		glTexture.SubImage3D(0, 0, 0, i, AtlasSize, AtlasSize, 1, gl.RGBA, gl.UnsignedByte, tex.Buffer)
		textures[i] = nil
	}
	textureLock.Unlock()

	initUI()
	initLineDraw()

	gl.BlendFunc(gl.SrcAlpha, gl.OneMinusSrcAlpha)
}

var (
	textureIds    []int
	frameID       uint
	nearestBuffer *ChunkBuffer
	viewVector    vmath.Vector3
)

// Draw draws a single frame
func Draw(width, height int, delta float64) {
	tickAnimatedTextures(delta)
	frameID++
sync:
	for {
		select {
		case f := <-syncChan:
			f()
		default:
			break sync
		}
	}

	// Only update the viewport if the window was resized
	if lastHeight != height || lastWidth != width {
		lastWidth = width
		lastHeight = height

		perspectiveMatrix.Identity()
		perspectiveMatrix.Perspective(
			(math.Pi/180)*90,
			float32(width)/float32(height),
			0.1,
			10000.0,
		)
		gl.Viewport(0, 0, width, height)
	}

	// Textures
	textureLock.RLock()
	if textureDepth != len(textures) {
		glTexture.Bind(gl.Texture2DArray)
		data := make([]byte, AtlasSize*AtlasSize*len(textures)*4)
		glTexture.Get(0, gl.RGBA, gl.UnsignedByte, data)
		textureDepth = len(textures)
		glTexture.Image3D(0, AtlasSize, AtlasSize, len(textures), gl.RGBA, gl.UnsignedByte, data)
		for i := range textureDirty {
			textureDirty[i] = true
		}
	}
	for i, tex := range textures {
		if textureDirty[i] && tex != nil {
			textureDirty[i] = false
			glTexture.SubImage3D(0, 0, 0, i, AtlasSize, AtlasSize, 1, gl.RGBA, gl.UnsignedByte, tex.Buffer)
		}
	}
	textureLock.RUnlock()

	glTexture.Bind(gl.Texture2DArray)
	gl.ActiveTexture(0)

	gl.Clear(gl.ColorBufferBit | gl.DepthBufferBit)

	chunkProgram.Use()

	cameraMatrix.Identity()
	// +1.62 for the players height.
	// TODO(Think) Change this?
	cameraMatrix.Translate(float32(Camera.X), float32(Camera.Y), float32(-Camera.Z))
	cameraMatrix.RotateY(float32(Camera.Yaw))
	cameraMatrix.RotateX(float32(Camera.Pitch))
	cameraMatrix.Scale(-1.0, 1.0, 1.0)

	shaderChunk.PerspectiveMatrix.Matrix4(perspectiveMatrix)
	shaderChunk.CameraMatrix.Matrix4(cameraMatrix)
	shaderChunk.Texture.Int(0)

	chunkPos := position{
		X: int(Camera.X) >> 4,
		Y: int(Camera.Y) >> 4,
		Z: int(Camera.Z) >> 4,
	}
	nearestBuffer = buffers[chunkPos]

	viewVector.X = math.Cos(Camera.Yaw-math.Pi/2) * -math.Cos(Camera.Pitch)
	viewVector.Z = -math.Sin(Camera.Yaw-math.Pi/2) * -math.Cos(Camera.Pitch)
	viewVector.Y = -math.Sin(Camera.Pitch)

	for _, dir := range direction.Values {
		validDirs[dir] = viewVector.Dot(dir.AsVector()) > -0.8
	}

	renderOrder = renderOrder[:0]
	renderBuffer(nearestBuffer, chunkPos, direction.Invalid)

	drawLines()

	chunkProgramT.Use()
	shaderChunkT.PerspectiveMatrix.Matrix4(perspectiveMatrix)
	shaderChunkT.CameraMatrix.Matrix4(cameraMatrix)
	shaderChunkT.Texture.Int(0)

	gl.Enable(gl.Blend)
	for i := range renderOrder {
		chunk := renderOrder[len(renderOrder)-1-i]
		if chunk.countT > 0 && chunk.bufferT.IsValid() {
			shaderChunkT.Offset.Int3(chunk.X, chunk.Y, chunk.Z)

			chunk.arrayT.Bind()
			chunk.bufferT.Bind(gl.ArrayBuffer)
			offset := 0
			sort.Sort(chunk.transInfo)
			data := chunk.bufferT.Map(gl.WriteOnly, len(chunk.transData))
			for _, i := range chunk.transInfo {
				offset += copy(data[offset:], chunk.transData[i.Offset:i.Offset+i.Count])
			}
			chunk.bufferT.Unmap()
			gl.DrawArrays(gl.Triangles, 0, chunk.countT)
		}
	}
	gl.Disable(gl.Blend)

	drawUI()
}

var (
	renderOrder []*ChunkBuffer
	validDirs   = make([]bool, len(direction.Values))
)

type renderRequest struct {
	chunk *ChunkBuffer
	pos   position
	from  direction.Type
}

const (
	renderQueueSize = 5000
)

var rQueue renderQueue

func renderBuffer(chunk *ChunkBuffer, pos position, from direction.Type) {
	rQueue.Append(renderRequest{chunk, pos, from})
itQueue:
	for !rQueue.Empty() {
		req := rQueue.Take()
		chunk, pos, from = req.chunk, req.pos, req.from
		v := vmath.Vector3{
			float64((pos.X<<4)+8) - Camera.X,
			float64((pos.Y<<4)+8) - Camera.Y,
			float64((pos.Z<<4)+8) - Camera.Z,
		}
		if v.LengthSquared() > 40*40 && v.Dot(viewVector) < 0 {
			continue itQueue
		}
		if chunk == nil || chunk.renderedOn == frameID {
			continue itQueue
		}
		chunk.renderedOn = frameID
		renderOrder = append(renderOrder, chunk)

		if chunk.count > 0 && chunk.buffer.IsValid() {
			shaderChunk.Offset.Int3(chunk.X, chunk.Y, chunk.Z)

			chunk.array.Bind()
			gl.DrawArrays(gl.Triangles, 0, chunk.count)
		}

		for _, dir := range direction.Values {
			if dir != from && (from == direction.Invalid || (chunk.IsVisible(from, dir) && validDirs[dir])) {
				ox, oy, oz := dir.Offset()
				pos := position{pos.X + ox, pos.Y + oy, pos.Z + oz}
				rQueue.Append(renderRequest{chunk.neighborChunks[dir], pos, dir.Opposite()})
			}
		}
	}
}

// Sync runs the passed function on the next frame on the same goroutine
// as the renderer.
func Sync(f func()) {
	syncChan <- f
}
<commit_msg>render: optimize culling<commit_after>// Copyright 2015 Matthew Collins
//
// 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 render

import (
	"math"
	"sort"
	"strings"

	"github.com/thinkofdeath/steven/render/gl"
	"github.com/thinkofdeath/steven/type/direction"
	"github.com/thinkofdeath/steven/type/vmath"
)

var (
	chunkProgram  gl.Program
	shaderChunk   *chunkShader
	chunkProgramT gl.Program
	shaderChunkT  *chunkShader
	lineProgram   gl.Program
	shaderLine    *lineShader

	lastWidth, lastHeight int = -1, -1
	perspectiveMatrix         = vmath.NewMatrix4()
	cameraMatrix              = vmath.NewMatrix4()

	syncChan = make(chan func(), 500)

	glTexture    gl.Texture
	textureDepth int
)

// Start starts the renderer
func Start() {
	gl.ClearColor(122.0/255.0, 165.0/255.0, 247.0/255.0, 1.0)
	gl.Enable(gl.DepthTest)
	gl.Enable(gl.CullFaceFlag)
	gl.CullFace(gl.Back)
	gl.FrontFace(gl.ClockWise)

	chunkProgram = CreateProgram(vertex, fragment)
	shaderChunk = &chunkShader{}
	InitStruct(shaderChunk, chunkProgram)

	chunkProgramT = CreateProgram(vertex, strings.Replace(fragment, "#version 150", "#version 150\n#define alpha", 1))
	shaderChunkT = &chunkShader{}
	InitStruct(shaderChunkT, chunkProgramT)

	textureLock.Lock()
	glTexture = gl.CreateTexture()
	glTexture.Bind(gl.Texture2DArray)
	textureDepth = len(textures)
	glTexture.Image3D(0, AtlasSize, AtlasSize, len(textures), gl.RGBA, gl.UnsignedByte, make([]byte, AtlasSize*AtlasSize*len(textures)*4))
	glTexture.Parameter(gl.TextureMagFilter, gl.Nearest)
	glTexture.Parameter(gl.TextureMinFilter, gl.Linear)
	glTexture.Parameter(gl.TextureWrapS, gl.ClampToEdge)
	glTexture.Parameter(gl.TextureWrapT, gl.ClampToEdge)
	for i, tex := range textures {
		glTexture.SubImage3D(0, 0, 0, i, AtlasSize, AtlasSize, 1, gl.RGBA, gl.UnsignedByte, tex.Buffer)
		textures[i] = nil
	}
	textureLock.Unlock()

	initUI()
	initLineDraw()

	gl.BlendFunc(gl.SrcAlpha, gl.OneMinusSrcAlpha)
}

var (
	textureIds    []int
	frameID       uint
	nearestBuffer *ChunkBuffer
	viewVector    vmath.Vector3
)

// Draw draws a single frame
func Draw(width, height int, delta float64) {
	tickAnimatedTextures(delta)
	frameID++
sync:
	for {
		select {
		case f := <-syncChan:
			f()
		default:
			break sync
		}
	}

	// Only update the viewport if the window was resized
	if lastHeight != height || lastWidth != width {
		lastWidth = width
		lastHeight = height

		perspectiveMatrix.Identity()
		perspectiveMatrix.Perspective(
			(math.Pi/180)*90,
			float32(width)/float32(height),
			0.1,
			10000.0,
		)
		gl.Viewport(0, 0, width, height)
	}

	// Textures
	textureLock.RLock()
	if textureDepth != len(textures) {
		glTexture.Bind(gl.Texture2DArray)
		data := make([]byte, AtlasSize*AtlasSize*len(textures)*4)
		glTexture.Get(0, gl.RGBA, gl.UnsignedByte, data)
		textureDepth = len(textures)
		glTexture.Image3D(0, AtlasSize, AtlasSize, len(textures), gl.RGBA, gl.UnsignedByte, data)
		for i := range textureDirty {
			textureDirty[i] = true
		}
	}
	for i, tex := range textures {
		if textureDirty[i] && tex != nil {
			textureDirty[i] = false
			glTexture.SubImage3D(0, 0, 0, i, AtlasSize, AtlasSize, 1, gl.RGBA, gl.UnsignedByte, tex.Buffer)
		}
	}
	textureLock.RUnlock()

	glTexture.Bind(gl.Texture2DArray)
	gl.ActiveTexture(0)

	gl.Clear(gl.ColorBufferBit | gl.DepthBufferBit)

	chunkProgram.Use()

	cameraMatrix.Identity()
	// +1.62 for the players height.
	// TODO(Think) Change this?
	cameraMatrix.Translate(float32(Camera.X), float32(Camera.Y), float32(-Camera.Z))
	cameraMatrix.RotateY(float32(Camera.Yaw))
	cameraMatrix.RotateX(float32(Camera.Pitch))
	cameraMatrix.Scale(-1.0, 1.0, 1.0)

	shaderChunk.PerspectiveMatrix.Matrix4(perspectiveMatrix)
	shaderChunk.CameraMatrix.Matrix4(cameraMatrix)
	shaderChunk.Texture.Int(0)

	chunkPos := position{
		X: int(Camera.X) >> 4,
		Y: int(Camera.Y) >> 4,
		Z: int(Camera.Z) >> 4,
	}
	nearestBuffer = buffers[chunkPos]

	viewVector.X = math.Cos(Camera.Yaw-math.Pi/2) * -math.Cos(Camera.Pitch)
	viewVector.Z = -math.Sin(Camera.Yaw-math.Pi/2) * -math.Cos(Camera.Pitch)
	viewVector.Y = -math.Sin(Camera.Pitch)

	for _, dir := range direction.Values {
		validDirs[dir] = viewVector.Dot(dir.AsVector()) > -0.8
	}

	renderOrder = renderOrder[:0]
	renderBuffer(nearestBuffer, chunkPos, direction.Invalid)

	drawLines()

	chunkProgramT.Use()
	shaderChunkT.PerspectiveMatrix.Matrix4(perspectiveMatrix)
	shaderChunkT.CameraMatrix.Matrix4(cameraMatrix)
	shaderChunkT.Texture.Int(0)

	gl.Enable(gl.Blend)
	for i := range renderOrder {
		chunk := renderOrder[len(renderOrder)-1-i]
		if chunk.countT > 0 && chunk.bufferT.IsValid() {
			shaderChunkT.Offset.Int3(chunk.X, chunk.Y, chunk.Z)

			chunk.arrayT.Bind()
			chunk.bufferT.Bind(gl.ArrayBuffer)
			offset := 0
			sort.Sort(chunk.transInfo)
			data := chunk.bufferT.Map(gl.WriteOnly, len(chunk.transData))
			for _, i := range chunk.transInfo {
				offset += copy(data[offset:], chunk.transData[i.Offset:i.Offset+i.Count])
			}
			chunk.bufferT.Unmap()
			gl.DrawArrays(gl.Triangles, 0, chunk.countT)
		}
	}
	gl.Disable(gl.Blend)

	drawUI()
}

var (
	renderOrder []*ChunkBuffer
	validDirs   = make([]bool, len(direction.Values))
)

type renderRequest struct {
	chunk *ChunkBuffer
	pos   position
	from  direction.Type
}

const (
	renderQueueSize = 5000
)

var rQueue renderQueue

func renderBuffer(chunk *ChunkBuffer, pos position, from direction.Type) {
	rQueue.Append(renderRequest{chunk, pos, from})
itQueue:
	for !rQueue.Empty() {
		req := rQueue.Take()
		chunk, pos, from = req.chunk, req.pos, req.from
		v := vmath.Vector3{
			float64((pos.X<<4)+8) - Camera.X,
			float64((pos.Y<<4)+8) - Camera.Y,
			float64((pos.Z<<4)+8) - Camera.Z,
		}
		if v.LengthSquared() > 40*40 && v.Dot(viewVector) < 0 {
			continue itQueue
		}
		if chunk == nil || chunk.renderedOn == frameID {
			continue itQueue
		}
		chunk.renderedOn = frameID
		renderOrder = append(renderOrder, chunk)

		if chunk.count > 0 && chunk.buffer.IsValid() {
			shaderChunk.Offset.Int3(chunk.X, chunk.Y, chunk.Z)

			chunk.array.Bind()
			gl.DrawArrays(gl.Triangles, 0, chunk.count)
		}

		for _, dir := range direction.Values {
			c := chunk.neighborChunks[dir]
			if dir != from && c != nil && c.renderedOn != frameID &&
				(from == direction.Invalid || (chunk.IsVisible(from, dir) && validDirs[dir])) {
				ox, oy, oz := dir.Offset()
				pos := position{pos.X + ox, pos.Y + oy, pos.Z + oz}
				rQueue.Append(renderRequest{c, pos, dir.Opposite()})
			}
		}
	}
}

// Sync runs the passed function on the next frame on the same goroutine
// as the renderer.
func Sync(f func()) {
	syncChan <- f
}
<|endoftext|>


<commit_before>package main

func schemaInserts(printOnly bool, verbose bool) {
	idx := 0
	// map for storing the SQL statements by name
	queryMap := make(map[string]string)
	// slice storing the required statement order so foreign keys can
	// resolve successfully
	queries := make([]string, 10)

	queryMap["insertSystemGroupWheel"] = `
INSERT INTO inventory.organizational_teams (
    organizational_team_id,
    organizational_team_name,
    organizational_team_ldap_id,
    organizational_team_system )
VALUES (
    '00000000-0000-0000-0000-000000000000',
    'wheel',
    0,
    'yes'
);`
	queries[idx] = "insertSystemGroupWheel"
	idx++

	queryMap["insertSystemUserRoot"] = `
INSERT INTO inventory.users (
    user_id,
    user_uid,
    user_first_name,
    user_last_name,
    user_employee_number,
    user_mail_address,
    user_is_active,
    user_is_system,
    user_is_deleted,
    organizational_team_id )
VALUES (
    '00000000-0000-0000-0000-000000000000',
    'root',
    'Charlie',
    'Root',
    0,
    'monitoring@1und1.de',
    'yes',
    'yes',
    'no',
    '00000000-0000-0000-0000-000000000000'
);`
	queries[idx] = "insertSystemUserRoot"
	idx++

	queryMap["insertJobStatus"] = `
INSERT INTO soma.job_status (
    job_status )
VALUES
    ( 'queued' ),
    ( 'in_progress' ),
    ( 'processed' )
;`
	queries[idx] = "insertJobStatus"
	idx++

	queryMap["insertJobResults"] = `
INSERT INTO soma.job_results (
    job_result )
VALUES
    ( 'pending' ),
    ( 'success' ),
    ( 'failed' )
;`
	queries[idx] = "insertJobResults"
	idx++

	queryMap["insertJobTypes"] = `
INSERT INTO soma.job_types (
    job_type )
VALUES
    ( 'create_bucket' ),
    ( 'create_group' ),
    ( 'create_cluster' ),
    ( 'assign_node' ),
    ( 'add_group_to_group' ),
    ( 'add_cluster_to_group' ),
    ( 'add_node_to_group' ),
    ( 'add_node_to_cluster' ),
    ( 'add_system_property_to_repository' ),
    ( 'add_custom_property_to_repository' ),
    ( 'add_oncall_property_to_repository' ),
    ( 'add_service_property_to_repository' ),
    ( 'add_system_property_to_bucket' ),
    ( 'add_custom_property_to_bucket' ),
    ( 'add_oncall_property_to_bucket' ),
    ( 'add_service_property_to_bucket' ),
    ( 'add_system_property_to_group' ),
    ( 'add_custom_property_to_group' ),
    ( 'add_oncall_property_to_group' ),
    ( 'add_service_property_to_group' ),
    ( 'add_system_property_to_cluster' ),
    ( 'add_custom_property_to_cluster' ),
    ( 'add_oncall_property_to_cluster' ),
    ( 'add_service_property_to_cluster' ),
    ( 'add_system_property_to_node' ),
    ( 'add_custom_property_to_node' ),
    ( 'add_oncall_property_to_node' ),
    ( 'add_service_property_to_node' ),
    ( 'add_check_to_repository' ),
    ( 'add_check_to_bucket' ),
    ( 'add_check_to_group' ),
    ( 'add_check_to_cluster' ),
    ( 'add_check_to_node' )
;`
	queries[idx] = "insertJobTypes"
	idx++

	/*
			queryMap["insertSystemPropertyValidity"] = `
		INSERT INTO soma.system_property_validity (
		    system_property,
		    object_type,
		    inherited )
		VALUES
		    ( 'disable_ip4',        'repository', 'no'  ),
		    ( 'disable_ip4',        'bucket',     'no'  ),
		    ( 'disable_ip4',        'group',      'no'  ),
		    ( 'disable_ip4',        'cluster',    'no'  ),
		    ( 'disable_ip4',        'node',       'no'  ),
		    ( 'disable_ip4',        'repository', 'yes' ),
		    ( 'disable_ip4',        'bucket',     'yes' ),
		    ( 'disable_ip4',        'group',      'yes' ),
		    ( 'disable_ip4',        'cluster',    'yes' ),
		    ( 'disable_ip4',        'node',       'yes' ),
		    ( 'disable_ip6',        'repository', 'no'  ),
		    ( 'disable_ip6',        'bucket',     'no'  ),
		    ( 'disable_ip6',        'group',      'no'  ),
		    ( 'disable_ip6',        'cluster',    'no'  ),
		    ( 'disable_ip6',        'node',       'no'  ),
		    ( 'disable_ip6',        'repository', 'yes' ),
		    ( 'disable_ip6',        'bucket',     'yes' ),
		    ( 'disable_ip6',        'group',      'yes' ),
		    ( 'disable_ip6',        'cluster',    'yes' ),
		    ( 'disable_ip6',        'node',       'yes' ),
		    ( 'dns_zone',           'repository', 'yes' ),
		    ( 'dns_zone',           'bucket',     'yes' ),
		    ( 'dns_zone',           'group',      'yes' ),
		    ( 'dns_zone',           'cluster',    'yes' ),
		    ( 'dns_zone',           'node',       'yes' ),
		    ( 'dns_zone',           'repository', 'no'  ),
		    ( 'dns_zone',           'bucket',     'no'  ),
		    ( 'dns_zone',           'group',      'no'  ),
		    ( 'dns_zone',           'cluster',    'no'  ),
		    ( 'dns_zone',           'node',       'no'  ),
		    ( 'fqdn',               'group',      'yes' ),
		    ( 'fqdn',               'cluster',    'yes' ),
		    ( 'fqdn',               'node',       'yes' ),
		    ( 'fqdn',               'group',      'no'  ),
		    ( 'fqdn',               'cluster',    'no'  ),
		    ( 'fqdn',               'node',       'no'  ),
		    ( 'cluster_state',      'cluster',    'no'  ),
		    ( 'cluster_state',      'node',       'yes' ),
		    ( 'cluster_ha_address', 'cluster',    'no'  ),
		    ( 'cluster_ha_address', 'node',       'yes' ),
		    ( 'cluster_datacenter', 'cluster',    'no'  ),
		    ( 'cluster_datacenter', 'node',       'yes' ),
		    ( 'group_ha_address',   'group',      'no'  ),
		    ( 'group_ha_address',   'group',      'yes' ),
		    ( 'group_ha_address',   'cluster',    'yes' ),
		    ( 'group_ha_address',   'node',       'yes' ),
		    ( 'group_datacenter',   'group',      'no'  ),
		    ( 'group_datacenter',   'group',      'yes' ),
		    ( 'group_datacenter',   'cluster',    'yes' ),
		    ( 'group_datacenter',   'node',       'yes' ),
		    ( 'information_system', 'repository', 'yes' ),
		    ( 'information_system', 'bucket',     'yes' ),
		    ( 'information_system', 'group',      'yes' ),
		    ( 'information_system', 'cluster',    'yes' ),
		    ( 'information_system', 'node',       'yes' ),
		    ( 'information_system', 'repository', 'no'  ),
		    ( 'information_system', 'bucket',     'no'  ),
		    ( 'information_system', 'group',      'no'  ),
		    ( 'information_system', 'cluster',    'no'  ),
		    ( 'information_system', 'node',       'no'  ),
		    ( 'yp_asset',           'repository', 'yes' ),
		    ( 'yp_asset',           'bucket',     'yes' ),
		    ( 'yp_asset',           'group',      'yes' ),
		    ( 'yp_asset',           'cluster',    'yes' ),
		    ( 'yp_asset',           'node',       'yes' ),
		    ( 'yp_asset',           'repository', 'no'  ),
		    ( 'yp_asset',           'bucket',     'no'  ),
		    ( 'yp_asset',           'group',      'no'  ),
		    ( 'yp_asset',           'cluster',    'no'  ),
		    ( 'yp_asset',           'node',       'no'  ),
		    ( 'frozen',             'repository', 'yes' ),
		    ( 'frozen',             'bucket',     'yes' ),
		    ( 'frozen',             'group',      'yes' ),
		    ( 'frozen',             'cluster',    'yes' ),
		    ( 'frozen',             'node',       'yes' ),
		    ( 'frozen',             'repository', 'no'  ),
		    ( 'frozen',             'bucket',     'no'  ),
		    ( 'frozen',             'group',      'no'  ),
		    ( 'frozen',             'cluster',    'no'  ),
		    ( 'frozen',             'node',       'no'  ),
		    ( 'tag',                'repository', 'yes' ),
		    ( 'tag',                'bucket',     'yes' ),
		    ( 'tag',                'group',      'yes' ),
		    ( 'tag',                'cluster',    'yes' ),
		    ( 'tag',                'node',       'yes' ),
		    ( 'tag',                'repository', 'no'  ),
		    ( 'tag',                'bucket',     'no'  ),
		    ( 'tag',                'group',      'no'  ),
		    ( 'tag',                'cluster',    'no'  ),
		    ( 'tag',                'node',       'no'  ),
		    ( 'documentation',      'repository', 'yes' ),
		    ( 'documentation',      'bucket',     'yes' ),
		    ( 'documentation',      'group',      'yes' ),
		    ( 'documentation',      'cluster',    'yes' ),
		    ( 'documentation',      'node',       'yes' ),
		    ( 'documentation',      'repository', 'no'  ),
		    ( 'documentation',      'bucket',     'no'  ),
		    ( 'documentation',      'group',      'no'  ),
		    ( 'documentation',      'cluster',    'no'  ),
		    ( 'documentation',      'node',       'no'  ),
		    ( 'link',               'repository', 'yes' ),
		    ( 'link',               'bucket',     'yes' ),
		    ( 'link',               'group',      'yes' ),
		    ( 'link',               'cluster',    'yes' ),
		    ( 'link',               'node',       'yes' ),
		    ( 'link',               'repository', 'no'  ),
		    ( 'link',               'bucket',     'no'  ),
		    ( 'link',               'group',      'no'  ),
		    ( 'link',               'cluster',    'no'  ),
		    ( 'link',               'node',       'no'  ),
		    ( 'user_management',    'repository', 'yes' ),
		    ( 'user_management',    'bucket',     'yes' ),
		    ( 'user_management',    'group',      'yes' ),
		    ( 'user_management',    'cluster',    'yes' ),
		    ( 'user_management',    'node',       'yes' ),
		    ( 'user_management',    'repository', 'no'  ),
		    ( 'user_management',    'bucket',     'no'  ),
		    ( 'user_management',    'group',      'no'  ),
		    ( 'user_management',    'cluster',    'no'  ),
		    ( 'user_management',    'node',       'no'  ),
		    ( 'wiki',               'repository', 'yes' ),
		    ( 'wiki',               'bucket',     'yes' ),
		    ( 'wiki',               'group',      'yes' ),
		    ( 'wiki',               'cluster',    'yes' ),
		    ( 'wiki',               'node',       'yes' ),
		    ( 'wiki',               'repository', 'no'  ),
		    ( 'wiki',               'bucket',     'no'  ),
		    ( 'wiki',               'group',      'no'  ),
		    ( 'wiki',               'cluster',    'no'  ),
		    ( 'wiki',               'node',       'no'  )
		;`
			queries[idx] = "insertSystemPropertyValidity"
			idx++
	*/

	performDatabaseTask(printOnly, verbose, queries, queryMap)
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<commit_msg>Remove commented out validity section, moved to somaadm command<commit_after>package main

func schemaInserts(printOnly bool, verbose bool) {
	idx := 0
	// map for storing the SQL statements by name
	queryMap := make(map[string]string)
	// slice storing the required statement order so foreign keys can
	// resolve successfully
	queries := make([]string, 10)

	queryMap["insertSystemGroupWheel"] = `
INSERT INTO inventory.organizational_teams (
    organizational_team_id,
    organizational_team_name,
    organizational_team_ldap_id,
    organizational_team_system )
VALUES (
    '00000000-0000-0000-0000-000000000000',
    'wheel',
    0,
    'yes'
);`
	queries[idx] = "insertSystemGroupWheel"
	idx++

	queryMap["insertSystemUserRoot"] = `
INSERT INTO inventory.users (
    user_id,
    user_uid,
    user_first_name,
    user_last_name,
    user_employee_number,
    user_mail_address,
    user_is_active,
    user_is_system,
    user_is_deleted,
    organizational_team_id )
VALUES (
    '00000000-0000-0000-0000-000000000000',
    'root',
    'Charlie',
    'Root',
    0,
    'monitoring@1und1.de',
    'yes',
    'yes',
    'no',
    '00000000-0000-0000-0000-000000000000'
);`
	queries[idx] = "insertSystemUserRoot"
	idx++

	queryMap["insertJobStatus"] = `
INSERT INTO soma.job_status (
    job_status )
VALUES
    ( 'queued' ),
    ( 'in_progress' ),
    ( 'processed' )
;`
	queries[idx] = "insertJobStatus"
	idx++

	queryMap["insertJobResults"] = `
INSERT INTO soma.job_results (
    job_result )
VALUES
    ( 'pending' ),
    ( 'success' ),
    ( 'failed' )
;`
	queries[idx] = "insertJobResults"
	idx++

	queryMap["insertJobTypes"] = `
INSERT INTO soma.job_types (
    job_type )
VALUES
    ( 'create_bucket' ),
    ( 'create_group' ),
    ( 'create_cluster' ),
    ( 'assign_node' ),
    ( 'add_group_to_group' ),
    ( 'add_cluster_to_group' ),
    ( 'add_node_to_group' ),
    ( 'add_node_to_cluster' ),
    ( 'add_system_property_to_repository' ),
    ( 'add_custom_property_to_repository' ),
    ( 'add_oncall_property_to_repository' ),
    ( 'add_service_property_to_repository' ),
    ( 'add_system_property_to_bucket' ),
    ( 'add_custom_property_to_bucket' ),
    ( 'add_oncall_property_to_bucket' ),
    ( 'add_service_property_to_bucket' ),
    ( 'add_system_property_to_group' ),
    ( 'add_custom_property_to_group' ),
    ( 'add_oncall_property_to_group' ),
    ( 'add_service_property_to_group' ),
    ( 'add_system_property_to_cluster' ),
    ( 'add_custom_property_to_cluster' ),
    ( 'add_oncall_property_to_cluster' ),
    ( 'add_service_property_to_cluster' ),
    ( 'add_system_property_to_node' ),
    ( 'add_custom_property_to_node' ),
    ( 'add_oncall_property_to_node' ),
    ( 'add_service_property_to_node' ),
    ( 'add_check_to_repository' ),
    ( 'add_check_to_bucket' ),
    ( 'add_check_to_group' ),
    ( 'add_check_to_cluster' ),
    ( 'add_check_to_node' )
;`
	queries[idx] = "insertJobTypes"
	idx++

	performDatabaseTask(printOnly, verbose, queries, queryMap)
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<|endoftext|>


<commit_before>package comicplugin

import (
	"bytes"
	"fmt"
	"image/png"
	"log"
	"math"
	"math/rand"
	"strconv"
	"strings"
	"sync"

	"github.com/iopred/bruxism"
	"github.com/iopred/comicgen"
)

type comicPlugin struct {
	sync.Mutex

	bruxism.SimplePlugin
	log map[string][]bruxism.Message
}

func (p *comicPlugin) helpFunc(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, detailed bool) []string {
	help := bruxism.CommandHelp(service, "comic", "[1-10]", "Creates a comic from recent messages, or a number of messages if provided.")

	ticks := ""
	if service.Name() == bruxism.DiscordServiceName {
		ticks = "`"
	}
	if detailed {
		help = append(help, []string{
			bruxism.CommandHelp(service, "customcomic", "[id|name:] <text> | [id|name:] <text>", fmt.Sprintf("Creates a custom comic. Available names: %s%s%s", ticks, strings.Join(comicgen.CharacterNames, ", "), ticks))[0],
			bruxism.CommandHelp(service, "customcomicsimple", "[id:] <text> | [id:] <text>", "Creates a simple custom comic.")[0],
			"Examples:",
			bruxism.CommandHelp(service, "comic", "5", "Creates a comic from the last 5 messages")[0],
			bruxism.CommandHelp(service, "customcomic", "A | B | C", "Creates a comic with 3 lines.")[0],
			bruxism.CommandHelp(service, "customcomic", "0: Hi! | 1: Hello! | 0: Goodbye.", "Creates a comic with 3 lines, the second line spoken by a different character")[0],
			bruxism.CommandHelp(service, "customcomic", "tiki: Hi! | jordy: Hello! | tiki: Goodbye.", "Creates a comic with 3 lines, containing tiki and jordy.")[0],
			bruxism.CommandHelp(service, "customcomicsimple", "0: Foo | 1: Bar", "Creates a comic with 2 lines, both spoken by different characters.")[0],
		}...)
	}

	return help
}

func makeScriptFromMessages(service bruxism.Service, message bruxism.Message, messages []bruxism.Message) *comicgen.Script {
	speakers := make(map[string]int)
	avatars := make(map[int]string)

	script := []*comicgen.Message{}

	for _, message := range messages {
		speaker, ok := speakers[message.UserName()]
		if !ok {
			speaker = len(speakers)
			speakers[message.UserName()] = speaker
			avatars[speaker] = message.UserAvatar()
		}

		script = append(script, &comicgen.Message{
			Speaker: speaker,
			Text:    message.Message(),
			Author:  message.UserName(),
		})
	}
	return &comicgen.Script{
		Messages: script,
		Author:   fmt.Sprintf(service.UserName()),
		Avatars:  avatars,
		Type:     comicgen.ComicTypeChat,
	}
}

func (p *comicPlugin) makeComic(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, script *comicgen.Script) {
	comic := comicgen.NewComicGen("arial", service.Name() != bruxism.DiscordServiceName)
	image, err := comic.MakeComic(script)
	if err != nil {
		service.SendMessage(message.Channel(), fmt.Sprintf("Sorry %s, there was an error creating the comic. %s", message.UserName(), err))
	} else {
		go func() {
			b := &bytes.Buffer{}
			err = png.Encode(b, image)
			if err != nil {
				service.SendMessage(message.Channel(), fmt.Sprintf("Sorry %s, there was a problem creating your comic.", message.UserName()))
				return
			}

			url, err := bot.UploadToImgur(b, "comic.png")
			if err == nil {
				if service.Name() == bruxism.DiscordServiceName {
					service.SendMessage(message.Channel(), fmt.Sprintf("Here's your comic <@%s>: %s", message.UserID(), url))
				} else {
					service.SendMessage(message.Channel(), fmt.Sprintf("Here's your comic %s: %s", message.UserName(), url))
				}
			} else {
				// If imgur failed and we're on Discord, try file send instead!
				if service.Name() == bruxism.DiscordServiceName {
					service.SendFile(message.Channel(), "comic.png", b)
					return
				}

				log.Println("Error uploading comic: ", err)
				service.SendMessage(message.Channel(), fmt.Sprintf("Sorry %s, there was a problem uploading the comic to imgur.", message.UserName()))
			}
		}()
	}
}

func (p *comicPlugin) messageFunc(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) {
	if service.IsMe(message) {
		return
	}

	p.Lock()
	defer p.Unlock()

	log, ok := p.log[message.Channel()]
	if !ok {
		log = []bruxism.Message{}
	}

	if bruxism.MatchesCommand(service, "customcomic", message) || bruxism.MatchesCommand(service, "customcomicsimple", message) {
		ty := comicgen.ComicTypeChat
		if bruxism.MatchesCommand(service, "customcomicsimple", message) {
			ty = comicgen.ComicTypeSimple
		}

		service.Typing(message.Channel())

		str, _ := bruxism.ParseCommand(service, message)

		messages := []*comicgen.Message{}

		splits := strings.Split(str, "|")
		for _, line := range splits {
			line := strings.Trim(line, " ")

			text := ""
			speaker := 0
			author := ""
			if strings.Index(line, ":") != -1 {
				lineSplit := strings.Split(line, ":")

				author = strings.ToLower(strings.Trim(lineSplit[0], " "))

				var err error
				speaker, err = strconv.Atoi(author)
				if err != nil {
					speaker = -1
				}

				text = strings.Trim(lineSplit[1], " ")
			} else {
				text = line
			}

			messages = append(messages, &comicgen.Message{
				Speaker: speaker,
				Text:    text,
				Author:  author,
			})
		}

		if len(messages) == 0 {
			service.SendMessage(message.Channel(), fmt.Sprintf("Sorry %s, you didn't add any text.", message.UserName()))
			return
		}

		p.makeComic(bot, service, message, &comicgen.Script{
			Messages: messages,
			Author:   fmt.Sprintf(service.UserName()),
			Type:     ty,
		})
	} else if bruxism.MatchesCommand(service, "comic", message) {
		if len(log) == 0 {
			service.SendMessage(message.Channel(), fmt.Sprintf("Sorry %s, I don't have enough messages to make a comic yet.", message.UserName()))
			return
		}

		service.Typing(message.Channel())

		lines := 0
		linesString, parts := bruxism.ParseCommand(service, message)
		if len(parts) > 0 {
			lines, _ = strconv.Atoi(linesString)
		}

		if lines <= 0 {
			lines = 1 + int(math.Floor((math.Pow(2*rand.Float64()-1, 3)/2+0.5)*float64(5)))
		}

		if lines > len(log) {
			lines = len(log)
		}

		p.makeComic(bot, service, message, makeScriptFromMessages(service, message, log[len(log)-lines:]))
	} else {
		// Don't append commands.
		if strings.HasPrefix(strings.ToLower(strings.Trim(message.Message(), " ")), strings.ToLower(service.CommandPrefix())) {
			return
		}

		switch message.Type() {
		case bruxism.MessageTypeCreate:
			if len(log) < 10 {
				log = append(log, message)
			} else {
				log = append(log[1:], message)
			}
		case bruxism.MessageTypeUpdate:
			for i, m := range log {
				if m.MessageID() == message.MessageID() {
					log[i] = message
					break
				}
			}
		case bruxism.MessageTypeDelete:
			for i, m := range log {
				if m.MessageID() == message.MessageID() {
					log = append(log[:i], log[i+1:]...)
					break
				}
			}
		}
		p.log[message.Channel()] = log
	}
}

// New will create a new comic plugin.
func New() bruxism.Plugin {
	p := &comicPlugin{
		SimplePlugin: *bruxism.NewSimplePlugin("Comic"),
		log:          make(map[string][]bruxism.Message),
	}
	p.MessageFunc = p.messageFunc
	p.HelpFunc = p.helpFunc
	return p
}
<commit_msg>On discord, always try to upload comics first.<commit_after>package comicplugin

import (
	"bytes"
	"fmt"
	"image/png"
	"log"
	"math"
	"math/rand"
	"strconv"
	"strings"
	"sync"

	"github.com/iopred/bruxism"
	"github.com/iopred/comicgen"
	"github.com/iopred/discordgo"
)

type comicPlugin struct {
	sync.Mutex

	bruxism.SimplePlugin
	log map[string][]bruxism.Message
}

func (p *comicPlugin) helpFunc(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, detailed bool) []string {
	help := bruxism.CommandHelp(service, "comic", "[1-10]", "Creates a comic from recent messages, or a number of messages if provided.")

	ticks := ""
	if service.Name() == bruxism.DiscordServiceName {
		ticks = "`"
	}
	if detailed {
		help = append(help, []string{
			bruxism.CommandHelp(service, "customcomic", "[id|name:] <text> | [id|name:] <text>", fmt.Sprintf("Creates a custom comic. Available names: %s%s%s", ticks, strings.Join(comicgen.CharacterNames, ", "), ticks))[0],
			bruxism.CommandHelp(service, "customcomicsimple", "[id:] <text> | [id:] <text>", "Creates a simple custom comic.")[0],
			"Examples:",
			bruxism.CommandHelp(service, "comic", "5", "Creates a comic from the last 5 messages")[0],
			bruxism.CommandHelp(service, "customcomic", "A | B | C", "Creates a comic with 3 lines.")[0],
			bruxism.CommandHelp(service, "customcomic", "0: Hi! | 1: Hello! | 0: Goodbye.", "Creates a comic with 3 lines, the second line spoken by a different character")[0],
			bruxism.CommandHelp(service, "customcomic", "tiki: Hi! | jordy: Hello! | tiki: Goodbye.", "Creates a comic with 3 lines, containing tiki and jordy.")[0],
			bruxism.CommandHelp(service, "customcomicsimple", "0: Foo | 1: Bar", "Creates a comic with 2 lines, both spoken by different characters.")[0],
		}...)
	}

	return help
}

func makeScriptFromMessages(service bruxism.Service, message bruxism.Message, messages []bruxism.Message) *comicgen.Script {
	speakers := make(map[string]int)
	avatars := make(map[int]string)

	script := []*comicgen.Message{}

	for _, message := range messages {
		speaker, ok := speakers[message.UserName()]
		if !ok {
			speaker = len(speakers)
			speakers[message.UserName()] = speaker
			avatars[speaker] = message.UserAvatar()
		}

		script = append(script, &comicgen.Message{
			Speaker: speaker,
			Text:    message.Message(),
			Author:  message.UserName(),
		})
	}
	return &comicgen.Script{
		Messages: script,
		Author:   fmt.Sprintf(service.UserName()),
		Avatars:  avatars,
		Type:     comicgen.ComicTypeChat,
	}
}

func (p *comicPlugin) makeComic(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, script *comicgen.Script) {
	comic := comicgen.NewComicGen("arial", service.Name() != bruxism.DiscordServiceName)
	image, err := comic.MakeComic(script)
	if err != nil {
		service.SendMessage(message.Channel(), fmt.Sprintf("Sorry %s, there was an error creating the comic. %s", message.UserName(), err))
	} else {
		go func() {
			b := &bytes.Buffer{}
			err = png.Encode(b, image)
			if err != nil {
				service.SendMessage(message.Channel(), fmt.Sprintf("Sorry %s, there was a problem creating your comic.", message.UserName()))
				return
			}

			if service.Name() == bruxism.DiscordServiceName {
				discord := service.(*bruxism.Discord)
				p, err := discord.Session.State.UserChannelPermissions(message.UserID(), message.Channel())
				if err == nil && p&discordgo.PermissionAttachFiles != 0 {
					service.SendFile(message.Channel(), "comic.png", b)
					return
				}
			}

			url, err := bot.UploadToImgur(b, "comic.png")
			if err != nil {
				service.SendMessage(message.Channel(), fmt.Sprintf("Sorry %s, there was a problem uploading the comic to imgur.", message.UserName()))
				log.Println("Error uploading comic: ", err)
				return
			}

			if service.Name() == bruxism.DiscordServiceName {
				service.SendMessage(message.Channel(), fmt.Sprintf("Here's your comic <@%s>: %s", message.UserID(), url))
			} else {
				service.SendMessage(message.Channel(), fmt.Sprintf("Here's your comic %s: %s", message.UserName(), url))
			}

			// If imgur failed and we're on Discord, try file send instead!
			if service.Name() == bruxism.DiscordServiceName {
				service.SendFile(message.Channel(), "comic.png", b)
				return
			}
		}()
	}
}

func (p *comicPlugin) messageFunc(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) {
	if service.IsMe(message) {
		return
	}

	p.Lock()
	defer p.Unlock()

	log, ok := p.log[message.Channel()]
	if !ok {
		log = []bruxism.Message{}
	}

	if bruxism.MatchesCommand(service, "customcomic", message) || bruxism.MatchesCommand(service, "customcomicsimple", message) {
		ty := comicgen.ComicTypeChat
		if bruxism.MatchesCommand(service, "customcomicsimple", message) {
			ty = comicgen.ComicTypeSimple
		}

		service.Typing(message.Channel())

		str, _ := bruxism.ParseCommand(service, message)

		messages := []*comicgen.Message{}

		splits := strings.Split(str, "|")
		for _, line := range splits {
			line := strings.Trim(line, " ")

			text := ""
			speaker := 0
			author := ""
			if strings.Index(line, ":") != -1 {
				lineSplit := strings.Split(line, ":")

				author = strings.ToLower(strings.Trim(lineSplit[0], " "))

				var err error
				speaker, err = strconv.Atoi(author)
				if err != nil {
					speaker = -1
				}

				text = strings.Trim(lineSplit[1], " ")
			} else {
				text = line
			}

			messages = append(messages, &comicgen.Message{
				Speaker: speaker,
				Text:    text,
				Author:  author,
			})
		}

		if len(messages) == 0 {
			service.SendMessage(message.Channel(), fmt.Sprintf("Sorry %s, you didn't add any text.", message.UserName()))
			return
		}

		p.makeComic(bot, service, message, &comicgen.Script{
			Messages: messages,
			Author:   fmt.Sprintf(service.UserName()),
			Type:     ty,
		})
	} else if bruxism.MatchesCommand(service, "comic", message) {
		if len(log) == 0 {
			service.SendMessage(message.Channel(), fmt.Sprintf("Sorry %s, I don't have enough messages to make a comic yet.", message.UserName()))
			return
		}

		service.Typing(message.Channel())

		lines := 0
		linesString, parts := bruxism.ParseCommand(service, message)
		if len(parts) > 0 {
			lines, _ = strconv.Atoi(linesString)
		}

		if lines <= 0 {
			lines = 1 + int(math.Floor((math.Pow(2*rand.Float64()-1, 3)/2+0.5)*float64(5)))
		}

		if lines > len(log) {
			lines = len(log)
		}

		p.makeComic(bot, service, message, makeScriptFromMessages(service, message, log[len(log)-lines:]))
	} else {
		// Don't append commands.
		if strings.HasPrefix(strings.ToLower(strings.Trim(message.Message(), " ")), strings.ToLower(service.CommandPrefix())) {
			return
		}

		switch message.Type() {
		case bruxism.MessageTypeCreate:
			if len(log) < 10 {
				log = append(log, message)
			} else {
				log = append(log[1:], message)
			}
		case bruxism.MessageTypeUpdate:
			for i, m := range log {
				if m.MessageID() == message.MessageID() {
					log[i] = message
					break
				}
			}
		case bruxism.MessageTypeDelete:
			for i, m := range log {
				if m.MessageID() == message.MessageID() {
					log = append(log[:i], log[i+1:]...)
					break
				}
			}
		}
		p.log[message.Channel()] = log
	}
}

// New will create a new comic plugin.
func New() bruxism.Plugin {
	p := &comicPlugin{
		SimplePlugin: *bruxism.NewSimplePlugin("Comic"),
		log:          make(map[string][]bruxism.Message),
	}
	p.MessageFunc = p.messageFunc
	p.HelpFunc = p.helpFunc
	return p
}
<|endoftext|>


<commit_before>package main

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import (
	"crypto/tls"
	"database/sql"
	"fmt"
	"net/http"
	"net/http/httputil"
)

// Routes returns the routes, and a catchall route for when no route matches.
func Routes(d ServerData) ([]Route, http.Handler, error) {
	rd, err := routeData(d)
	if err != nil {
		return nil, nil, err
	}
	return []Route{
		{1.2, http.MethodGet, "cdns/{cdn}/configs/monitoring", wrapHeaders(wrapAuth(monitoringHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, MonitoringPrivLevel))},
		{1.2, http.MethodGet, "cdns/{cdn}/configs/monitoring.json", wrapHeaders(wrapAuth(monitoringHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, MonitoringPrivLevel))},
		{1.2, http.MethodGet, "servers", wrapHeaders(wrapAuthWithData(serversHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, ServersPrivLevel))},
		{1.2, http.MethodGet, "servers.json", wrapHeaders(wrapAuthWithData(serversHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, ServersPrivLevel))},
		{1.2, http.MethodGet, "cdns", wrapHeaders(wrapAuthWithData(cdnsHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, ServersPrivLevel))},
		{1.2, http.MethodGet, "cdns.json", wrapHeaders(wrapAuthWithData(cdnsHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, ServersPrivLevel))},
	}, rootHandler(d), nil
}

type RouteData struct {
	PrivLevelStmt *sql.Stmt
}

func routeData(d ServerData) (RouteData, error) {
	rd := RouteData{}
	err := error(nil)

	if rd.PrivLevelStmt, err = preparePrivLevelStmt(d.DB); err != nil {
		return rd, fmt.Errorf("Error preparing db priv level query: ", err)
	}

	return rd, nil
}

// getRootHandler returns the / handler for the service, which reverse-proxies the old Perl Traffic Ops
func rootHandler(d ServerData) http.Handler {
	// debug
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	rp := httputil.NewSingleHostReverseProxy(d.TOURL)
	rp.Transport = tr

	loggingProxyHandler := wrapAccessLog(d.TOSecret, rp)
	return loggingProxyHandler
}
<commit_msg>fixed cdnprivlevel reference<commit_after>package main

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import (
	"crypto/tls"
	"database/sql"
	"fmt"
	"net/http"
	"net/http/httputil"
)

// Routes returns the routes, and a catchall route for when no route matches.
func Routes(d ServerData) ([]Route, http.Handler, error) {
	rd, err := routeData(d)
	if err != nil {
		return nil, nil, err
	}
	return []Route{
		{1.2, http.MethodGet, "cdns/{cdn}/configs/monitoring", wrapHeaders(wrapAuth(monitoringHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, MonitoringPrivLevel))},
		{1.2, http.MethodGet, "cdns/{cdn}/configs/monitoring.json", wrapHeaders(wrapAuth(monitoringHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, MonitoringPrivLevel))},
		{1.2, http.MethodGet, "servers", wrapHeaders(wrapAuthWithData(serversHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, ServersPrivLevel))},
		{1.2, http.MethodGet, "servers.json", wrapHeaders(wrapAuthWithData(serversHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, ServersPrivLevel))},
		{1.2, http.MethodGet, "cdns", wrapHeaders(wrapAuthWithData(cdnsHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, CdnsPrivLevel))},
		{1.2, http.MethodGet, "cdns.json", wrapHeaders(wrapAuthWithData(cdnsHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, CdnsPrivLevel))},
	}, rootHandler(d), nil
}

type RouteData struct {
	PrivLevelStmt *sql.Stmt
}

func routeData(d ServerData) (RouteData, error) {
	rd := RouteData{}
	err := error(nil)

	if rd.PrivLevelStmt, err = preparePrivLevelStmt(d.DB); err != nil {
		return rd, fmt.Errorf("Error preparing db priv level query: ", err)
	}

	return rd, nil
}

// getRootHandler returns the / handler for the service, which reverse-proxies the old Perl Traffic Ops
func rootHandler(d ServerData) http.Handler {
	// debug
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	rp := httputil.NewSingleHostReverseProxy(d.TOURL)
	rp.Transport = tr

	loggingProxyHandler := wrapAccessLog(d.TOSecret, rp)
	return loggingProxyHandler
}
<|endoftext|>