<commit_before>package main

import (
	g "github.com/oatot/oatot/generated"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"log"
	"reflect"
	"unsafe"
)

// #include "../game/g_local.h"
// #include "../game/bg_public.h"
import "C"

const maxActiveBetsN = 5
const betsPerPageN = 15
const maxCStrLen = 1024

var (
	grpcAddr = "127.0.0.1:13283"
	client   g.OatotClient
)

// Dirty type paramertic function.
// Just in order to prevent code repetition.
func CopyGoSliceToCArray(
	cArray interface{},
	goSlice interface{},
	funcCopyElement interface{},
	cArraySize int,
) C.int {
	// Get values of interfaces first.
	vCArray := reflect.ValueOf(cArray)
	vGoSlice := reflect.ValueOf(goSlice)
	// Create slice which has the same type like elements of `cArray`.
	dataSlice := reflect.MakeSlice(
		reflect.SliceOf(reflect.Indirect(vCArray).Type()),
		cArraySize,
		cArraySize,
	)
	// Create and set pointer to this slice.
	dataSlicePtr := reflect.New(dataSlice.Type())
	dataSlicePtr.Elem().Set(dataSlice)
	// Len of Go slice.
	goSliceLen := vGoSlice.Len()
	if goSliceLen > cArraySize {
		log.Fatalf("Go-client: attempt to copy slice to C array of smaller size.")
	}
	// sliceHeader trick to convert the C array into a Go slice.
	sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(dataSlicePtr.Pointer()))
	sliceHeader.Cap = cArraySize
	sliceHeader.Len = cArraySize
	sliceHeader.Data = vCArray.Pointer()
	for i := 0; i < goSliceLen; i++ {
		// Hacky reflect usage: call function to copy individual element.
		reflect.ValueOf(funcCopyElement).Call(
			[]reflect.Value{vGoSlice.Index(i), dataSlice.Index(i).Addr()},
		)
	}
	return C.int(goSliceLen)
}

func CharToC(goChar byte, cChar *C.char) {
	*cChar = C.char(goChar)
}

func StringToC(str string, cStr *C.char) {
	str += "\x00"
	CopyGoSliceToCArray(cStr, str, CharToC, maxCStrLen)
}

func CBetFromGo(in *g.Bet, out *C.bet_t) {
	//TODO: implement time storage in backend.
	//timeStr := (*in.OpenTime).String()
	out.amount = C.int(*in.Amount)
	out.bet_ID = C.int(*in.BetId)
	StringToC(*in.Horse, &(out.horse[0]))
	StringToC(*in.Currency, &(out.currency[0]))
	//StringToC(timeStr, &(out.open_time[0]))
}

func CFullbetFromGo(in *g.Bet, out *C.fullbet_t) {
	//TODO: implement time storage in backend.
	//timeStr := (*in.CloseTime).String()
	CBetFromGo(in, &out.open_bet)
	out.prize = C.int(*in.Prize)
	StringToC(*in.Winner, &(out.winner[0]))
	//StringToC(timeStr, &(out.close_time[0]))
}

func CCurrencySummaryFromGo(in *g.CurrencySummary) C.currencySummary_t {
	return C.currencySummary_t{
		total_bet:   C.int(*in.TotalBet),
		total_prize: C.int(*in.TotalPrize),
		total_lost:  C.int(*in.TotalLost),
		bets_won:    C.int(*in.BetsWon),
		bets_lost:   C.int(*in.BetsLost),
	}
}

//export GInitializeClient
func GInitializeClient() {
	if client == nil {
		// Set up a connection to the server.
		conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure())
		if err != nil {
			log.Fatalf("Did not connect: %v", err)
		}
		client = g.NewOatotClient(conn)
	}
}

//export GOaChangeGameStage
func GOaChangeGameStage(newStage C.int) {
	newStageVal := uint64(newStage)
	_, err := client.OaChangeGameStage(
		context.Background(),
		&g.OaChangeGameStageRequest{
			NewStage: &newStageVal,
		},
	)
	if err != nil {
		log.Fatalf("OaChangeGameStage: %v", err)
	}
}

//export GOaCloseBets
func GOaCloseBets(winner *C.char) {
	winnerStr := C.GoString(winner)
	_, err := client.OaCloseBets(
		context.Background(),
		&g.OaCloseBetsRequest{
			Winner: &winnerStr,
		},
	)
	if err != nil {
		log.Fatalf("OaCloseBets: %v", err)
	}
}

//export GOaCloseBetsByIncident
func GOaCloseBetsByIncident() {
	_, err := client.OaCloseBetsByIncident(
		context.Background(),
		&g.OaCloseBetsByIncidentRequest{},
	)
	if err != nil {
		log.Fatalf("OaCloseBetsByIncident: %v", err)
	}
}

//export GOaIsNew
func GOaIsNew(clGuid *C.char) C.int {
	clGuidStr := C.GoString(clGuid)
	res, err := client.OaIsNew(
		context.Background(),
		&g.OaIsNewRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
		},
	)
	if err != nil {
		log.Fatalf("OaIsNew: %v", err)
	}
	if *res.Result {
		return C.int(1)
	} else {
		return C.int(0)
	}
}

//export GOaRegister
func GOaRegister(clGuid *C.char) {
	clGuidStr := C.GoString(clGuid)
	_, err := client.OaRegister(
		context.Background(),
		&g.OaRegisterRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
		},
	)
	if err != nil {
		log.Fatalf("OaRegister: %v", err)
	}
}

//export GOaTransferMoney
func GOaTransferMoney(clGuid *C.char, amount C.int, currency *C.char) {
	clGuidStr := C.GoString(clGuid)
	currencyStr := C.GoString(currency)
	amountVal := uint64(amount)
	_, err := client.OaTransferMoney(
		context.Background(),
		&g.OaTransferMoneyRequest{
			Amount:   &amountVal,
			OaAuth:   &g.OaAuth{ClGuid: &clGuidStr},
			Currency: &currencyStr,
		},
	)
	if err != nil {
		log.Fatalf("OaTransferMoney: %v", err)
	}
}

//export GOaActiveBetsSums
func GOaActiveBetsSums(horse *C.char) C.betSum_t {
	horseStr := C.GoString(horse)
	res, err := client.OaActiveBetsSums(
		context.Background(),
		&g.OaActiveBetsSumsRequest{
			Horse: &horseStr,
		},
	)
	if err != nil {
		log.Fatalf("OaActiveBetsSums: %v", err)
	}
	return C.betSum_t{C.int(*res.OacAmount), C.int(*res.BtcAmount)}
}

//export GOaMyBalance
func GOaMyBalance(clGuid *C.char, currency *C.char) C.balance_t {
	clGuidStr := C.GoString(clGuid)
	currencyStr := C.GoString(currency)
	res, err := client.OaMyBalance(
		context.Background(),
		&g.OaMyBalanceRequest{
			OaAuth:   &g.OaAuth{ClGuid: &clGuidStr},
			Currency: &currencyStr,
		},
	)
	if err != nil {
		log.Fatalf("OaMyBalance: %v", err)
	}
	return C.balance_t{C.int(*res.FreeMoney), C.int(*res.MoneyOnBets)}
}

//export GOaMyBet
func GOaMyBet(clGuid *C.char, bet C.bet_t) {
	clGuidStr := C.GoString(clGuid)
	horseStr := C.GoString(&(bet.horse[0]))
	currencyStr := C.GoString(&(bet.currency[0]))
	amountVal := uint64(bet.amount)
	betN := &g.Bet{
		Horse:    &horseStr,
		Currency: &currencyStr,
		Amount:   &amountVal,
	}
	_, err := client.OaMyBet(
		context.Background(),
		&g.OaMyBetRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
			Bet:    betN,
		},
	)
	if err != nil {
		log.Fatalf("OaMyBet: %v", err)
	}
}

//export GOaDiscardBet
func GOaDiscardBet(clGuid *C.char, betId C.int) {
	clGuidStr := C.GoString(clGuid)
	betIdVal := uint64(betId)
	_, err := client.OaDiscardBet(
		context.Background(),
		&g.OaDiscardBetRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
			BetId:  &betIdVal,
		},
	)
	if err != nil {
		log.Fatalf("OaDiscardBet: %v", err)
	}
}

//export GOaMyActiveBets
func GOaMyActiveBets(clGuid *C.char, activeBets *C.bet_t) C.int {
	clGuidStr := C.GoString(clGuid)
	res, err := client.OaMyActiveBets(
		context.Background(),
		&g.OaMyActiveBetsRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
		},
	)
	if err != nil {
		log.Fatalf("OaMyActiveBets: %v", err)
	}
	return CopyGoSliceToCArray(activeBets, res.Bets, CBetFromGo, maxActiveBetsN)
}

//export GOaMyPastBets
func GOaMyPastBets(clGuid *C.char, page *C.char, nextPage *C.char, pastBets *C.fullbet_t) C.int {
	clGuidStr := C.GoString(clGuid)
	pageStr := C.GoString(page)
	res, err := client.OaMyPastBets(
		context.Background(),
		&g.OaMyPastBetsRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
			Page:   &pageStr,
		},
	)
	if err != nil {
		log.Fatalf("OaMyPastBets: %v", err)
	}
	StringToC(*res.NextPage, nextPage)
	return CopyGoSliceToCArray(pastBets, res.Bets, CFullbetFromGo, betsPerPageN)
}

//export GOaMyBetsSummary
func GOaMyBetsSummary(clGuid *C.char) C.betsSummary_t {
	clGuidStr := C.GoString(clGuid)
	res, err := client.OaMyBetsSummary(
		context.Background(),
		&g.OaMyBetsSummaryRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
		},
	)
	if err != nil {
		log.Fatalf("OaMyBetsSummary: %v", err)
	}
	oacSummary := CCurrencySummaryFromGo(res.OacSummary)
	btcSummary := CCurrencySummaryFromGo(res.BtcSummary)
	return C.betsSummary_t{oacSummary, btcSummary}
}

func main() {}
<commit_msg>Fix bug in CopyGoSliceToCArray()<commit_after>package main

import (
	g "github.com/oatot/oatot/generated"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"log"
	"reflect"
	"unsafe"
)

// #include "../game/g_local.h"
// #include "../game/bg_public.h"
import "C"

const maxActiveBetsN = 5
const betsPerPageN = 15
const maxCStrLen = 1024

var (
	grpcAddr = "127.0.0.1:13283"
	client   g.OatotClient
)

// Dirty type paramertic function.
// Just in order to prevent code repetition.
func CopyGoSliceToCArray(
	cArray interface{},
	goSlice interface{},
	dataSlicePtr interface{},
	funcCopyElement interface{},
	cArraySize int,
) C.int {
	// Get values of interfaces first.
	vCArray := reflect.ValueOf(cArray)
	vGoSlice := reflect.ValueOf(goSlice)
	vDataSlicePtr := reflect.ValueOf(dataSlicePtr)
	vDataSlice := reflect.Indirect(vDataSlicePtr)
	// Len of Go slice.
	goSliceLen := vGoSlice.Len()
	if goSliceLen > cArraySize {
		log.Fatalf("Go-client: attempt to copy slice to C array of smaller size.")
	}
	// sliceHeader trick to convert the C array into a Go slice.
	sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(vDataSlicePtr.Pointer()))
	sliceHeader.Cap = cArraySize
	sliceHeader.Len = cArraySize
	sliceHeader.Data = vCArray.Pointer()
	for i := 0; i < goSliceLen; i++ {
		// Hacky reflect usage: call function to copy individual element.
		reflect.ValueOf(funcCopyElement).Call(
			[]reflect.Value{vGoSlice.Index(i), vDataSlice.Index(i).Addr()},
		)
	}
	return C.int(goSliceLen)
}

func CharToC(goChar byte, cChar *C.char) {
	*cChar = C.char(goChar)
}

func StringToC(str string, cStr *C.char) {
	str += "\x00"
	var slice []C.char
	CopyGoSliceToCArray(cStr, str, &slice, CharToC, maxCStrLen)
}

func CBetFromGo(in *g.Bet, out *C.bet_t) {
	//TODO: implement time storage in backend.
	//timeStr := (*in.OpenTime).String()
	out.amount = C.int(*in.Amount)
	out.bet_ID = C.int(*in.BetId)
	StringToC(*in.Horse, &(out.horse[0]))
	StringToC(*in.Currency, &(out.currency[0]))
	//StringToC(timeStr, &(out.open_time[0]))
}

func CFullbetFromGo(in *g.Bet, out *C.fullbet_t) {
	//TODO: implement time storage in backend.
	//timeStr := (*in.CloseTime).String()
	CBetFromGo(in, &out.open_bet)
	out.prize = C.int(*in.Prize)
	StringToC(*in.Winner, &(out.winner[0]))
	//StringToC(timeStr, &(out.close_time[0]))
}

func CCurrencySummaryFromGo(in *g.CurrencySummary) C.currencySummary_t {
	return C.currencySummary_t{
		total_bet:   C.int(*in.TotalBet),
		total_prize: C.int(*in.TotalPrize),
		total_lost:  C.int(*in.TotalLost),
		bets_won:    C.int(*in.BetsWon),
		bets_lost:   C.int(*in.BetsLost),
	}
}

//export GInitializeClient
func GInitializeClient() {
	if client == nil {
		// Set up a connection to the server.
		conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure())
		if err != nil {
			log.Fatalf("Did not connect: %v", err)
		}
		client = g.NewOatotClient(conn)
	}
}

//export GOaChangeGameStage
func GOaChangeGameStage(newStage C.int) {
	newStageVal := uint64(newStage)
	_, err := client.OaChangeGameStage(
		context.Background(),
		&g.OaChangeGameStageRequest{
			NewStage: &newStageVal,
		},
	)
	if err != nil {
		log.Fatalf("OaChangeGameStage: %v", err)
	}
}

//export GOaCloseBets
func GOaCloseBets(winner *C.char) {
	winnerStr := C.GoString(winner)
	_, err := client.OaCloseBets(
		context.Background(),
		&g.OaCloseBetsRequest{
			Winner: &winnerStr,
		},
	)
	if err != nil {
		log.Fatalf("OaCloseBets: %v", err)
	}
}

//export GOaCloseBetsByIncident
func GOaCloseBetsByIncident() {
	_, err := client.OaCloseBetsByIncident(
		context.Background(),
		&g.OaCloseBetsByIncidentRequest{},
	)
	if err != nil {
		log.Fatalf("OaCloseBetsByIncident: %v", err)
	}
}

//export GOaIsNew
func GOaIsNew(clGuid *C.char) C.int {
	clGuidStr := C.GoString(clGuid)
	res, err := client.OaIsNew(
		context.Background(),
		&g.OaIsNewRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
		},
	)
	if err != nil {
		log.Fatalf("OaIsNew: %v", err)
	}
	if *res.Result {
		return C.int(1)
	} else {
		return C.int(0)
	}
}

//export GOaRegister
func GOaRegister(clGuid *C.char) {
	clGuidStr := C.GoString(clGuid)
	_, err := client.OaRegister(
		context.Background(),
		&g.OaRegisterRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
		},
	)
	if err != nil {
		log.Fatalf("OaRegister: %v", err)
	}
}

//export GOaTransferMoney
func GOaTransferMoney(clGuid *C.char, amount C.int, currency *C.char) {
	clGuidStr := C.GoString(clGuid)
	currencyStr := C.GoString(currency)
	amountVal := uint64(amount)
	_, err := client.OaTransferMoney(
		context.Background(),
		&g.OaTransferMoneyRequest{
			Amount:   &amountVal,
			OaAuth:   &g.OaAuth{ClGuid: &clGuidStr},
			Currency: &currencyStr,
		},
	)
	if err != nil {
		log.Fatalf("OaTransferMoney: %v", err)
	}
}

//export GOaActiveBetsSums
func GOaActiveBetsSums(horse *C.char) C.betSum_t {
	horseStr := C.GoString(horse)
	res, err := client.OaActiveBetsSums(
		context.Background(),
		&g.OaActiveBetsSumsRequest{
			Horse: &horseStr,
		},
	)
	if err != nil {
		log.Fatalf("OaActiveBetsSums: %v", err)
	}
	return C.betSum_t{C.int(*res.OacAmount), C.int(*res.BtcAmount)}
}

//export GOaMyBalance
func GOaMyBalance(clGuid *C.char, currency *C.char) C.balance_t {
	clGuidStr := C.GoString(clGuid)
	currencyStr := C.GoString(currency)
	res, err := client.OaMyBalance(
		context.Background(),
		&g.OaMyBalanceRequest{
			OaAuth:   &g.OaAuth{ClGuid: &clGuidStr},
			Currency: &currencyStr,
		},
	)
	if err != nil {
		log.Fatalf("OaMyBalance: %v", err)
	}
	return C.balance_t{C.int(*res.FreeMoney), C.int(*res.MoneyOnBets)}
}

//export GOaMyBet
func GOaMyBet(clGuid *C.char, bet C.bet_t) {
	clGuidStr := C.GoString(clGuid)
	horseStr := C.GoString(&(bet.horse[0]))
	currencyStr := C.GoString(&(bet.currency[0]))
	amountVal := uint64(bet.amount)
	betN := &g.Bet{
		Horse:    &horseStr,
		Currency: &currencyStr,
		Amount:   &amountVal,
	}
	_, err := client.OaMyBet(
		context.Background(),
		&g.OaMyBetRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
			Bet:    betN,
		},
	)
	if err != nil {
		log.Fatalf("OaMyBet: %v", err)
	}
}

//export GOaDiscardBet
func GOaDiscardBet(clGuid *C.char, betId C.int) {
	clGuidStr := C.GoString(clGuid)
	betIdVal := uint64(betId)
	_, err := client.OaDiscardBet(
		context.Background(),
		&g.OaDiscardBetRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
			BetId:  &betIdVal,
		},
	)
	if err != nil {
		log.Fatalf("OaDiscardBet: %v", err)
	}
}

//export GOaMyActiveBets
func GOaMyActiveBets(clGuid *C.char, activeBets *C.bet_t) C.int {
	clGuidStr := C.GoString(clGuid)
	res, err := client.OaMyActiveBets(
		context.Background(),
		&g.OaMyActiveBetsRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
		},
	)
	if err != nil {
		log.Fatalf("OaMyActiveBets: %v", err)
	}
	var bets []C.bet_t
	return CopyGoSliceToCArray(activeBets, res.Bets, &bets, CBetFromGo, maxActiveBetsN)
}

//export GOaMyPastBets
func GOaMyPastBets(clGuid *C.char, page *C.char, nextPage *C.char, pastBets *C.fullbet_t) C.int {
	clGuidStr := C.GoString(clGuid)
	pageStr := C.GoString(page)
	res, err := client.OaMyPastBets(
		context.Background(),
		&g.OaMyPastBetsRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
			Page:   &pageStr,
		},
	)
	if err != nil {
		log.Fatalf("OaMyPastBets: %v", err)
	}
	StringToC(*res.NextPage, nextPage)
	var bets []C.fullbet_t
	return CopyGoSliceToCArray(pastBets, res.Bets, &bets, CFullbetFromGo, betsPerPageN)
}

//export GOaMyBetsSummary
func GOaMyBetsSummary(clGuid *C.char) C.betsSummary_t {
	clGuidStr := C.GoString(clGuid)
	res, err := client.OaMyBetsSummary(
		context.Background(),
		&g.OaMyBetsSummaryRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
		},
	)
	if err != nil {
		log.Fatalf("OaMyBetsSummary: %v", err)
	}
	oacSummary := CCurrencySummaryFromGo(res.OacSummary)
	btcSummary := CCurrencySummaryFromGo(res.BtcSummary)
	return C.betsSummary_t{oacSummary, btcSummary}
}

func main() {}
<|endoftext|>


<commit_before>package db

import (
	"fmt"
	"path"

	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/errors"
	"github.com/syndtr/goleveldb/leveldb/opt"
	"github.com/syndtr/goleveldb/leveldb/util"

	. "github.com/tendermint/tmlibs/common"
)

func init() {
	dbCreator := func(name string, dir string) (DB, error) {
		return NewGoLevelDB(name, dir)
	}
	registerDBCreator(LevelDBBackendStr, dbCreator, false)
	registerDBCreator(GoLevelDBBackendStr, dbCreator, false)
}

type GoLevelDB struct {
	db *leveldb.DB
}

func NewGoLevelDB(name string, dir string) (*GoLevelDB, error) {
	dbPath := path.Join(dir, name+".db")
	db, err := leveldb.OpenFile(dbPath, nil)
	if err != nil {
		return nil, err
	}
	database := &GoLevelDB{db: db}
	return database, nil
}

func (db *GoLevelDB) Get(key []byte) []byte {
	res, err := db.db.Get(key, nil)
	if err != nil {
		if err == errors.ErrNotFound {
			return nil
		} else {
			PanicCrisis(err)
		}
	}
	return res
}

func (db *GoLevelDB) Set(key []byte, value []byte) {
	err := db.db.Put(key, value, nil)
	if err != nil {
		PanicCrisis(err)
	}
}

func (db *GoLevelDB) SetSync(key []byte, value []byte) {
	err := db.db.Put(key, value, &opt.WriteOptions{Sync: true})
	if err != nil {
		PanicCrisis(err)
	}
}

func (db *GoLevelDB) Delete(key []byte) {
	err := db.db.Delete(key, nil)
	if err != nil {
		PanicCrisis(err)
	}
}

func (db *GoLevelDB) DeleteSync(key []byte) {
	err := db.db.Delete(key, &opt.WriteOptions{Sync: true})
	if err != nil {
		PanicCrisis(err)
	}
}

func (db *GoLevelDB) DB() *leveldb.DB {
	return db.db
}

func (db *GoLevelDB) Close() {
	db.db.Close()
}

func (db *GoLevelDB) Print() {
	str, _ := db.db.GetProperty("leveldb.stats")
	fmt.Printf("%v\n", str)

	iter := db.db.NewIterator(nil, nil)
	for iter.Next() {
		key := iter.Key()
		value := iter.Value()
		fmt.Printf("[%X]:\t[%X]\n", key, value)
	}
}

func (db *GoLevelDB) Stats() map[string]string {
	keys := []string{
		"leveldb.num-files-at-level{n}",
		"leveldb.stats",
		"leveldb.sstables",
		"leveldb.blockpool",
		"leveldb.cachedblock",
		"leveldb.openedtables",
		"leveldb.alivesnaps",
		"leveldb.aliveiters",
	}

	stats := make(map[string]string)
	for _, key := range keys {
		str, err := db.db.GetProperty(key)
		if err == nil {
			stats[key] = str
		}
	}
	return stats
}

func (db *GoLevelDB) Iterator() Iterator {
	return db.db.NewIterator(nil, nil)
}

func (db *GoLevelDB) IteratorPrefix(prefix []byte) Iterator {
	return db.db.NewIterator(util.BytesPrefix(prefix), nil)
}

func (db *GoLevelDB) NewBatch() Batch {
	batch := new(leveldb.Batch)
	return &goLevelDBBatch{db, batch}
}

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

type goLevelDBBatch struct {
	db    *GoLevelDB
	batch *leveldb.Batch
}

func (mBatch *goLevelDBBatch) Set(key, value []byte) {
	mBatch.batch.Put(key, value)
}

func (mBatch *goLevelDBBatch) Delete(key []byte) {
	mBatch.batch.Delete(key)
}

func (mBatch *goLevelDBBatch) Write() {
	err := mBatch.db.db.Write(mBatch.batch, nil)
	if err != nil {
		PanicCrisis(err)
	}
}
<commit_msg>Implement Key/Value on Iterator for GoLevelDB<commit_after>package db

import (
	"fmt"
	"path"

	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/errors"
	"github.com/syndtr/goleveldb/leveldb/iterator"
	"github.com/syndtr/goleveldb/leveldb/opt"
	"github.com/syndtr/goleveldb/leveldb/util"

	. "github.com/tendermint/tmlibs/common"
)

func init() {
	dbCreator := func(name string, dir string) (DB, error) {
		return NewGoLevelDB(name, dir)
	}
	registerDBCreator(LevelDBBackendStr, dbCreator, false)
	registerDBCreator(GoLevelDBBackendStr, dbCreator, false)
}

type GoLevelDB struct {
	db *leveldb.DB
}

func NewGoLevelDB(name string, dir string) (*GoLevelDB, error) {
	dbPath := path.Join(dir, name+".db")
	db, err := leveldb.OpenFile(dbPath, nil)
	if err != nil {
		return nil, err
	}
	database := &GoLevelDB{db: db}
	return database, nil
}

func (db *GoLevelDB) Get(key []byte) []byte {
	res, err := db.db.Get(key, nil)
	if err != nil {
		if err == errors.ErrNotFound {
			return nil
		} else {
			PanicCrisis(err)
		}
	}
	return res
}

func (db *GoLevelDB) Set(key []byte, value []byte) {
	err := db.db.Put(key, value, nil)
	if err != nil {
		PanicCrisis(err)
	}
}

func (db *GoLevelDB) SetSync(key []byte, value []byte) {
	err := db.db.Put(key, value, &opt.WriteOptions{Sync: true})
	if err != nil {
		PanicCrisis(err)
	}
}

func (db *GoLevelDB) Delete(key []byte) {
	err := db.db.Delete(key, nil)
	if err != nil {
		PanicCrisis(err)
	}
}

func (db *GoLevelDB) DeleteSync(key []byte) {
	err := db.db.Delete(key, &opt.WriteOptions{Sync: true})
	if err != nil {
		PanicCrisis(err)
	}
}

func (db *GoLevelDB) DB() *leveldb.DB {
	return db.db
}

func (db *GoLevelDB) Close() {
	db.db.Close()
}

func (db *GoLevelDB) Print() {
	str, _ := db.db.GetProperty("leveldb.stats")
	fmt.Printf("%v\n", str)

	iter := db.db.NewIterator(nil, nil)
	for iter.Next() {
		key := iter.Key()
		value := iter.Value()
		fmt.Printf("[%X]:\t[%X]\n", key, value)
	}
}

func (db *GoLevelDB) Stats() map[string]string {
	keys := []string{
		"leveldb.num-files-at-level{n}",
		"leveldb.stats",
		"leveldb.sstables",
		"leveldb.blockpool",
		"leveldb.cachedblock",
		"leveldb.openedtables",
		"leveldb.alivesnaps",
		"leveldb.aliveiters",
	}

	stats := make(map[string]string)
	for _, key := range keys {
		str, err := db.db.GetProperty(key)
		if err == nil {
			stats[key] = str
		}
	}
	return stats
}

type goLevelDBIterator struct {
	iterator.Iterator
}

func (it *goLevelDBIterator) Key() []byte {
	key := it.Key()
	k := make([]byte, len(key))
	copy(k, key)

	return k
}

func (it *goLevelDBIterator) Value() []byte {
	val := it.Value()
	v := make([]byte, len(val))
	copy(v, val)

	return v
}

func (db *GoLevelDB) Iterator() Iterator {
	return &goLevelDBIterator{db.db.NewIterator(nil, nil)}
}

func (db *GoLevelDB) IteratorPrefix(prefix []byte) Iterator {
	return &goLevelDBIterator{db.db.NewIterator(util.BytesPrefix(prefix), nil)}
}

func (db *GoLevelDB) NewBatch() Batch {
	batch := new(leveldb.Batch)
	return &goLevelDBBatch{db, batch}
}

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

type goLevelDBBatch struct {
	db    *GoLevelDB
	batch *leveldb.Batch
}

func (mBatch *goLevelDBBatch) Set(key, value []byte) {
	mBatch.batch.Put(key, value)
}

func (mBatch *goLevelDBBatch) Delete(key []byte) {
	mBatch.batch.Delete(key)
}

func (mBatch *goLevelDBBatch) Write() {
	err := mBatch.db.db.Write(mBatch.batch, nil)
	if err != nil {
		PanicCrisis(err)
	}
}
<|endoftext|>


<commit_before>package frame

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/mattn/go-colorable"

	"github.com/zetamatta/nyagos/history"
	"github.com/zetamatta/nyagos/readline"
	"github.com/zetamatta/nyagos/shell"
)

type CmdStreamConsole struct {
	shell.CmdSeeker
	DoPrompt func() (int, error)
	History  *history.Container
	Editor   *readline.Editor
	HistPath string
}

var console io.Writer
var prevOptionGoColorable bool = false

var isEscapeSequenceAvailable = false

func GetConsole() io.Writer {
	if isEscapeSequenceAvailable {
		dos.EnableStdoutVirtualTerminalProcessing()
		console = os.Stdout
	} else if console == nil || prevOptionGoColorable != OptionGoColorable {
		if dos.IsEscapeSequenceAvailable() {
			console = os.Stdout
			dos.EnableStdoutVirtualTerminalProcessing()
			isEscapeSequenceAvailable = true
		} else if OptionGoColorable {
			console = colorable.NewColorableStdout()
		} else {
			console = os.Stdout
			if OptionEnableVirtualTerminalProcessing {
				enableVirtualTerminalProcessing()
			}
		}
		prevOptionGoColorable = OptionGoColorable
	}
	return console
}

func NewCmdStreamConsole(doPrompt func() (int, error)) *CmdStreamConsole {
	history1 := &history.Container{}
	this := &CmdStreamConsole{
		History: history1,
		Editor: &readline.Editor{
			History: history1,
			Prompt:  doPrompt,
			Writer:  GetConsole()},
		HistPath: filepath.Join(AppDataDir(), "nyagos.history"),
		CmdSeeker: shell.CmdSeeker{
			PlainHistory: []string{},
			Pointer:      -1,
		},
	}
	history1.Load(this.HistPath)
	history1.Save(this.HistPath)
	return this
}

func (this *CmdStreamConsole) ReadLine(ctx context.Context) (context.Context, string, error) {
	if this.Pointer >= 0 {
		if this.Pointer < len(this.PlainHistory) {
			this.Pointer++
			return ctx, this.PlainHistory[this.Pointer-1], nil
		}
		this.Pointer = -1
	}
	var line string
	var err error
	for {
		line, err = this.Editor.ReadLine(ctx)
		if err != nil {
			return ctx, line, err
		}
		var isReplaced bool
		line, isReplaced, err = this.History.Replace(line)
		if err != nil {
			return ctx, line, err
		}
		if isReplaced {
			fmt.Fprintln(os.Stdout, line)
		}
		if line != "" {
			break
		}
	}
	row := history.NewHistoryLine(line)
	this.History.PushLine(row)
	fd, err := os.OpenFile(this.HistPath, os.O_APPEND|os.O_CREATE, 0600)
	if err == nil {
		fmt.Fprintln(fd, row.String())
		fd.Close()
	} else {
		fmt.Fprintln(os.Stderr, err.Error())
	}
	this.PlainHistory = append(this.PlainHistory, line)
	return ctx, line, err
}
<commit_msg>Fix compile error (forgot fixing conclict completely)<commit_after>package frame

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/mattn/go-colorable"

	"github.com/zetamatta/nyagos/history"
	"github.com/zetamatta/nyagos/readline"
	"github.com/zetamatta/nyagos/shell"
)

type CmdStreamConsole struct {
	shell.CmdSeeker
	DoPrompt func() (int, error)
	History  *history.Container
	Editor   *readline.Editor
	HistPath string
}

var console io.Writer
var prevOptionGoColorable bool = false

var isEscapeSequenceAvailableFlag = false

func GetConsole() io.Writer {
	if isEscapeSequenceAvailableFlag {
		enableVirtualTerminalProcessing()
		console = os.Stdout
	} else if console == nil || prevOptionGoColorable != OptionGoColorable {
		if isEscapeSequenceAvailable() {
			console = os.Stdout
			enableVirtualTerminalProcessing()
			isEscapeSequenceAvailableFlag = true
		} else if OptionGoColorable {
			console = colorable.NewColorableStdout()
		} else {
			console = os.Stdout
			if OptionEnableVirtualTerminalProcessing {
				enableVirtualTerminalProcessing()
			}
		}
		prevOptionGoColorable = OptionGoColorable
	}
	return console
}

func NewCmdStreamConsole(doPrompt func() (int, error)) *CmdStreamConsole {
	history1 := &history.Container{}
	this := &CmdStreamConsole{
		History: history1,
		Editor: &readline.Editor{
			History: history1,
			Prompt:  doPrompt,
			Writer:  GetConsole()},
		HistPath: filepath.Join(AppDataDir(), "nyagos.history"),
		CmdSeeker: shell.CmdSeeker{
			PlainHistory: []string{},
			Pointer:      -1,
		},
	}
	history1.Load(this.HistPath)
	history1.Save(this.HistPath)
	return this
}

func (this *CmdStreamConsole) ReadLine(ctx context.Context) (context.Context, string, error) {
	if this.Pointer >= 0 {
		if this.Pointer < len(this.PlainHistory) {
			this.Pointer++
			return ctx, this.PlainHistory[this.Pointer-1], nil
		}
		this.Pointer = -1
	}
	var line string
	var err error
	for {
		line, err = this.Editor.ReadLine(ctx)
		if err != nil {
			return ctx, line, err
		}
		var isReplaced bool
		line, isReplaced, err = this.History.Replace(line)
		if err != nil {
			return ctx, line, err
		}
		if isReplaced {
			fmt.Fprintln(os.Stdout, line)
		}
		if line != "" {
			break
		}
	}
	row := history.NewHistoryLine(line)
	this.History.PushLine(row)
	fd, err := os.OpenFile(this.HistPath, os.O_APPEND|os.O_CREATE, 0600)
	if err == nil {
		fmt.Fprintln(fd, row.String())
		fd.Close()
	} else {
		fmt.Fprintln(os.Stderr, err.Error())
	}
	this.PlainHistory = append(this.PlainHistory, line)
	return ctx, line, err
}
<|endoftext|>


<commit_before>package srnd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"
)

type varnishHandler struct {
	cache *VarnishCache
}

func (self *varnishHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	_, file := filepath.Split(path)

	isjson := strings.HasSuffix(path, "/json") || strings.HasSuffix(path, "/json/")

	if strings.HasPrefix(path, "/t/") {
		// thread handler
		parts := strings.Split(path, "/")
		if len(parts) == 1 {
			goto notfound
		}
		hash := parts[1]
		msg, err := self.cache.database.GetMessageIDByHash(hash)
		if err == nil {
			template.genThread(self.cache.attachments, msg, self.cache.prefix, self.cache.name, w, self.cache.database, isjson)
			return
		} else {
			goto notfound
		}
	}
	if strings.Trim(path, "/") == "overboard" {
		// generate ukko aka overboard
		template.genUkko(self.cache.prefix, self.cache.name, w, self.cache.database, isjson)
		return
	}

	if strings.HasPrefix(path, "/b/") {
		// board handler
		parts := strings.Split(path[3:], "/")
		page := 0
		group := parts[0]
		if len(parts) > 1 && parts[1] != "" && parts[1] != "json" {
			var err error
			page, err = strconv.Atoi(parts[1])
			if err != nil {
				goto notfound
			}
		}
		hasgroup := self.cache.database.HasNewsgroup(group)
		if !hasgroup {
			goto notfound
		}
		pages := self.cache.database.GetGroupPageCount(group)
		if page >= int(pages) {
			goto notfound
		}
		template.genBoardPage(self.cache.attachments, self.cache.prefix, self.cache.name, group, page, w, self.cache.database, isjson)
		return
	}

	if strings.HasPrefix(path, "/o/") {
		page := 0
		parts := strings.Split(path, "/")
		if len(parts) > 1 {
			if parts[1] != "json" {
				var err error
				page, err = strconv.Atoi(parts[1])
				if err != nil {
					goto notfound
				}

			}
		}
		template.genUkkoPaginated(self.cache.prefix, self.cache.name, w, self.cache.database, page, isjson)
		return
	}

	if len(file) == 0 || file == "index.html" {
		template.genFrontPage(10, self.cache.prefix, self.cache.name, w, ioutil.Discard, self.cache.database)
		return
	}

	if file == "index.json" {
		// TODO: index.json
		goto notfound
	}
	if strings.HasPrefix(file, "history.html") {
		template.genGraphs(self.cache.prefix, w, self.cache.database)
		return
	}
	if strings.HasPrefix(file, "boards.html") {
		template.genFrontPage(10, self.cache.prefix, self.cache.name, ioutil.Discard, w, self.cache.database)
		return
	}

	if strings.HasPrefix(file, "boards.json") {
		b := self.cache.database.GetAllNewsgroups()
		json.NewEncoder(w).Encode(b)
		return
	}

	if strings.HasPrefix(file, "ukko.html") {
		template.genUkko(self.cache.prefix, self.cache.name, w, self.cache.database, false)
		return
	}
	if strings.HasPrefix(file, "ukko.json") {
		template.genUkko(self.cache.prefix, self.cache.name, w, self.cache.database, true)
		return
	}

	if strings.HasPrefix(file, "ukko-") {
		page := getUkkoPage(file)
		template.genUkkoPaginated(self.cache.prefix, self.cache.name, w, self.cache.database, page, isjson)
		return
	}
	if strings.HasPrefix(file, "thread-") {
		hash := getThreadHash(file)
		if len(hash) == 0 {
			goto notfound
		}
		msg, err := self.cache.database.GetMessageIDByHash(hash)
		if err != nil {
			goto notfound
		}
		template.genThread(self.cache.attachments, msg, self.cache.prefix, self.cache.name, w, self.cache.database, isjson)
		return
	}
	if strings.HasPrefix(file, "catalog-") {
		group := getGroupForCatalog(file)
		if len(group) == 0 {
			goto notfound
		}
		hasgroup := self.cache.database.HasNewsgroup(group)
		if !hasgroup {
			goto notfound
		}
		template.genCatalog(self.cache.prefix, self.cache.name, group, w, self.cache.database)
		return
	} else {
		group, page := getGroupAndPage(file)
		if len(group) == 0 || page < 0 {
			goto notfound
		}
		hasgroup := self.cache.database.HasNewsgroup(group)
		if !hasgroup {
			goto notfound
		}
		pages := self.cache.database.GetGroupPageCount(group)
		if page >= int(pages) {
			goto notfound
		}
		template.genBoardPage(self.cache.attachments, self.cache.prefix, self.cache.name, group, page, w, self.cache.database, isjson)
		return
	}

notfound:
	template.renderNotFound(w, r, self.cache.prefix, self.cache.name)
}

type VarnishCache struct {
	database Database
	store    ArticleStore

	webroot_dir string
	name        string

	regen_threads int
	attachments   bool

	prefix          string
	varnish_url     string
	client          *http.Client
	regenThreadChan chan ArticleEntry
	regenGroupChan  chan groupRegenRequest
}

func (self *VarnishCache) invalidate(r string) {
	u, _ := url.Parse(r)
	resp, err := self.client.Do(&http.Request{
		Method: "PURGE",
		URL:    u,
	})
	if err == nil {
		resp.Body.Close()
	} else {
		log.Println("varnish cache error", err)
	}
}

func (self *VarnishCache) DeleteBoardMarkup(group string) {
	n, _ := self.database.GetPagesPerBoard(group)
	for n > 0 {
		go self.invalidate(fmt.Sprintf("%s%s%s-%d.html", self.varnish_url, self.prefix, group, n))
		go self.invalidate(fmt.Sprintf("%s%sb/%s/%d/", self.varnish_url, self.prefix, group, n))
		n--
	}
	self.invalidate(fmt.Sprintf("%s%sb/%s/", self.varnish_url, self.prefix, group))
}

// try to delete root post's page
func (self *VarnishCache) DeleteThreadMarkup(root_post_id string) {
	self.invalidate(fmt.Sprintf("%s%sthread-%s.html", self.varnish_url, self.prefix, HashMessageID(root_post_id)))
	self.invalidate(fmt.Sprintf("%s%st/%s/", self.varnish_url, self.prefix, HashMessageID(root_post_id)))
}

// regen every newsgroup
func (self *VarnishCache) RegenAll() {
	// we will do this as it's used by rengen on start for frontend
	groups := self.database.GetAllNewsgroups()
	for _, group := range groups {
		self.database.GetGroupThreads(group, self.regenThreadChan)
	}
}

func (self *VarnishCache) RegenFrontPage() {
	self.invalidate(fmt.Sprintf("%s%s", self.varnish_url, self.prefix))
}

func (self *VarnishCache) invalidateUkko() {
	// TODO: invalidate paginated ukko
	self.invalidate(fmt.Sprintf("%s%sukko.html", self.varnish_url, self.prefix))
	self.invalidate(fmt.Sprintf("%s%soverboard/", self.varnish_url, self.prefix))
}

func (self *VarnishCache) pollRegen() {
	for {
		select {
		// consume regen requests
		case ev := <-self.regenGroupChan:
			{
				self.invalidate(fmt.Sprintf("%s%s%s-%d.html", self.varnish_url, self.prefix, ev.group, ev.page))
				self.invalidate(fmt.Sprintf("%s%sb/%s/%d/", self.varnish_url, self.prefix, ev.group, ev.page))
				if ev.page == 0 {
					self.invalidate(fmt.Sprintf("%s%sb/%s/", self.varnish_url, self.prefix, ev.group))
				}
			}
		case ev := <-self.regenThreadChan:
			{
				self.Regen(ev)
			}
		}
	}
}

// regen every page of the board
func (self *VarnishCache) RegenerateBoard(group string) {
	n, _ := self.database.GetPagesPerBoard(group)
	for n > 0 {
		go self.invalidate(fmt.Sprintf("%s%s%s-%d.html", self.varnish_url, self.prefix, group, n))
		go self.invalidate(fmt.Sprintf("%s%s%s/%d/", self.varnish_url, self.prefix, group, n))
		n--
	}
	self.invalidate(fmt.Sprintf("%s%sb/%s/", self.varnish_url, self.prefix, group))
}

// regenerate pages after a mod event
func (self *VarnishCache) RegenOnModEvent(newsgroup, msgid, root string, page int) {
	self.regenGroupChan <- groupRegenRequest{newsgroup, page}
	self.regenThreadChan <- ArticleEntry{newsgroup, root}
}

func (self *VarnishCache) Start() {
	go self.pollRegen()
}

func (self *VarnishCache) Regen(msg ArticleEntry) {
	go self.invalidate(fmt.Sprintf("%s%s%s-%d.html", self.varnish_url, self.prefix, msg.Newsgroup(), 0))
	go self.invalidate(fmt.Sprintf("%s%s%s/%d/", self.varnish_url, self.prefix, msg.Newsgroup(), 0))
	go self.invalidate(fmt.Sprintf("%s%sthread-%s.html", self.varnish_url, self.prefix, HashMessageID(msg.MessageID())))
	go self.invalidate(fmt.Sprintf("%s%st/%s/", self.varnish_url, self.prefix, HashMessageID(msg.MessageID())))
	self.invalidateUkko()
}

func (self *VarnishCache) GetThreadChan() chan ArticleEntry {
	return self.regenThreadChan
}

func (self *VarnishCache) GetGroupChan() chan groupRegenRequest {
	return self.regenGroupChan
}

func (self *VarnishCache) GetHandler() http.Handler {
	return &varnishHandler{self}
}

func (self *VarnishCache) Close() {
	//nothig to do
}

func NewVarnishCache(varnish_url, bind_addr, prefix, webroot, name string, attachments bool, db Database, store ArticleStore) CacheInterface {
	cache := new(VarnishCache)
	cache.regenThreadChan = make(chan ArticleEntry, 16)
	cache.regenGroupChan = make(chan groupRegenRequest, 8)
	local_addr, err := net.ResolveTCPAddr("tcp", bind_addr)
	if err != nil {
		log.Fatalf("failed to resolve %s for varnish cache: %s", bind_addr, err)
	}
	cache.client = &http.Client{
		Transport: &http.Transport{
			Dial: func(network, addr string) (c net.Conn, err error) {
				var remote_addr *net.TCPAddr
				remote_addr, err = net.ResolveTCPAddr(network, addr)
				if err == nil {
					c, err = net.DialTCP(network, local_addr, remote_addr)
				}
				return
			},
		},
	}
	cache.prefix = prefix
	cache.webroot_dir = webroot
	cache.name = name
	cache.attachments = attachments
	cache.database = db
	cache.store = store
	cache.varnish_url = varnish_url
	return cache
}
<commit_msg>fix json<commit_after>package srnd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"
)

type varnishHandler struct {
	cache *VarnishCache
}

func (self *varnishHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	_, file := filepath.Split(path)

	isjson := strings.HasSuffix(path, "/json") || strings.HasSuffix(path, "/json/")

	if strings.HasPrefix(path, "/t/") {
		// thread handler
		parts := strings.Split(path[3:], "/")
		if len(parts) == 1 {
			goto notfound
		}
		hash := parts[1]
		msg, err := self.cache.database.GetMessageIDByHash(hash)
		if err == nil {
			template.genThread(self.cache.attachments, msg, self.cache.prefix, self.cache.name, w, self.cache.database, isjson)
			return
		} else {
			goto notfound
		}
	}
	if strings.Trim(path, "/") == "overboard" {
		// generate ukko aka overboard
		template.genUkko(self.cache.prefix, self.cache.name, w, self.cache.database, isjson)
		return
	}

	if strings.HasPrefix(path, "/b/") {
		// board handler
		parts := strings.Split(path[3:], "/")
		page := 0
		group := parts[0]
		if len(parts) > 1 && parts[1] != "" && parts[1] != "json" {
			var err error
			page, err = strconv.Atoi(parts[1])
			if err != nil {
				goto notfound
			}
		}
		hasgroup := self.cache.database.HasNewsgroup(group)
		if !hasgroup {
			goto notfound
		}
		pages := self.cache.database.GetGroupPageCount(group)
		if page >= int(pages) {
			goto notfound
		}
		template.genBoardPage(self.cache.attachments, self.cache.prefix, self.cache.name, group, page, w, self.cache.database, isjson)
		return
	}

	if strings.HasPrefix(path, "/o/") {
		page := 0
		parts := strings.Split(path, "/")
		if len(parts) > 1 {
			if parts[1] != "json" {
				var err error
				page, err = strconv.Atoi(parts[1])
				if err != nil {
					goto notfound
				}

			}
		}
		template.genUkkoPaginated(self.cache.prefix, self.cache.name, w, self.cache.database, page, isjson)
		return
	}

	if len(file) == 0 || file == "index.html" {
		template.genFrontPage(10, self.cache.prefix, self.cache.name, w, ioutil.Discard, self.cache.database)
		return
	}

	if file == "index.json" {
		// TODO: index.json
		goto notfound
	}
	if strings.HasPrefix(file, "history.html") {
		template.genGraphs(self.cache.prefix, w, self.cache.database)
		return
	}
	if strings.HasPrefix(file, "boards.html") {
		template.genFrontPage(10, self.cache.prefix, self.cache.name, ioutil.Discard, w, self.cache.database)
		return
	}

	if strings.HasPrefix(file, "boards.json") {
		b := self.cache.database.GetAllNewsgroups()
		json.NewEncoder(w).Encode(b)
		return
	}

	if strings.HasPrefix(file, "ukko.html") {
		template.genUkko(self.cache.prefix, self.cache.name, w, self.cache.database, false)
		return
	}
	if strings.HasPrefix(file, "ukko.json") {
		template.genUkko(self.cache.prefix, self.cache.name, w, self.cache.database, true)
		return
	}

	if strings.HasPrefix(file, "ukko-") {
		page := getUkkoPage(file)
		template.genUkkoPaginated(self.cache.prefix, self.cache.name, w, self.cache.database, page, isjson)
		return
	}
	if strings.HasPrefix(file, "thread-") {
		hash := getThreadHash(file)
		if len(hash) == 0 {
			goto notfound
		}
		msg, err := self.cache.database.GetMessageIDByHash(hash)
		if err != nil {
			goto notfound
		}
		template.genThread(self.cache.attachments, msg, self.cache.prefix, self.cache.name, w, self.cache.database, isjson)
		return
	}
	if strings.HasPrefix(file, "catalog-") {
		group := getGroupForCatalog(file)
		if len(group) == 0 {
			goto notfound
		}
		hasgroup := self.cache.database.HasNewsgroup(group)
		if !hasgroup {
			goto notfound
		}
		template.genCatalog(self.cache.prefix, self.cache.name, group, w, self.cache.database)
		return
	} else {
		group, page := getGroupAndPage(file)
		if len(group) == 0 || page < 0 {
			goto notfound
		}
		hasgroup := self.cache.database.HasNewsgroup(group)
		if !hasgroup {
			goto notfound
		}
		pages := self.cache.database.GetGroupPageCount(group)
		if page >= int(pages) {
			goto notfound
		}
		template.genBoardPage(self.cache.attachments, self.cache.prefix, self.cache.name, group, page, w, self.cache.database, isjson)
		return
	}

notfound:
	template.renderNotFound(w, r, self.cache.prefix, self.cache.name)
}

type VarnishCache struct {
	database Database
	store    ArticleStore

	webroot_dir string
	name        string

	regen_threads int
	attachments   bool

	prefix          string
	varnish_url     string
	client          *http.Client
	regenThreadChan chan ArticleEntry
	regenGroupChan  chan groupRegenRequest
}

func (self *VarnishCache) invalidate(r string) {
	u, _ := url.Parse(r)
	resp, err := self.client.Do(&http.Request{
		Method: "PURGE",
		URL:    u,
	})
	if err == nil {
		resp.Body.Close()
	} else {
		log.Println("varnish cache error", err)
	}
}

func (self *VarnishCache) DeleteBoardMarkup(group string) {
	n, _ := self.database.GetPagesPerBoard(group)
	for n > 0 {
		go self.invalidate(fmt.Sprintf("%s%s%s-%d.html", self.varnish_url, self.prefix, group, n))
		go self.invalidate(fmt.Sprintf("%s%sb/%s/%d/", self.varnish_url, self.prefix, group, n))
		n--
	}
	self.invalidate(fmt.Sprintf("%s%sb/%s/", self.varnish_url, self.prefix, group))
}

// try to delete root post's page
func (self *VarnishCache) DeleteThreadMarkup(root_post_id string) {
	self.invalidate(fmt.Sprintf("%s%sthread-%s.html", self.varnish_url, self.prefix, HashMessageID(root_post_id)))
	self.invalidate(fmt.Sprintf("%s%st/%s/", self.varnish_url, self.prefix, HashMessageID(root_post_id)))
}

// regen every newsgroup
func (self *VarnishCache) RegenAll() {
	// we will do this as it's used by rengen on start for frontend
	groups := self.database.GetAllNewsgroups()
	for _, group := range groups {
		self.database.GetGroupThreads(group, self.regenThreadChan)
	}
}

func (self *VarnishCache) RegenFrontPage() {
	self.invalidate(fmt.Sprintf("%s%s", self.varnish_url, self.prefix))
}

func (self *VarnishCache) invalidateUkko() {
	// TODO: invalidate paginated ukko
	self.invalidate(fmt.Sprintf("%s%sukko.html", self.varnish_url, self.prefix))
	self.invalidate(fmt.Sprintf("%s%soverboard/", self.varnish_url, self.prefix))
}

func (self *VarnishCache) pollRegen() {
	for {
		select {
		// consume regen requests
		case ev := <-self.regenGroupChan:
			{
				self.invalidate(fmt.Sprintf("%s%s%s-%d.html", self.varnish_url, self.prefix, ev.group, ev.page))
				self.invalidate(fmt.Sprintf("%s%sb/%s/%d/", self.varnish_url, self.prefix, ev.group, ev.page))
				if ev.page == 0 {
					self.invalidate(fmt.Sprintf("%s%sb/%s/", self.varnish_url, self.prefix, ev.group))
				}
			}
		case ev := <-self.regenThreadChan:
			{
				self.Regen(ev)
			}
		}
	}
}

// regen every page of the board
func (self *VarnishCache) RegenerateBoard(group string) {
	n, _ := self.database.GetPagesPerBoard(group)
	for n > 0 {
		go self.invalidate(fmt.Sprintf("%s%s%s-%d.html", self.varnish_url, self.prefix, group, n))
		go self.invalidate(fmt.Sprintf("%s%s%s/%d/", self.varnish_url, self.prefix, group, n))
		n--
	}
	self.invalidate(fmt.Sprintf("%s%sb/%s/", self.varnish_url, self.prefix, group))
}

// regenerate pages after a mod event
func (self *VarnishCache) RegenOnModEvent(newsgroup, msgid, root string, page int) {
	self.regenGroupChan <- groupRegenRequest{newsgroup, page}
	self.regenThreadChan <- ArticleEntry{newsgroup, root}
}

func (self *VarnishCache) Start() {
	go self.pollRegen()
}

func (self *VarnishCache) Regen(msg ArticleEntry) {
	go self.invalidate(fmt.Sprintf("%s%s%s-%d.html", self.varnish_url, self.prefix, msg.Newsgroup(), 0))
	go self.invalidate(fmt.Sprintf("%s%s%s/%d/", self.varnish_url, self.prefix, msg.Newsgroup(), 0))
	go self.invalidate(fmt.Sprintf("%s%sthread-%s.html", self.varnish_url, self.prefix, HashMessageID(msg.MessageID())))
	go self.invalidate(fmt.Sprintf("%s%st/%s/", self.varnish_url, self.prefix, HashMessageID(msg.MessageID())))
	self.invalidateUkko()
}

func (self *VarnishCache) GetThreadChan() chan ArticleEntry {
	return self.regenThreadChan
}

func (self *VarnishCache) GetGroupChan() chan groupRegenRequest {
	return self.regenGroupChan
}

func (self *VarnishCache) GetHandler() http.Handler {
	return &varnishHandler{self}
}

func (self *VarnishCache) Close() {
	//nothig to do
}

func NewVarnishCache(varnish_url, bind_addr, prefix, webroot, name string, attachments bool, db Database, store ArticleStore) CacheInterface {
	cache := new(VarnishCache)
	cache.regenThreadChan = make(chan ArticleEntry, 16)
	cache.regenGroupChan = make(chan groupRegenRequest, 8)
	local_addr, err := net.ResolveTCPAddr("tcp", bind_addr)
	if err != nil {
		log.Fatalf("failed to resolve %s for varnish cache: %s", bind_addr, err)
	}
	cache.client = &http.Client{
		Transport: &http.Transport{
			Dial: func(network, addr string) (c net.Conn, err error) {
				var remote_addr *net.TCPAddr
				remote_addr, err = net.ResolveTCPAddr(network, addr)
				if err == nil {
					c, err = net.DialTCP(network, local_addr, remote_addr)
				}
				return
			},
		},
	}
	cache.prefix = prefix
	cache.webroot_dir = webroot
	cache.name = name
	cache.attachments = attachments
	cache.database = db
	cache.store = store
	cache.varnish_url = varnish_url
	return cache
}
<|endoftext|>


<commit_before>package grid

import (
	"fmt"
	"image"
	"math"
	"os"
	"path"

	"github.com/llgcode/draw2d/draw2dimg"
	"github.com/mrfuxi/digit/common"
	"gopkg.in/cheggaaa/pb.v1"
)

const ImageSize = 50

type FragmentType uint8

const (
	FragmentTypeCornerNW FragmentType = 1 << iota
	FragmentTypeCornerNE
	FragmentTypeCornerSE
	FragmentTypeCornerSW
	FragmentTypeEdgeN
	FragmentTypeEdgeE
	FragmentTypeEdgeS
	FragmentTypeEdgeW
)

type GridInfo struct {
	Fragment FragmentType
	Train    bool
}

type DrawDirections struct {
	GridInfo
	DStartAngle float64
	DDiffAngle  float64
	Dx          float64
	Dy          float64
}
type Image struct {
	GridInfo
	Image image.Image
}

type Counter struct {
	Image
	ID int
}

var (
	outDir    = path.Join("out_grid")
	TestFile  = path.Join(outDir, "test.dat")
	TrainFile = path.Join(outDir, "train.dat")
)

var (
	progress *pb.ProgressBar
)

func IsCorner(fragment FragmentType) bool {
	switch fragment {
	case FragmentTypeCornerNW:
		return true
	case FragmentTypeCornerNE:
		return true
	case FragmentTypeCornerSE:
		return true
	case FragmentTypeCornerSW:
		return true
	}
	return false
}

func IsEdge(fragment FragmentType) bool {
	switch fragment {
	case FragmentTypeEdgeN:
		return true
	case FragmentTypeEdgeE:
		return true
	case FragmentTypeEdgeS:
		return true
	case FragmentTypeEdgeW:
		return true
	}
	return false
}

func prepareDrawDirections(directions chan<- DrawDirections) {
	fragments := []FragmentType{
		FragmentTypeCornerNW,
		FragmentTypeCornerNE,
		FragmentTypeCornerSE,
		FragmentTypeCornerSW,
		FragmentTypeEdgeN,
		FragmentTypeEdgeE,
		FragmentTypeEdgeS,
		FragmentTypeEdgeW,
	}

	xyMovements := []float64{}
	for d := 0.0; d <= ImageSize/4.0; d += 2.0 {
		xyMovements = append(xyMovements, d)
		if d != 0 {
			xyMovements = append(xyMovements, -d)
		}
	}
	dAngle := []float64{}
	for d := 0.0; d <= 15; d += 5 {
		dAngle = append(dAngle, d, -d)
		if d != 0 {
			dAngle = append(dAngle, -d)
		}
	}

	progress = pb.StartNew(len(fragments) * len(dAngle) * len(dAngle) * len(xyMovements) * len(xyMovements))

	for _, fragment := range fragments {
		for _, ds := range dAngle {
			for _, dd := range dAngle {
				for _, dx := range xyMovements {
					for _, dy := range xyMovements {
						directions <- DrawDirections{
							GridInfo: GridInfo{
								Fragment: fragment,
								Train:    true,
							},
							DStartAngle: ds,
							DDiffAngle:  dd,
							Dx:          dx,
							Dy:          dy,
						}
					}
				}
			}
		}
	}

}

func draw(directions <-chan DrawDirections, images chan<- Image) {
	for {
		direction, ok := <-directions
		if !ok {
			break
		}
		digit, err := drawFragment(direction)
		if err != nil {
			fmt.Printf("Counld not draw %#v. Err: %s", direction, err.Error())
			continue
		}
		images <- Image{
			GridInfo: direction.GridInfo,
			Image:    digit,
		}
	}
}

func imgCouter(images <-chan Image, counters chan<- Counter) {
	cnt := 1
	for img := range images {
		counters <- Counter{
			Image: img,
			ID:    cnt,
		}
		cnt++
	}
}

func imgSaver(counters <-chan Counter) {
	for counter := range counters {
		fileName := fmt.Sprintf("fragment-%06d-%v.png", counter.ID, counter.Fragment)
		if err := draw2dimg.SaveToPngFile(path.Join(outDir, fileName), counter.Image.Image); err != nil {
			fmt.Println(err)
		}
		progress.Increment()
	}
}

func drawFragment(directions DrawDirections) (img image.Image, err error) {
	center := ImageSize / 2.0

	canvas := image.NewRGBA(image.Rect(0, 0, ImageSize, ImageSize))
	gc := draw2dimg.NewGraphicContext(canvas)

	gc.DrawImage(image.Black)      // Background color
	gc.SetStrokeColor(image.White) // Line color
	gc.SetLineWidth(2)

	var startAngle float64
	var diffAngle float64 = 90
	switch directions.Fragment {
	case FragmentTypeCornerNW:
		startAngle = 0
	case FragmentTypeCornerNE:
		startAngle = 90
	case FragmentTypeCornerSE:
		startAngle = 180
	case FragmentTypeCornerSW:
		startAngle = 270

	case FragmentTypeEdgeN:
		startAngle = 0
	case FragmentTypeEdgeE:
		startAngle = 90
	case FragmentTypeEdgeS:
		startAngle = 180
	case FragmentTypeEdgeW:
		startAngle = 270
	}

	startAngle += directions.DStartAngle
	diffAngle += directions.DDiffAngle

	gc.Translate(center+directions.Dx, center+directions.Dy)
	gc.Rotate(startAngle * math.Pi / 180.0)

	if IsCorner(directions.Fragment) {
		gc.MoveTo(0, 0)
	} else {
		gc.MoveTo(-ImageSize, 0)
	}

	gc.LineTo(ImageSize, 0)
	gc.Close()
	gc.FillStroke()

	gc.Rotate(diffAngle * math.Pi / 180.0)

	gc.MoveTo(0, 0)
	gc.LineTo(ImageSize, 0)
	gc.Close()
	gc.FillStroke()

	return canvas, nil
}

func GenerateSudokuGrid() error {
	os.RemoveAll(outDir)
	if err := os.Mkdir(outDir, 0764); err != nil {
		return err
	}

	directions := make(chan DrawDirections, 1000)
	images := make(chan Image, 100)
	counters := make(chan Counter, 100)

	common.RoutineRunner(1, true, func() { prepareDrawDirections(directions) }, func() { close(directions) })
	common.RoutineRunner(4, true, func() { draw(directions, images) }, func() { close(images) })
	common.RoutineRunner(1, true, func() { imgCouter(images, counters) }, func() { close(counters) })
	common.RoutineRunner(4, false, func() { imgSaver(counters) }, nil)

	return nil
}
<commit_msg>Generate crosses and empty space<commit_after>package grid

import (
	"fmt"
	"image"
	"math"
	"math/rand"
	"os"
	"path"

	"github.com/llgcode/draw2d/draw2dimg"
	"github.com/mrfuxi/digit/common"
	"gopkg.in/cheggaaa/pb.v1"
)

const ImageSize = 50

type FragmentType uint8

const (
	FragmentTypeCornerNW FragmentType = iota
	FragmentTypeCornerNE
	FragmentTypeCornerSE
	FragmentTypeCornerSW
	FragmentTypeEdgeN
	FragmentTypeEdgeE
	FragmentTypeEdgeS
	FragmentTypeEdgeW
	FragmentTypeCross
	FragmentTypeEmpty
)

type FragmentSuperType uint8

const (
	FragmentSuperTypeCorner FragmentSuperType = iota
	FragmentSuperTypeEdge
	FragmentSuperTypeCross
	FragmentSuperTypeEmpty
)

type GridInfo struct {
	Fragment      FragmentType
	FragmentSuper FragmentSuperType
	Train         bool
}

type DrawDirections struct {
	GridInfo
	DStartAngle float64
	DDiffAngle  float64
	Dx          float64
	Dy          float64
}
type Image struct {
	GridInfo
	Image image.Image
}

type Counter struct {
	Image
	ID int
}

var (
	outDir    = path.Join("out_grid")
	TestFile  = path.Join(outDir, "test.dat")
	TrainFile = path.Join(outDir, "train.dat")
)

var (
	progress *pb.ProgressBar
)

func IsCorner(fragment FragmentType) bool {
	switch fragment {
	case FragmentTypeCornerNW:
		return true
	case FragmentTypeCornerNE:
		return true
	case FragmentTypeCornerSE:
		return true
	case FragmentTypeCornerSW:
		return true
	}
	return false
}

func IsEdge(fragment FragmentType) bool {
	switch fragment {
	case FragmentTypeEdgeN:
		return true
	case FragmentTypeEdgeE:
		return true
	case FragmentTypeEdgeS:
		return true
	case FragmentTypeEdgeW:
		return true
	}
	return false
}

func IsCross(fragment FragmentType) bool {
	return fragment == FragmentTypeCross
}

func IsEmpty(fragment FragmentType) bool {
	return fragment == FragmentTypeEmpty
}

func FragmentTypeToSuper(fragment FragmentType) FragmentSuperType {
	switch {
	case IsCorner(fragment):
		return FragmentSuperTypeCorner
	case IsEdge(fragment):
		return FragmentSuperTypeEdge
	case IsCross(fragment):
		return FragmentSuperTypeCross
	case IsEmpty(fragment):
		return FragmentSuperTypeEmpty
	default:
		panic("What is this?")
	}
}

func prepareDrawDirections(directions chan<- DrawDirections) {
	fragments := []FragmentType{
		FragmentTypeCornerNW,
		FragmentTypeCornerNE,
		FragmentTypeCornerSE,
		FragmentTypeCornerSW,
		FragmentTypeEdgeN,
		FragmentTypeEdgeE,
		FragmentTypeEdgeS,
		FragmentTypeEdgeW,
		FragmentTypeCross,
		FragmentTypeEmpty,
	}

	xyMovements := []float64{}
	for d := 0.0; d <= ImageSize/4.0; d += 2.0 {
		xyMovements = append(xyMovements, d)
		if d != 0 {
			xyMovements = append(xyMovements, -d)
		}
	}
	dAngle := []float64{}
	for d := 0.0; d <= 15; d += 5 {
		dAngle = append(dAngle, d, -d)
		if d != 0 {
			dAngle = append(dAngle, -d)
		}
	}

	progress = pb.StartNew(len(fragments) * len(dAngle) * len(dAngle) * len(xyMovements) * len(xyMovements))

	for _, fragment := range fragments {
		for _, ds := range dAngle {
			for _, dd := range dAngle {
				for _, dx := range xyMovements {
					for _, dy := range xyMovements {
						directions <- DrawDirections{
							GridInfo: GridInfo{
								Fragment:      fragment,
								FragmentSuper: FragmentTypeToSuper(fragment),
								Train:         rand.Intn(100) >= 5,
							},
							DStartAngle: ds,
							DDiffAngle:  dd,
							Dx:          dx,
							Dy:          dy,
						}
					}
				}
			}
		}
	}

}

func draw(directions <-chan DrawDirections, images chan<- Image) {
	for {
		direction, ok := <-directions
		if !ok {
			break
		}
		digit, err := drawFragment(direction)
		if err != nil {
			fmt.Printf("Counld not draw %#v. Err: %s", direction, err.Error())
			continue
		}
		images <- Image{
			GridInfo: direction.GridInfo,
			Image:    digit,
		}
	}
}

func imgCouter(images <-chan Image, counters chan<- Counter) {
	cnt := 1
	for img := range images {
		counters <- Counter{
			Image: img,
			ID:    cnt,
		}
		cnt++
	}
}

func imgSaver(counters <-chan Counter) {
	for counter := range counters {
		fileName := fmt.Sprintf("fragment-%06d-%v.png", counter.ID, counter.Fragment)
		if err := draw2dimg.SaveToPngFile(path.Join(outDir, fileName), counter.Image.Image); err != nil {
			fmt.Println(err)
		}
		progress.Increment()
	}
}

func drawFragment(directions DrawDirections) (img image.Image, err error) {
	center := ImageSize / 2.0

	canvas := image.NewRGBA(image.Rect(0, 0, ImageSize, ImageSize))

	if IsEmpty(directions.Fragment) {
		return canvas, nil
	}

	gc := draw2dimg.NewGraphicContext(canvas)

	gc.DrawImage(image.Black)      // Background color
	gc.SetStrokeColor(image.White) // Line color
	gc.SetLineWidth(2)

	var startAngle float64
	var diffAngle float64 = 90
	switch directions.Fragment {
	case FragmentTypeCornerNW:
		startAngle = 0
	case FragmentTypeCornerNE:
		startAngle = 90
	case FragmentTypeCornerSE:
		startAngle = 180
	case FragmentTypeCornerSW:
		startAngle = 270

	case FragmentTypeEdgeN:
		startAngle = 0
	case FragmentTypeEdgeE:
		startAngle = 90
	case FragmentTypeEdgeS:
		startAngle = 180
	case FragmentTypeEdgeW:
		startAngle = 270
	}

	startAngle += directions.DStartAngle
	diffAngle += directions.DDiffAngle

	gc.Translate(center+directions.Dx, center+directions.Dy)
	gc.Rotate(startAngle * math.Pi / 180.0)

	if IsCorner(directions.Fragment) {
		gc.MoveTo(0, 0)
	} else {
		gc.MoveTo(-ImageSize, 0)
	}

	gc.LineTo(ImageSize, 0)
	gc.Close()
	gc.FillStroke()

	gc.Rotate(diffAngle * math.Pi / 180.0)

	if IsCross(directions.Fragment) {
		gc.LineTo(-ImageSize, 0)
	} else {
		gc.MoveTo(0, 0)
	}
	gc.LineTo(ImageSize, 0)
	gc.Close()
	gc.FillStroke()

	return canvas, nil
}

func GenerateSudokuGrid() error {
	os.RemoveAll(outDir)
	if err := os.Mkdir(outDir, 0764); err != nil {
		return err
	}

	directions := make(chan DrawDirections, 1000)
	images := make(chan Image, 100)
	counters := make(chan Counter, 100)

	common.RoutineRunner(1, true, func() { prepareDrawDirections(directions) }, func() { close(directions) })
	common.RoutineRunner(4, true, func() { draw(directions, images) }, func() { close(images) })
	common.RoutineRunner(1, true, func() { imgCouter(images, counters) }, func() { close(counters) })
	common.RoutineRunner(4, false, func() { imgSaver(counters) }, nil)

	return nil
}
<|endoftext|>


<commit_before>package gridt

import (
	"fmt"
	"testing"
)

const (
	logMsgf   = "\n%s: %s"
	fatalMsgf = logMsgf + "\ncolumns = %v\nlines = %v\nfit = %v"
)

var (
	randomLists = [][]string{
		{"a_value", "another_value", "lots_of_cells", "small_value", "biiiiiiiiiiiig_vaaaaaaaaaalue", "with spaces, it's better to read", "abc", "123", "baby_u_n_me", "bla bla blablablablab", "I see the endings, now", "nothing is broken"},
		{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "b", "c"},
		{"a", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "c"},
		{"a", "b", "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"},
		{"aaaaa", "b", "c", "d", "e", "f", "g", "h", "i"},
	}
)

func fixedList(n int) []string {
	s := make([]string, n)
	for i := 0; i < n; i++ {
		s[i] = "1234567890"
	}
	return s
}

func fmtMsg(i, ii, len int, f bool) string {
	var list, fit string
	if !f {
		list = "n empty list"
		fit = "does not fit"
	} else {
		fit = "fits"
		list = fmt.Sprintf(" list with %d column(s)", len)
	}
	return fmt.Sprintf("#%d #%d Should return a%s that %s", i, ii, list, fit)
}

func TestFitIntoWidth(t *testing.T) {
	gridTestFitInto(t, func(g *Grid, c, w int) (Dimensions, bool) { return g.FitIntoWidth(w) })

}
func TestFitIntoColumns(t *testing.T) {
	gridTestFitInto(t, func(g *Grid, c, w int) (Dimensions, bool) { return g.FitIntoColumns(c) })
}

func gridTestFitInto(t *testing.T, test func(g *Grid, c, w int) (Dimensions, bool)) {
	for i, c := range []struct {
		v []string  //=> cells' values
		m int       //=> maximum width
		d Direction //=> direction
		s string    //=> separator
		c int       //=> columns
		l int       //=> lines
		f bool      //=> if it fits
	}{
		// Empty lists.
		{fixedList(0), 10, TopToBottom, " ", 0, 0, false},
		{fixedList(0), 10, LeftToRight, " ", 0, 0, false},

		// Lists with one cell.
		{fixedList(1), 20, TopToBottom, " ", 1, 1, true},
		{fixedList(1), 5, TopToBottom, " ", 0, 0, false},
		{fixedList(1), 20, LeftToRight, " ", 1, 1, true},
		{fixedList(1), 5, LeftToRight, " ", 0, 0, false},

		// Invalid maximum size.
		{fixedList(1), 0, TopToBottom, " ", 0, 0, false},
		{fixedList(1), 0, LeftToRight, " ", 0, 0, false},

		// Lists with two cells.
		{fixedList(2), 30, TopToBottom, " ", 2, 1, true},
		{fixedList(2), 15, TopToBottom, " ", 1, 2, true},
		{fixedList(2), 5, TopToBottom, " ", 0, 0, false},
		{fixedList(2), 30, LeftToRight, " ", 2, 1, true},
		{fixedList(2), 15, LeftToRight, " ", 1, 2, true},
		{fixedList(2), 5, LeftToRight, " ", 0, 0, false},

		// Lists with three cells.
		{fixedList(3), 50, TopToBottom, " ", 3, 1, true},
		{fixedList(3), 30, TopToBottom, " ", 2, 2, true},
		{fixedList(3), 15, TopToBottom, " ", 1, 3, true},
		{fixedList(3), 5, TopToBottom, " ", 0, 0, false},
		{fixedList(3), 50, LeftToRight, " ", 3, 1, true},
		{fixedList(3), 30, LeftToRight, " ", 2, 2, true},
		{fixedList(3), 15, LeftToRight, " ", 1, 3, true},
		{fixedList(3), 5, LeftToRight, " ", 0, 0, false},

		// Random-sized lists.
		{randomLists[0], 72, TopToBottom, "  ", 3, 4, true},
		{randomLists[0], 72, LeftToRight, "  ", 2, 6, true},
		{randomLists[1], 100, TopToBottom, "^-._,^-._,^-._,^-._,^-._,^-._,^", 1, 3, true},
		{randomLists[1], 100, LeftToRight, "^-._,^-._,^-._,^-._,^-._,^-._,^", 1, 3, true},
		{randomLists[2], 100, TopToBottom, "^-._,^-._,^-._,^-._,^-._,^-._,^", 1, 3, true},
		{randomLists[2], 100, LeftToRight, "^-._,^-._,^-._,^-._,^-._,^-._,^", 1, 3, true},
		{randomLists[3], 100, TopToBottom, "^-._,^-._,^-._,^-._,^-._,^-._,^", 1, 3, true},
		{randomLists[3], 100, LeftToRight, "^-._,^-._,^-._,^-._,^-._,^-._,^", 1, 3, true},
		{randomLists[4], 9, TopToBottom, " ", 3, 3, true},
		{randomLists[4], 9, LeftToRight, " ", 3, 3, true},
	} {
		gNewWithCells := NewWithCells(c.d, c.s, c.v...)
		gNewAdd := New(c.d, c.s).Add(c.v...)
		gNewInsertDelete := NewWithCells(c.d, c.s, c.v...)
		for i, v := range c.v {
			gNewInsertDelete.Delete(i)
			gNewInsertDelete.Insert(i, v)
		}
		for ii, g := range []*Grid{
			gNewWithCells,
			gNewAdd,
			gNewInsertDelete,
		} {
			msg := fmtMsg(i, ii, c.c, c.f)
			d, f := test(g, c.c, c.m)
			cellsOk := len(c.v) == len(g.Cells())
			for i, cell := range g.Cells() {
				if c.v[i] != cell {
					cellsOk = false
					break
				}
			}
			if g.Direction() != c.d ||
				g.Separator() != c.s ||
				!cellsOk ||
				d.l != c.l ||
				f != c.f {
				t.Fatalf(fatalMsgf, msg, ballotX, d.ws, d.l, f)
			}
			t.Logf(logMsgf, msg, checkMark)
		}
	}
}
<commit_msg>Organize tests.<commit_after>package gridt

import (
	"fmt"
	"testing"
)

const (
	logMsgf   = "\n%s: %s"
	fatalMsgf = logMsgf + "\ncolumns = %v\nlines = %v\nfit = %v"
)

var (
	randomLists = [][]string{
		{"a_value", "another_value", "lots_of_cells", "small_value", "biiiiiiiiiiiig_vaaaaaaaaaalue", "with spaces, it's better to read", "abc", "123", "baby_u_n_me", "bla bla blablablablab", "I see the endings, now", "nothing is broken"},
		{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "b", "c"},
		{"a", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "c"},
		{"a", "b", "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"},
		{"aaaaa", "b", "c", "d", "e", "f", "g", "h", "i"},
	}
)

func fixedList(n int) []string {
	s := make([]string, n)
	for i := 0; i < n; i++ {
		s[i] = "1234567890"
	}
	return s
}

func fmtMsg(i, ii, len int, f bool) string {
	var list, fit string
	if !f {
		list = "n empty list"
		fit = "does not fit"
	} else {
		fit = "fits"
		list = fmt.Sprintf(" list with %d column(s)", len)
	}
	return fmt.Sprintf("#%d #%d Should return a%s that %s", i, ii, list, fit)
}

func TestFitIntoWidth(t *testing.T) {
	gridTestFitInto(t, func(g *Grid, c, w int) (Dimensions, bool) { return g.FitIntoWidth(w) })

}
func TestFitIntoColumns(t *testing.T) {
	gridTestFitInto(t, func(g *Grid, c, w int) (Dimensions, bool) { return g.FitIntoColumns(c) })
}

func gridTestFitInto(t *testing.T, test func(g *Grid, c, w int) (Dimensions, bool)) {
	for i, c := range []struct {
		v []string  //=> cells' values
		m int       //=> maximum width
		d Direction //=> direction
		s string    //=> separator
		c int       //=> columns
		l int       //=> lines
		f bool      //=> if it fits
	}{
		// Empty lists.
		{fixedList(0), 10, TopToBottom, " ", 0, 0, false},
		{fixedList(0), 10, LeftToRight, " ", 0, 0, false},

		// Invalid maximum size.
		{fixedList(10), 0, TopToBottom, " ", 0, 0, false},
		{fixedList(10), 0, LeftToRight, " ", 0, 0, false},

		// Lists with one cell.
		{fixedList(1), 20, TopToBottom, " ", 1, 1, true},
		{fixedList(1), 5, TopToBottom, " ", 0, 0, false},
		{fixedList(1), 20, LeftToRight, " ", 1, 1, true},
		{fixedList(1), 5, LeftToRight, " ", 0, 0, false},

		// Lists with two cells.
		{fixedList(2), 30, TopToBottom, " ", 2, 1, true},
		{fixedList(2), 15, TopToBottom, " ", 1, 2, true},
		{fixedList(2), 5, TopToBottom, " ", 0, 0, false},
		{fixedList(2), 30, LeftToRight, " ", 2, 1, true},
		{fixedList(2), 15, LeftToRight, " ", 1, 2, true},
		{fixedList(2), 5, LeftToRight, " ", 0, 0, false},

		// Lists with three cells.
		{fixedList(3), 50, TopToBottom, " ", 3, 1, true},
		{fixedList(3), 30, TopToBottom, " ", 2, 2, true},
		{fixedList(3), 15, TopToBottom, " ", 1, 3, true},
		{fixedList(3), 5, TopToBottom, " ", 0, 0, false},
		{fixedList(3), 50, LeftToRight, " ", 3, 1, true},
		{fixedList(3), 30, LeftToRight, " ", 2, 2, true},
		{fixedList(3), 15, LeftToRight, " ", 1, 3, true},
		{fixedList(3), 5, LeftToRight, " ", 0, 0, false},

		// Random-sized lists.
		{randomLists[0], 72, TopToBottom, "  ", 3, 4, true},
		{randomLists[0], 72, LeftToRight, "  ", 2, 6, true},
		{randomLists[1], 100, TopToBottom, "^-._,^-._,^-._,^-._,^-._,^-._,^", 1, 3, true},
		{randomLists[1], 100, LeftToRight, "^-._,^-._,^-._,^-._,^-._,^-._,^", 1, 3, true},
		{randomLists[2], 100, TopToBottom, "^-._,^-._,^-._,^-._,^-._,^-._,^", 1, 3, true},
		{randomLists[2], 100, LeftToRight, "^-._,^-._,^-._,^-._,^-._,^-._,^", 1, 3, true},
		{randomLists[3], 100, TopToBottom, "^-._,^-._,^-._,^-._,^-._,^-._,^", 1, 3, true},
		{randomLists[3], 100, LeftToRight, "^-._,^-._,^-._,^-._,^-._,^-._,^", 1, 3, true},
		{randomLists[4], 9, TopToBottom, " ", 3, 3, true},
		{randomLists[4], 9, LeftToRight, " ", 3, 3, true},
	} {
		gNewWithCells := NewWithCells(c.d, c.s, c.v...)
		gNewAdd := New(c.d, c.s).Add(c.v...)
		gNewInsertDelete := NewWithCells(c.d, c.s, c.v...)
		for i, v := range c.v {
			gNewInsertDelete.Delete(i)
			gNewInsertDelete.Insert(i, v)
		}
		for ii, g := range []*Grid{
			gNewWithCells,
			gNewAdd,
			gNewInsertDelete,
		} {
			msg := fmtMsg(i, ii, c.c, c.f)
			d, f := test(g, c.c, c.m)
			cellsOk := len(c.v) == len(g.Cells())
			for i, cell := range g.Cells() {
				if c.v[i] != cell {
					cellsOk = false
					break
				}
			}
			if g.Direction() != c.d ||
				g.Separator() != c.s ||
				!cellsOk ||
				d.l != c.l ||
				f != c.f {
				t.Fatalf(fatalMsgf, msg, ballotX, d.ws, d.l, f)
			}
			t.Logf(logMsgf, msg, checkMark)
		}
	}
}
<|endoftext|>


<commit_before><commit_msg>remove unimplemented layer<commit_after><|endoftext|>


<commit_before>package mails

import (
	"bytes"
	"crypto/md5"
	"fmt"
	"net/url"
	"os"
	"regexp"
	"sync"

	"github.com/toomore/mailbox/campaign"
	"github.com/toomore/mailbox/utils"
)

var areg = regexp.MustCompile(`href="(http[s]?://[a-zA-z0-9/\.:?=,-@%()_&\+]+)"`)

// ReplaceReader is to replace reader open mail link
func ReplaceReader(html *[]byte, cid string, seed string, uid string) {
	data := url.Values{}
	data.Set("c", cid)
	data.Set("u", uid)
	hm := campaign.MakeMacSeed(seed, data)
	*html = bytes.Replace(
		*html,
		[]byte("{{READER}}"),
		[]byte(fmt.Sprintf("https://%s/read/%x?%s", os.Getenv("mailbox_web_site"), hm, data.Encode())),
		1)
}

// ReplaceFname is to replace FNAME tag
func ReplaceFname(html *[]byte, fname string) {
	*html = bytes.Replace(*html, []byte("{{FNAME}}"), []byte(fname), -1)
}

// ReplaceLname is to replace FNAME tag
func ReplaceLname(html *[]byte, lname string) {
	*html = bytes.Replace(*html, []byte("{{LNAME}}"), []byte(lname), -1)
}

// ReplaceATag is to replace HTML a tag
func ReplaceATag(html *[]byte, allATags map[string]LinksData, cid string, seed string, uid string) {
	data := url.Values{}
	data.Set("c", cid)
	data.Set("u", uid)
	data.Set("t", "a")

	for _, v := range allATags {
		data.Set("l", v.LinkID)
		hm := campaign.MakeMacSeed(seed, data)

		*html = bytes.Replace(*html, []byte(fmt.Sprintf("href=\"%s\"", v.URL)),
			[]byte(fmt.Sprintf("href=\"https://%s/door/%x?%s\"", os.Getenv("mailbox_web_site"), hm, data.Encode())), -1)
	}
}

// LinksData is the link data
type LinksData struct {
	Md5h   string
	LinkID string
	URL    []byte
}

// FilterATags is to filter, find all a tag data
func FilterATags(body *[]byte, cid string) map[string]LinksData {
	allATags := areg.FindAllSubmatch(*body, -1)
	result := make(map[string]LinksData)
	var wg sync.WaitGroup
	wg.Add(len(allATags))
	for _, v := range allATags {
		go func(url []byte) {
			md5h := md5.New()
			md5h.Write(url)
			md5hstr := fmt.Sprintf("%x", md5h.Sum(nil))
			linkID := fmt.Sprintf("%s", utils.GenSeed())
			_, err := utils.GetConn().Query(`INSERT INTO links(id,cid,url,urlhash) VALUES(?,?,?,?)`, linkID, cid, url, md5hstr)
			if err != nil {
				rows, _ := utils.GetConn().Query(`SELECT id FROM links WHERE cid=? AND urlhash=?`, cid, md5hstr)
				for rows.Next() {
					rows.Scan(&linkID)
				}
			}
			result[linkID] = LinksData{
				Md5h:   md5hstr,
				LinkID: linkID,
				URL:    url,
			}
			wg.Done()
		}(v[1])
	}
	wg.Wait()
	return result
}
<commit_msg>Rename<commit_after>package mails

import (
	"bytes"
	"crypto/md5"
	"fmt"
	"net/url"
	"os"
	"regexp"
	"sync"

	"github.com/toomore/mailbox/campaign"
	"github.com/toomore/mailbox/utils"
)

var htmla = regexp.MustCompile(`href="(http[s]?://[a-zA-z0-9/\.:?=,-@%()_&\+]+)"`)

// ReplaceReader is to replace reader open mail link
func ReplaceReader(html *[]byte, cid string, seed string, uid string) {
	data := url.Values{}
	data.Set("c", cid)
	data.Set("u", uid)
	hm := campaign.MakeMacSeed(seed, data)
	*html = bytes.Replace(
		*html,
		[]byte("{{READER}}"),
		[]byte(fmt.Sprintf("https://%s/read/%x?%s", os.Getenv("mailbox_web_site"), hm, data.Encode())),
		1)
}

// ReplaceFname is to replace FNAME tag
func ReplaceFname(html *[]byte, fname string) {
	*html = bytes.Replace(*html, []byte("{{FNAME}}"), []byte(fname), -1)
}

// ReplaceLname is to replace FNAME tag
func ReplaceLname(html *[]byte, lname string) {
	*html = bytes.Replace(*html, []byte("{{LNAME}}"), []byte(lname), -1)
}

// ReplaceATag is to replace HTML a tag
func ReplaceATag(html *[]byte, allATags map[string]LinksData, cid string, seed string, uid string) {
	data := url.Values{}
	data.Set("c", cid)
	data.Set("u", uid)
	data.Set("t", "a")

	for _, v := range allATags {
		data.Set("l", v.LinkID)
		hm := campaign.MakeMacSeed(seed, data)

		*html = bytes.Replace(*html, []byte(fmt.Sprintf("href=\"%s\"", v.URL)),
			[]byte(fmt.Sprintf("href=\"https://%s/door/%x?%s\"", os.Getenv("mailbox_web_site"), hm, data.Encode())), -1)
	}
}

// LinksData is the link data
type LinksData struct {
	Md5h   string
	LinkID string
	URL    []byte
}

// FilterATags is to filter, find all a tag data
func FilterATags(body *[]byte, cid string) map[string]LinksData {
	allATags := htmla.FindAllSubmatch(*body, -1)
	result := make(map[string]LinksData)
	var wg sync.WaitGroup
	wg.Add(len(allATags))
	for _, v := range allATags {
		go func(url []byte) {
			md5h := md5.New()
			md5h.Write(url)
			md5hstr := fmt.Sprintf("%x", md5h.Sum(nil))
			linkID := fmt.Sprintf("%s", utils.GenSeed())
			_, err := utils.GetConn().Query(`INSERT INTO links(id,cid,url,urlhash) VALUES(?,?,?,?)`, linkID, cid, url, md5hstr)
			if err != nil {
				rows, _ := utils.GetConn().Query(`SELECT id FROM links WHERE cid=? AND urlhash=?`, cid, md5hstr)
				for rows.Next() {
					rows.Scan(&linkID)
				}
			}
			result[linkID] = LinksData{
				Md5h:   md5hstr,
				LinkID: linkID,
				URL:    url,
			}
			wg.Done()
		}(v[1])
	}
	wg.Wait()
	return result
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package context

import (
	"os"
	"path/filepath"
	"strings"

	"github.com/juju/cmd"
	"github.com/juju/errors"
	"github.com/juju/loggo"
	"gopkg.in/juju/charm.v5"
	"gopkg.in/yaml.v1"
	"launchpad.net/gnuflag"

	"github.com/juju/juju/process"
)

var logger = loggo.GetLogger("juju.process.persistence")

type cmdInfo struct {
	// Name is the command's name.
	Name string
	// ExtraArgs is the list of arg names that follow "name", if any.
	ExtraArgs []string
	// OptionalArgs is the list of optional args, if any.
	OptionalArgs []string
	// Summary is the one-line description of the command.
	Summary string
	// Doc is the multi-line description of the command.
	Doc string
}

// TODO(ericsnow) How to convert endpoints (charm.Process.Ports[].Name)
// into actual ports? For now we should error out with such definitions
// (and recommend overriding).

// baseCommand implements the common portions of the workload process
// hook env commands.
type baseCommand struct {
	cmd.CommandBase

	cmdInfo

	ctx     HookContext
	compCtx Component

	// Name is the name of the process in charm metadata.
	Name string
	// info is the process info for the named workload process.
	info *process.Info
}

func newCommand(ctx HookContext) (*baseCommand, error) {
	compCtx, err := ContextComponent(ctx)
	if err != nil {
		// The component wasn't registered properly.
		return nil, errors.Trace(err)
	}
	return &baseCommand{
		ctx:     ctx,
		compCtx: compCtx,
	}, nil
}

// Info implements cmd.Command.
func (c baseCommand) Info() *cmd.Info {
	args := []string{"<name>"} // name isn't optional
	for _, name := range c.cmdInfo.ExtraArgs {
		arg := "<" + name + ">"
		for _, optional := range c.cmdInfo.OptionalArgs {
			if name == optional {
				arg = "[" + arg + "]"
				break
			}
		}
		args = append(args, arg)
	}
	return &cmd.Info{
		Name:    c.cmdInfo.Name,
		Args:    strings.Join(args, " "),
		Purpose: c.cmdInfo.Summary,
		Doc:     c.cmdInfo.Doc,
	}
}

// Init implements cmd.Command.
func (c *baseCommand) Init(args []string) error {
	if len(args) == 0 {
		return errors.Errorf("missing process name")
	}
	return errors.Trace(c.init(args[0]))
}

func (c *baseCommand) init(name string) error {
	if name == "" {
		return errors.Errorf("got empty name")
	}
	c.Name = name

	pInfo, err := c.compCtx.Get(c.Name)
	if err != nil && !errors.IsNotFound(err) {
		return errors.Trace(err)
	}
	c.info = pInfo

	return nil
}

// registeringCommand is the base for commands that register a process
// that has been launched.
type registeringCommand struct {
	baseCommand

	// Details is the launch details returned from the process plugin.
	Details process.Details

	// Overrides overwrite the process definition.
	Overrides []string

	// Additions extend the process definition.
	Additions []string

	// UpdatedProcess stores the new process, if there were any overrides OR additions.
	UpdatedProcess *charm.Process

	// Definition is the file definition of the process.
	Definition cmd.FileVar

	// ReadMetadata extracts charm metadata from the given file.
	ReadMetadata func(filename string) (*charm.Meta, error)
}

func newRegisteringCommand(ctx HookContext) (*registeringCommand, error) {
	base, err := newCommand(ctx)
	if err != nil {
		return nil, errors.Trace(err)
	}
	return &registeringCommand{
		baseCommand:  *base,
		ReadMetadata: readMetadata,
	}, nil
}

func readMetadata(filename string) (*charm.Meta, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, errors.Trace(err)
	}
	defer file.Close()

	meta, err := charm.ReadMeta(file)
	if err != nil {
		return nil, errors.Trace(err)
	}

	return meta, nil
}

// SetFlags implements cmd.Command.
func (c *registeringCommand) SetFlags(f *gnuflag.FlagSet) {
	f.Var(&c.Definition, "definition", "process definition filename (use \"-\" for STDIN)")
	f.Var(cmd.NewAppendStringsValue(&c.Overrides), "override", "override process definition")
	f.Var(cmd.NewAppendStringsValue(&c.Additions), "extend", "extend process definition")
}

func (c *registeringCommand) init(name string) error {
	if err := c.baseCommand.init(name); err != nil {
		return errors.Trace(err)
	}

	if c.info != nil {
		return errors.Errorf("process %q already registered", c.Name)
	}

	if err := c.checkSpace(); err != nil {
		return errors.Trace(err)
	}

	// Either the named process must already be defined or the command
	// must have been run with the --definition option.
	if c.Definition.Path != "" {
		if c.info != nil {
			return errors.Errorf("process %q already defined", c.Name)
		}
	}

	return nil
}

// register updates the hook context with the information for the
// registered workload process. An error is returned if the process
// was already registered.
func (c *registeringCommand) register(ctx *cmd.Context) error {
	info, err := c.findValidInfo(ctx)
	if err != nil {
		return errors.Trace(err)
	}

	info.Details = c.Details

	if err := c.compCtx.Set(c.Name, info); err != nil {
		return errors.Trace(err)
	}

	// We flush to state immedeiately so that status reflects the
	// process correctly.
	if err := c.compCtx.Flush(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (c *registeringCommand) findValidInfo(ctx *cmd.Context) (*process.Info, error) {
	if c.info == nil {
		info, err := c.findInfo(ctx)
		if err != nil {
			return nil, errors.Trace(err)
		}
		c.info = info
	}
	info := *c.info // copied

	if c.UpdatedProcess == nil {
		logger.Debugf("parsing updates")
		newProcess, err := c.parseUpdates(c.info.Process)
		if err != nil {
			return nil, errors.Trace(err)
		}
		c.UpdatedProcess = newProcess
	}
	info.Process = *c.UpdatedProcess

	// validate
	if err := info.Validate(); err != nil {
		return nil, errors.Trace(err)
	}
	if info.IsRegistered() {
		return nil, errors.Errorf("already registered")
	}
	return &info, nil
}

func (c *registeringCommand) findInfo(ctx *cmd.Context) (*process.Info, error) {
	var definition charm.Process
	if c.Definition.Path == "" {
		filename := filepath.Join(ctx.Dir, "metadata.yaml")
		charmDef, err := c.defFromMetadata(c.Name, filename)
		if err != nil {
			return nil, errors.Trace(err)
		}
		definition = *charmDef
	} else {
		// c.info must be nil at this point.
		data, err := c.Definition.Read(ctx)
		if err != nil {
			return nil, errors.Trace(err)
		}
		cliDef, err := parseDefinition(c.Name, data)
		if err != nil {
			return nil, errors.Trace(err)
		}
		definition = *cliDef
	}
	logger.Debugf("creating new process.Info")
	return &process.Info{Process: definition}, nil
}

func (c *registeringCommand) defFromMetadata(name, filename string) (*charm.Process, error) {
	meta, err := c.ReadMetadata(filename)
	if err != nil {
		return nil, errors.Trace(err)
	}
	for _, definition := range meta.Processes {
		if name == definition.Name {
			return &definition, nil
		}
	}
	return nil, errors.NotFoundf(name)
}

func parseDefinition(name string, data []byte) (*charm.Process, error) {
	raw := make(map[interface{}]interface{})
	if err := yaml.Unmarshal(data, raw); err != nil {
		return nil, errors.Trace(err)
	}
	definition, err := charm.ParseProcess(name, raw)
	if err != nil {
		return nil, errors.Trace(err)
	}
	if definition.Name == "" {
		definition.Name = name
	} else if definition.Name != name {
		return nil, errors.Errorf("process name mismatch; %q != %q", definition.Name, name)
	}
	return definition, nil
}

// checkSpace ensures that the requested network space is available
// to the hook.
func (c *registeringCommand) checkSpace() error {
	// TODO(wwitzel3) implement this to ensure that the endpoints provided exist in this space
	return nil
}

func (c *registeringCommand) parseUpdates(definition charm.Process) (*charm.Process, error) {
	overrides, err := parseUpdates(c.Overrides)
	if err != nil {
		return nil, errors.Annotate(err, "override")
	}

	additions, err := parseUpdates(c.Additions)
	if err != nil {
		return nil, errors.Annotate(err, "extend")
	}

	newDefinition, err := definition.Apply(overrides, additions)
	if err != nil {
		return nil, errors.Trace(err)
	}

	return newDefinition, nil
}

// parseUpdate builds a charm.ProcessFieldValue from an update string.
func parseUpdate(update string) (charm.ProcessFieldValue, error) {
	var pfv charm.ProcessFieldValue

	parts := strings.SplitN(update, ":", 2)
	if len(parts) == 1 {
		return pfv, errors.Errorf("missing value")
	}
	pfv.Field, pfv.Value = parts[0], parts[1]

	if pfv.Field == "" {
		return pfv, errors.Errorf("missing field")
	}
	if pfv.Value == "" {
		return pfv, errors.Errorf("missing value")
	}

	fieldParts := strings.SplitN(pfv.Field, "/", 2)
	if len(fieldParts) == 2 {
		pfv.Field = fieldParts[0]
		pfv.Subfield = fieldParts[1]
	}

	return pfv, nil
}

// parseUpdates parses the updates list in to a charm.ProcessFieldValue list.
func parseUpdates(updates []string) ([]charm.ProcessFieldValue, error) {
	var results []charm.ProcessFieldValue
	for _, update := range updates {
		pfv, err := parseUpdate(update)
		if err != nil {
			return nil, errors.Trace(err)
		}
		results = append(results, pfv)
	}
	return results, nil
}
<commit_msg>Move ReadMetadata up to baseCommand.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package context

import (
	"os"
	"path/filepath"
	"strings"

	"github.com/juju/cmd"
	"github.com/juju/errors"
	"github.com/juju/loggo"
	"gopkg.in/juju/charm.v5"
	goyaml "gopkg.in/yaml.v1"
	"launchpad.net/gnuflag"

	"github.com/juju/juju/process"
)

var logger = loggo.GetLogger("juju.process.persistence")

type cmdInfo struct {
	// Name is the command's name.
	Name string
	// ExtraArgs is the list of arg names that follow "name", if any.
	ExtraArgs []string
	// OptionalArgs is the list of optional args, if any.
	OptionalArgs []string
	// Summary is the one-line description of the command.
	Summary string
	// Doc is the multi-line description of the command.
	Doc string
}

// TODO(ericsnow) How to convert endpoints (charm.Process.Ports[].Name)
// into actual ports? For now we should error out with such definitions
// (and recommend overriding).

// baseCommand implements the common portions of the workload process
// hook env commands.
type baseCommand struct {
	cmd.CommandBase

	cmdInfo

	ctx     HookContext
	compCtx Component

	// Name is the name of the process in charm metadata.
	Name string
	// info is the process info for the named workload process.
	info *process.Info
	// ReadMetadata extracts charm metadata from the given file.
	ReadMetadata func(filename string) (*charm.Meta, error)
}

func newCommand(ctx HookContext) (*baseCommand, error) {
	compCtx, err := ContextComponent(ctx)
	if err != nil {
		// The component wasn't registered properly.
		return nil, errors.Trace(err)
	}
	return &baseCommand{
		ctx:          ctx,
		compCtx:      compCtx,
		ReadMetadata: readMetadata,
	}, nil
}

// Info implements cmd.Command.
func (c baseCommand) Info() *cmd.Info {
	args := []string{"<name>"} // name isn't optional
	for _, name := range c.cmdInfo.ExtraArgs {
		arg := "<" + name + ">"
		for _, optional := range c.cmdInfo.OptionalArgs {
			if name == optional {
				arg = "[" + arg + "]"
				break
			}
		}
		args = append(args, arg)
	}
	return &cmd.Info{
		Name:    c.cmdInfo.Name,
		Args:    strings.Join(args, " "),
		Purpose: c.cmdInfo.Summary,
		Doc:     c.cmdInfo.Doc,
	}
}

func readMetadata(filename string) (*charm.Meta, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, errors.Trace(err)
	}
	defer file.Close()

	meta, err := charm.ReadMeta(file)
	if err != nil {
		return nil, errors.Trace(err)
	}

	return meta, nil
}

// Init implements cmd.Command.
func (c *baseCommand) Init(args []string) error {
	if len(args) == 0 {
		return errors.Errorf("missing process name")
	}
	return errors.Trace(c.init(args[0]))
}

func (c *baseCommand) init(name string) error {
	if name == "" {
		return errors.Errorf("got empty name")
	}
	c.Name = name

	// TODO(ericsnow) Pull the definitions from the metadata here...

	pInfo, err := c.compCtx.Get(c.Name)
	if err != nil && !errors.IsNotFound(err) {
		return errors.Trace(err)
	}
	c.info = pInfo

	return nil
}

// registeringCommand is the base for commands that register a process
// that has been launched.
type registeringCommand struct {
	baseCommand

	// Details is the launch details returned from the process plugin.
	Details process.Details

	// Overrides overwrite the process definition.
	Overrides []string

	// Additions extend the process definition.
	Additions []string

	// UpdatedProcess stores the new process, if there were any overrides OR additions.
	UpdatedProcess *charm.Process

	// Definition is the file definition of the process.
	Definition cmd.FileVar
}

func newRegisteringCommand(ctx HookContext) (*registeringCommand, error) {
	base, err := newCommand(ctx)
	if err != nil {
		return nil, errors.Trace(err)
	}
	return &registeringCommand{
		baseCommand: *base,
	}, nil
}

// SetFlags implements cmd.Command.
func (c *registeringCommand) SetFlags(f *gnuflag.FlagSet) {
	f.Var(&c.Definition, "definition", "process definition filename (use \"-\" for STDIN)")
	f.Var(cmd.NewAppendStringsValue(&c.Overrides), "override", "override process definition")
	f.Var(cmd.NewAppendStringsValue(&c.Additions), "extend", "extend process definition")
}

func (c *registeringCommand) init(name string) error {
	if err := c.baseCommand.init(name); err != nil {
		return errors.Trace(err)
	}

	if c.info != nil {
		return errors.Errorf("process %q already registered", c.Name)
	}

	if err := c.checkSpace(); err != nil {
		return errors.Trace(err)
	}

	// Either the named process must already be defined or the command
	// must have been run with the --definition option.
	if c.Definition.Path != "" {
		if c.info != nil {
			return errors.Errorf("process %q already defined", c.Name)
		}
	}

	return nil
}

// register updates the hook context with the information for the
// registered workload process. An error is returned if the process
// was already registered.
func (c *registeringCommand) register(ctx *cmd.Context) error {
	info, err := c.findValidInfo(ctx)
	if err != nil {
		return errors.Trace(err)
	}

	info.Details = c.Details

	if err := c.compCtx.Set(c.Name, info); err != nil {
		return errors.Trace(err)
	}

	// We flush to state immedeiately so that status reflects the
	// process correctly.
	if err := c.compCtx.Flush(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (c *registeringCommand) findValidInfo(ctx *cmd.Context) (*process.Info, error) {
	if c.info == nil {
		info, err := c.findInfo(ctx)
		if err != nil {
			return nil, errors.Trace(err)
		}
		c.info = info
	}
	info := *c.info // copied

	if c.UpdatedProcess == nil {
		logger.Debugf("parsing updates")
		newProcess, err := c.parseUpdates(c.info.Process)
		if err != nil {
			return nil, errors.Trace(err)
		}
		c.UpdatedProcess = newProcess
	}
	info.Process = *c.UpdatedProcess

	// validate
	if err := info.Validate(); err != nil {
		return nil, errors.Trace(err)
	}
	if info.IsRegistered() {
		return nil, errors.Errorf("already registered")
	}
	return &info, nil
}

func (c *registeringCommand) findInfo(ctx *cmd.Context) (*process.Info, error) {
	var definition charm.Process
	if c.Definition.Path == "" {
		filename := filepath.Join(ctx.Dir, "metadata.yaml")
		charmDef, err := c.defFromMetadata(c.Name, filename)
		if err != nil {
			return nil, errors.Trace(err)
		}
		definition = *charmDef
	} else {
		// c.info must be nil at this point.
		data, err := c.Definition.Read(ctx)
		if err != nil {
			return nil, errors.Trace(err)
		}
		cliDef, err := parseDefinition(c.Name, data)
		if err != nil {
			return nil, errors.Trace(err)
		}
		definition = *cliDef
	}
	logger.Debugf("creating new process.Info")
	return &process.Info{Process: definition}, nil
}

func (c *registeringCommand) defFromMetadata(name, filename string) (*charm.Process, error) {
	meta, err := c.ReadMetadata(filename)
	if err != nil {
		return nil, errors.Trace(err)
	}
	for _, definition := range meta.Processes {
		if name == definition.Name {
			return &definition, nil
		}
	}
	return nil, errors.NotFoundf(name)
}

func parseDefinition(name string, data []byte) (*charm.Process, error) {
	raw := make(map[interface{}]interface{})
	if err := goyaml.Unmarshal(data, raw); err != nil {
		return nil, errors.Trace(err)
	}
	definition, err := charm.ParseProcess(name, raw)
	if err != nil {
		return nil, errors.Trace(err)
	}
	if definition.Name == "" {
		definition.Name = name
	} else if definition.Name != name {
		return nil, errors.Errorf("process name mismatch; %q != %q", definition.Name, name)
	}
	return definition, nil
}

// checkSpace ensures that the requested network space is available
// to the hook.
func (c *registeringCommand) checkSpace() error {
	// TODO(wwitzel3) implement this to ensure that the endpoints provided exist in this space
	return nil
}

func (c *registeringCommand) parseUpdates(definition charm.Process) (*charm.Process, error) {
	overrides, err := parseUpdates(c.Overrides)
	if err != nil {
		return nil, errors.Annotate(err, "override")
	}

	additions, err := parseUpdates(c.Additions)
	if err != nil {
		return nil, errors.Annotate(err, "extend")
	}

	newDefinition, err := definition.Apply(overrides, additions)
	if err != nil {
		return nil, errors.Trace(err)
	}

	return newDefinition, nil
}

// parseUpdate builds a charm.ProcessFieldValue from an update string.
func parseUpdate(update string) (charm.ProcessFieldValue, error) {
	var pfv charm.ProcessFieldValue

	parts := strings.SplitN(update, ":", 2)
	if len(parts) == 1 {
		return pfv, errors.Errorf("missing value")
	}
	pfv.Field, pfv.Value = parts[0], parts[1]

	if pfv.Field == "" {
		return pfv, errors.Errorf("missing field")
	}
	if pfv.Value == "" {
		return pfv, errors.Errorf("missing value")
	}

	fieldParts := strings.SplitN(pfv.Field, "/", 2)
	if len(fieldParts) == 2 {
		pfv.Field = fieldParts[0]
		pfv.Subfield = fieldParts[1]
	}

	return pfv, nil
}

// parseUpdates parses the updates list in to a charm.ProcessFieldValue list.
func parseUpdates(updates []string) ([]charm.ProcessFieldValue, error) {
	var results []charm.ProcessFieldValue
	for _, update := range updates {
		pfv, err := parseUpdate(update)
		if err != nil {
			return nil, errors.Trace(err)
		}
		results = append(results, pfv)
	}
	return results, nil
}
<|endoftext|>