<commit_before>package web_test

import (
	"fmt"
	"github.com/gocraft/web"
	. "launchpad.net/gocheck"
	// "net/http"
	// "strings"
)

type MiddlewareTestSuite struct{}

var _ = Suite(&MiddlewareTestSuite{})

func (c *Context) A(w web.ResponseWriter, r *web.Request) {
	fmt.Fprintf(w, "context-A")
}

func (c *Context) Z(w web.ResponseWriter, r *web.Request) {
	fmt.Fprintf(w, "context-Z")
}

func (c *AdminContext) B(w web.ResponseWriter, r *web.Request) {
	fmt.Fprintf(w, "admin-B")
}

func (c *ApiContext) C(w web.ResponseWriter, r *web.Request) {
	fmt.Fprintf(w, "api-C")
}

func (c *TicketsContext) D(w web.ResponseWriter, r *web.Request) {
	fmt.Fprintf(w, "tickets-D")
}

func (c *Context) mwNoNext(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "context-mw-NoNext ")
}

func (c *Context) mwAlpha(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "context-mw-Alpha ")
	next(w, r)
}

func (c *Context) mwBeta(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "context-mw-Beta ")
	next(w, r)
}

func (c *Context) mwGamma(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "context-mw-Gamma ")
	next(w, r)
}

func (c *ApiContext) mwDelta(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "api-mw-Delta ")
	next(w, r)
}

func (c *AdminContext) mwEpsilon(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "admin-mw-Epsilon ")
	next(w, r)
}

func (c *AdminContext) mwZeta(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "admin-mw-Zeta ")
	next(w, r)
}

func (c *TicketsContext) mwEta(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "tickets-mw-Eta ")
	next(w, r)
}

func (s *MiddlewareTestSuite) TestFlatNoMiddleware(c *C) {
	router := web.New(Context{})
	router.Get("/action", (*Context).A)
	router.Get("/action_z", (*Context).Z)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-A", 200)

	rw, req = newTestRequest("GET", "/action_z")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-Z", 200)
}

func (s *MiddlewareTestSuite) TestFlatOneMiddleware(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha)
	router.Get("/action", (*Context).A)
	router.Get("/action_z", (*Context).Z)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-A", 200)

	rw, req = newTestRequest("GET", "/action_z")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-Z", 200)
}

func (s *MiddlewareTestSuite) TestFlatTwoMiddleware(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha)
	router.Middleware((*Context).mwBeta)
	router.Get("/action", (*Context).A)
	router.Get("/action_z", (*Context).Z)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-mw-Beta context-A", 200)

	rw, req = newTestRequest("GET", "/action_z")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-mw-Beta context-Z", 200)
}

func (s *MiddlewareTestSuite) TestDualTree(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha)
	router.Get("/action", (*Context).A)
	admin := router.Subrouter(AdminContext{}, "/admin")
	admin.Middleware((*AdminContext).mwEpsilon)
	admin.Get("/action", (*AdminContext).B)
	api := router.Subrouter(ApiContext{}, "/api")
	api.Middleware((*ApiContext).mwDelta)
	api.Get("/action", (*ApiContext).C)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-A", 200)

	rw, req = newTestRequest("GET", "/admin/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha admin-mw-Epsilon admin-B", 200)

	rw, req = newTestRequest("GET", "/api/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha api-mw-Delta api-C", 200)
}

func (s *MiddlewareTestSuite) TestDualLeaningLeftTree(c *C) {
	router := web.New(Context{})
	router.Get("/action", (*Context).A)
	admin := router.Subrouter(AdminContext{}, "/admin")
	admin.Get("/action", (*AdminContext).B)
	api := router.Subrouter(ApiContext{}, "/api")
	api.Middleware((*ApiContext).mwDelta)
	api.Get("/action", (*ApiContext).C)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-A", 200)

	rw, req = newTestRequest("GET", "/admin/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "admin-B", 200)

	rw, req = newTestRequest("GET", "/api/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "api-mw-Delta api-C", 200)
}

func (s *MiddlewareTestSuite) TestTicketsA(c *C) {
	router := web.New(Context{})
	admin := router.Subrouter(AdminContext{}, "/admin")
	admin.Middleware((*AdminContext).mwEpsilon)
	tickets := admin.Subrouter(TicketsContext{}, "/tickets")
	tickets.Get("/action", (*TicketsContext).D)

	rw, req := newTestRequest("GET", "/admin/tickets/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "admin-mw-Epsilon tickets-D", 200)
}

func (s *MiddlewareTestSuite) TestTicketsB(c *C) {
	router := web.New(Context{})
	admin := router.Subrouter(AdminContext{}, "/admin")
	tickets := admin.Subrouter(TicketsContext{}, "/tickets")
	tickets.Middleware((*TicketsContext).mwEta)
	tickets.Get("/action", (*TicketsContext).D)

	rw, req := newTestRequest("GET", "/admin/tickets/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "tickets-mw-Eta tickets-D", 200)
}

func (s *MiddlewareTestSuite) TestTicketsC(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha)
	admin := router.Subrouter(AdminContext{}, "/admin")
	tickets := admin.Subrouter(TicketsContext{}, "/tickets")
	tickets.Get("/action", (*TicketsContext).D)

	rw, req := newTestRequest("GET", "/admin/tickets/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha tickets-D", 200)
}

func (s *MiddlewareTestSuite) TestTicketsD(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha)
	admin := router.Subrouter(AdminContext{}, "/admin")
	tickets := admin.Subrouter(TicketsContext{}, "/tickets")
	tickets.Middleware((*TicketsContext).mwEta)
	tickets.Get("/action", (*TicketsContext).D)

	rw, req := newTestRequest("GET", "/admin/tickets/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha tickets-mw-Eta tickets-D", 200)
}

func (s *MiddlewareTestSuite) TestTicketsE(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha)
	router.Middleware((*Context).mwBeta)
	router.Middleware((*Context).mwGamma)
	admin := router.Subrouter(AdminContext{}, "/admin")
	admin.Middleware((*AdminContext).mwEpsilon)
	admin.Middleware((*AdminContext).mwZeta)
	tickets := admin.Subrouter(TicketsContext{}, "/tickets")
	tickets.Middleware((*TicketsContext).mwEta)
	tickets.Get("/action", (*TicketsContext).D)

	rw, req := newTestRequest("GET", "/admin/tickets/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-mw-Beta context-mw-Gamma admin-mw-Epsilon admin-mw-Zeta tickets-mw-Eta tickets-D", 200)
}

func (s *MiddlewareTestSuite) TestNoNext(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwNoNext)
	router.Get("/action", (*Context).A)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-NoNext", 200)
}

func (s *MiddlewareTestSuite) TestSameContext(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha).
		Middleware((*Context).mwBeta)
	admin := router.Subrouter(Context{}, "/admin")
	admin.Middleware((*Context).mwGamma)
	admin.Get("/foo", (*Context).A)

	rw, req := newTestRequest("GET", "/admin/foo")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-mw-Beta context-mw-Gamma context-A", 200)
}

func (s *MiddlewareTestSuite) TestSameNamespace(c *C) {
	router := web.New(Context{})
	admin := router.Subrouter(AdminContext{}, "/")
	admin.Get("/action", (*AdminContext).B)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "admin-B", 200)
}
<commit_msg>TESTS: Add tests to the previous merged pull request where you can have a generic interface{} as the context for middleware.<commit_after>package web_test

import (
	"fmt"
	"github.com/gocraft/web"
	. "launchpad.net/gocheck"
	// "net/http"
	// "strings"
)

type MiddlewareTestSuite struct{}

var _ = Suite(&MiddlewareTestSuite{})

func (c *Context) A(w web.ResponseWriter, r *web.Request) {
	fmt.Fprintf(w, "context-A")
}

func (c *Context) Z(w web.ResponseWriter, r *web.Request) {
	fmt.Fprintf(w, "context-Z")
}

func (c *AdminContext) B(w web.ResponseWriter, r *web.Request) {
	fmt.Fprintf(w, "admin-B")
}

func (c *ApiContext) C(w web.ResponseWriter, r *web.Request) {
	fmt.Fprintf(w, "api-C")
}

func (c *TicketsContext) D(w web.ResponseWriter, r *web.Request) {
	fmt.Fprintf(w, "tickets-D")
}

func (c *Context) mwNoNext(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "context-mw-NoNext ")
}

func (c *Context) mwAlpha(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "context-mw-Alpha ")
	next(w, r)
}

func (c *Context) mwBeta(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "context-mw-Beta ")
	next(w, r)
}

func (c *Context) mwGamma(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "context-mw-Gamma ")
	next(w, r)
}

func (c *ApiContext) mwDelta(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "api-mw-Delta ")
	next(w, r)
}

func (c *AdminContext) mwEpsilon(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "admin-mw-Epsilon ")
	next(w, r)
}

func (c *AdminContext) mwZeta(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "admin-mw-Zeta ")
	next(w, r)
}

func (c *TicketsContext) mwEta(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "tickets-mw-Eta ")
	next(w, r)
}

func mwGenricInterface(ctx interface{}, w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
	fmt.Fprintf(w, "context-mw-Interface ")
	next(w, r)
}

func (s *MiddlewareTestSuite) TestFlatNoMiddleware(c *C) {
	router := web.New(Context{})
	router.Get("/action", (*Context).A)
	router.Get("/action_z", (*Context).Z)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-A", 200)

	rw, req = newTestRequest("GET", "/action_z")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-Z", 200)
}

func (s *MiddlewareTestSuite) TestFlatOneMiddleware(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha)
	router.Get("/action", (*Context).A)
	router.Get("/action_z", (*Context).Z)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-A", 200)

	rw, req = newTestRequest("GET", "/action_z")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-Z", 200)
}

func (s *MiddlewareTestSuite) TestFlatTwoMiddleware(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha)
	router.Middleware((*Context).mwBeta)
	router.Get("/action", (*Context).A)
	router.Get("/action_z", (*Context).Z)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-mw-Beta context-A", 200)

	rw, req = newTestRequest("GET", "/action_z")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-mw-Beta context-Z", 200)
}

func (s *MiddlewareTestSuite) TestDualTree(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha)
	router.Get("/action", (*Context).A)
	admin := router.Subrouter(AdminContext{}, "/admin")
	admin.Middleware((*AdminContext).mwEpsilon)
	admin.Get("/action", (*AdminContext).B)
	api := router.Subrouter(ApiContext{}, "/api")
	api.Middleware((*ApiContext).mwDelta)
	api.Get("/action", (*ApiContext).C)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-A", 200)

	rw, req = newTestRequest("GET", "/admin/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha admin-mw-Epsilon admin-B", 200)

	rw, req = newTestRequest("GET", "/api/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha api-mw-Delta api-C", 200)
}

func (s *MiddlewareTestSuite) TestDualLeaningLeftTree(c *C) {
	router := web.New(Context{})
	router.Get("/action", (*Context).A)
	admin := router.Subrouter(AdminContext{}, "/admin")
	admin.Get("/action", (*AdminContext).B)
	api := router.Subrouter(ApiContext{}, "/api")
	api.Middleware((*ApiContext).mwDelta)
	api.Get("/action", (*ApiContext).C)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-A", 200)

	rw, req = newTestRequest("GET", "/admin/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "admin-B", 200)

	rw, req = newTestRequest("GET", "/api/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "api-mw-Delta api-C", 200)
}

func (s *MiddlewareTestSuite) TestTicketsA(c *C) {
	router := web.New(Context{})
	admin := router.Subrouter(AdminContext{}, "/admin")
	admin.Middleware((*AdminContext).mwEpsilon)
	tickets := admin.Subrouter(TicketsContext{}, "/tickets")
	tickets.Get("/action", (*TicketsContext).D)

	rw, req := newTestRequest("GET", "/admin/tickets/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "admin-mw-Epsilon tickets-D", 200)
}

func (s *MiddlewareTestSuite) TestTicketsB(c *C) {
	router := web.New(Context{})
	admin := router.Subrouter(AdminContext{}, "/admin")
	tickets := admin.Subrouter(TicketsContext{}, "/tickets")
	tickets.Middleware((*TicketsContext).mwEta)
	tickets.Get("/action", (*TicketsContext).D)

	rw, req := newTestRequest("GET", "/admin/tickets/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "tickets-mw-Eta tickets-D", 200)
}

func (s *MiddlewareTestSuite) TestTicketsC(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha)
	admin := router.Subrouter(AdminContext{}, "/admin")
	tickets := admin.Subrouter(TicketsContext{}, "/tickets")
	tickets.Get("/action", (*TicketsContext).D)

	rw, req := newTestRequest("GET", "/admin/tickets/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha tickets-D", 200)
}

func (s *MiddlewareTestSuite) TestTicketsD(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha)
	admin := router.Subrouter(AdminContext{}, "/admin")
	tickets := admin.Subrouter(TicketsContext{}, "/tickets")
	tickets.Middleware((*TicketsContext).mwEta)
	tickets.Get("/action", (*TicketsContext).D)

	rw, req := newTestRequest("GET", "/admin/tickets/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha tickets-mw-Eta tickets-D", 200)
}

func (s *MiddlewareTestSuite) TestTicketsE(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha)
	router.Middleware((*Context).mwBeta)
	router.Middleware((*Context).mwGamma)
	admin := router.Subrouter(AdminContext{}, "/admin")
	admin.Middleware((*AdminContext).mwEpsilon)
	admin.Middleware((*AdminContext).mwZeta)
	tickets := admin.Subrouter(TicketsContext{}, "/tickets")
	tickets.Middleware((*TicketsContext).mwEta)
	tickets.Get("/action", (*TicketsContext).D)

	rw, req := newTestRequest("GET", "/admin/tickets/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-mw-Beta context-mw-Gamma admin-mw-Epsilon admin-mw-Zeta tickets-mw-Eta tickets-D", 200)
}

func (s *MiddlewareTestSuite) TestNoNext(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwNoNext)
	router.Get("/action", (*Context).A)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-NoNext", 200)
}

func (s *MiddlewareTestSuite) TestSameContext(c *C) {
	router := web.New(Context{})
	router.Middleware((*Context).mwAlpha).
		Middleware((*Context).mwBeta)
	admin := router.Subrouter(Context{}, "/admin")
	admin.Middleware((*Context).mwGamma)
	admin.Get("/foo", (*Context).A)

	rw, req := newTestRequest("GET", "/admin/foo")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Alpha context-mw-Beta context-mw-Gamma context-A", 200)
}

func (s *MiddlewareTestSuite) TestSameNamespace(c *C) {
	router := web.New(Context{})
	admin := router.Subrouter(AdminContext{}, "/")
	admin.Get("/action", (*AdminContext).B)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "admin-B", 200)
}

func (s *MiddlewareTestSuite) TestInterfaceMiddleware(c *C) {
	router := web.New(Context{})
	router.Middleware(mwGenricInterface)
	router.Get("/action", (*Context).A)

	rw, req := newTestRequest("GET", "/action")
	router.ServeHTTP(rw, req)
	assertResponse(c, rw, "context-mw-Interface context-A", 200)
}
<|endoftext|>


<commit_before>package her0ldbot

import (
	"bytes"
	"fmt"
	"github.com/gonium/her0ld"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
	"github.com/robfig/cron"
	"log"
	"net/smtp"
	"strconv"
	"strings"
	"text/template"
	"time"
)

const (
	EVENTBOT_INVALID_TIME_FORMAT = "Invalid time format, use e.g.  01.01.2016-16:00"
	EVENTBOT_TIME_FORMAT         = "02.01.2006-15:04"
	EVENTBOT_INVALID_COMMAND     = "invalid command - see !event help."
	EVENTBOT_PREFIX              = "!event"
	EVENTBOT_CMD_HELP            = "help"
	EVENTBOT_HELP_TEXT           = `* !event help - this text
* !event add <date> <description> - add an event
* !event list - list all upcoming events (with id)
* !event del <id> - remove the event with the given id
* !event today - show all events today`
	EVENTBOT_CMD_ADD                     = "add"
	EVENTBOT_CMD_ADD_SUCCESS             = "Recorded new event."
	EVENTBOT_CMD_LIST                    = "list"
	EVENTBOT_CMD_LIST_NONE_AVAILABLE     = "no upcoming events."
	EVENTBOT_CMD_TODAY                   = "today"
	EVENTBOT_CMD_TODAY_NONE_AVAILABLE    = "no events today."
	EVENTBOT_CMD_DELETE                  = "del"
	EVENTBOT_CMD_EVENT_UNKNOWN           = "Unknown event."
	EVENTBOT_CMD_DELETED_EVENT           = "Event %d deleted."
	EVENTBOT_CMD_MAILTEST                = "mailtest"
	EVENTBOT_MAILTEST_REPLY              = "Attempted to send test mail."
	EVENTBOT_MAILTEST_NOTAUTHORIZED      = "Only my owner can do this."
	EVENTBOT_CMD_MAILREMINDER            = "mailreminder"
	EVENTBOT_MAILREMINDER_REPLY          = "Attempted to send a reminder mail."
	EVENTBOT_MAILREMINDER_NOTAUTHORIZED  = "Only my owner can do this."
	EVENTBOT_MAILREMINDER_SEND_ERROR     = "Failed to send reminder email, check log."
	EVENTBOT_MAILREMINDER_NONE_AVAILABLE = "No events for today found, not sending."
)

/********************************** Event *************************************/
type Event struct {
	Id          int `sql:"AUTO_INCREMENT" gorm:"primary_key"`
	Starttime   time.Time
	Description string
}

func (e *Event) String() string {
	return fmt.Sprintf("%d - %s: %s", e.Id,
		e.Starttime.Format("Mon 2 Jan 2006, 15:04:05"),
		e.Description)
}

type EventList []Event

func (e EventList) String() string {
	var eventstrings []string
	for _, s := range e {
		eventstrings = append(eventstrings, s.String())
	}
	return strings.Join(eventstrings, "\n")
}

/********************************** MailSender ***********************************/
type MailSender struct {
	FromAddress string
	SMTPAuth    smtp.Auth
	SMTPServer  string
	SMTPPort    int
}

func NewMailSender(from, username, password, server string, port int) *MailSender {
	return &MailSender{
		FromAddress: from,
		SMTPAuth:    smtp.PlainAuth("", username, password, server),
		SMTPServer:  server,
		SMTPPort:    port,
	}
}

func (ms *MailSender) SendPlainTextMail(msg string, toadress string) {
	serveradress := ms.SMTPServer + ":" + strconv.Itoa(ms.SMTPPort)
	err := smtp.SendMail(serveradress,
		ms.SMTPAuth,
		ms.FromAddress,
		[]string{toadress},
		[]byte(msg))
	if err != nil {
		log.Println("ERROR: failed to send email, ", err.Error())
	}
}

/********************************** EventBot *************************************/
/* The EventBot maintains a list of events and attempts to
 * remind people. */
type EventBot struct {
	BotName               string
	TimeLocation          *time.Location
	NumMessagesHandled    int
	Db                    *gorm.DB
	MailSender            *MailSender
	OwnerNick             string
	OwnerEmailAddress     string
	RecipientAddress      string
	EventListMailTemplate string
	Cron                  *cron.Cron
}

func NewEventBot(name string, cfg her0ld.EventbotConfig, generalcfg her0ld.GeneralConfig) *EventBot {
	if cfg.DBFile == "" {
		log.Fatalf("Eventbot: No database file given - aborting")
	}
	db, err := gorm.Open("sqlite3", cfg.DBFile)
	// no sense in continuing w/o database
	if err != nil {
		log.Fatalf("Eventbot: Cannot open database - %s", err.Error())
	}
	db.AutoMigrate(&Event{})
	if cfg.Timezone == "" {
		log.Fatalf("Eventbot: No timezone configured - aborting.")
	}
	loc, err := time.LoadLocation(cfg.Timezone)
	if err != nil {
		log.Fatalf("Eventbot: Cannot load location based on timezone - %s", err.Error())
	}
	ms := NewMailSender(
		cfg.EmailSettings.FromAddress,
		cfg.EmailSettings.SMTPUsername,
		cfg.EmailSettings.SMTPPassword,
		cfg.EmailSettings.SMTPServer,
		cfg.EmailSettings.SMTPPort,
	)
	retval := &EventBot{
		BotName:               name,
		TimeLocation:          loc,
		NumMessagesHandled:    0,
		Db:                    db,
		MailSender:            ms,
		OwnerNick:             generalcfg.OwnerNick,
		OwnerEmailAddress:     generalcfg.OwnerEmailAddress,
		RecipientAddress:      cfg.EmailSettings.RecipientAddress,
		EventListMailTemplate: cfg.EmailSettings.EventListMailTemplate,
		Cron: cron.New(),
	}
	retval.Cron.AddFunc("0 0 1 * * *", func() {
		log.Println("Cron: Triggering event list email.")
		switch retval.SendEventList() {
		case NO_EVENT_TODAY:
			log.Printf("Cron: %s", EVENTBOT_MAILREMINDER_NONE_AVAILABLE)
		case SEND_ERROR:
			log.Printf("Cron: %s", EVENTBOT_MAILREMINDER_SEND_ERROR)
		case SEND_SUCCESS:
			log.Printf("Cron: %s", EVENTBOT_MAILREMINDER_REPLY)
		default:
			log.Println("Cron: Unknown return code from SendEventList - WTF?")
		}
	})
	retval.Cron.Start()
	return retval
}

type SendEventListStatus int

const (
	NO_EVENT_TODAY = iota
	SEND_SUCCESS   = iota
	SEND_ERROR     = iota
)

func (eb *EventBot) SendEventList() (status SendEventListStatus) {
	var todayEvents EventList
	todaytime := time.Now().Truncate(24 * time.Hour)
	tomorrow := time.Now().Add(24 * time.Hour).Truncate(24 * time.Hour)
	eb.Db.Where("starttime > ? and starttime < ?", todaytime, tomorrow).Find(&todayEvents)
	if len(todayEvents) == 0 {
		status = NO_EVENT_TODAY
	} else {
		var upcomingEvents EventList
		eb.Db.Where("starttime > ?", tomorrow).Find(&upcomingEvents)
		type TemplateData struct {
			From            string
			To              string
			Now             string
			Subject         string
			HighlightEvents string
			UpcomingEvents  string
		}
		emailTemplate := `From: {{.From}}
To: {{.To}}
Subject: {{.Subject}}
Date: {{.Now}}
Content-Type: text/plain; charset=UTF-8

` + eb.EventListMailTemplate
		var doc bytes.Buffer
		var err error
		// TODO: Load Template from configuration file
		context := &TemplateData{
			From:            eb.MailSender.FromAddress,
			To:              eb.RecipientAddress,
			Now:             time.Now().Format(time.RFC822),
			Subject:         "Reminder: Heute beim Chaos inKL.",
			HighlightEvents: todayEvents.String(),
			UpcomingEvents:  upcomingEvents.String(),
		}
		t := template.New("emailTemplate")
		if t, err = t.Parse(emailTemplate); err != nil {
			log.Print("error trying to parse mail template ", err)
			status = SEND_ERROR
		}
		if err = t.Execute(&doc, context); err != nil {
			log.Print("error trying to execute mail template ", err)
			status = SEND_ERROR
		} else {
			eb.MailSender.SendPlainTextMail(doc.String(), eb.RecipientAddress)
			status = SEND_SUCCESS
		}
	}
	return status
}

func (b *EventBot) isFromOwner(msg InboundMessage) bool {
	return msg.Nick == b.OwnerNick
}

func (b *EventBot) ProcessChannelEvent(msg InboundMessage) ([]OutboundMessage, error) {
	b.NumMessagesHandled += 1
	// look for commands
	//log.Printf("Processing message %s", msg.Message)
	if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_HELP)) {
		// help command
		answer := strings.Split(EVENTBOT_HELP_TEXT, "\n")
		return strings2reply(msg.Channel, answer), nil
	} else if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_ADD)) {
		// add new event command
		request := strings.Split(msg.Message, " ")
		// request[0|1] contain the add command - we need date and
		// description.
		if date, err := time.ParseInLocation(EVENTBOT_TIME_FORMAT,
			request[2], b.TimeLocation); err != nil {
			answer := []string{
				EVENTBOT_INVALID_TIME_FORMAT,
				fmt.Sprintf("Error was: %s", err.Error()),
			}
			return strings2reply(msg.Channel, answer), nil
		} else {
			description := strings.Join(request[3:], " ")
			newEvent := Event{
				Starttime:   date,
				Description: description,
			}
			b.Db.Create(&newEvent)
			answer := []string{EVENTBOT_CMD_ADD_SUCCESS}
			return strings2reply(msg.Channel, answer), nil
		}
	} else if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_LIST)) {
		var answer []string
		var events []Event
		b.Db.Where("starttime > ?", time.Now()).Find(&events)
		if len(events) == 0 {
			answer = append(answer, EVENTBOT_CMD_LIST_NONE_AVAILABLE)
		} else {
			for _, event := range events {
				answer = append(answer, event.String())
			}
		}
		return strings2reply(msg.Channel, answer), nil
	} else if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_TODAY)) {
		var answer []string
		var events []Event
		starttime := time.Now().Truncate(24 * time.Hour)
		endtime := time.Now().Add(24 * time.Hour).Truncate(24 * time.Hour)
		b.Db.Where("starttime > ? and starttime < ?", starttime, endtime).Find(&events)
		if len(events) == 0 {
			answer = append(answer, EVENTBOT_CMD_TODAY_NONE_AVAILABLE)
		} else {
			for _, event := range events {
				answer = append(answer, event.String())
			}
		}
		return strings2reply(msg.Channel, answer), nil
	} else if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_DELETE)) {
		request := strings.Split(msg.Message, " ")
		var answer []string
		var events []Event
		b.Db.Where("ID == ?", request[2]).Find(&events)
		if len(events) == 0 {
			answer = append(answer, EVENTBOT_CMD_EVENT_UNKNOWN)
		} else {
			for _, event := range events {
				b.Db.Delete(&event)
				answer = append(answer, fmt.Sprintf(EVENTBOT_CMD_DELETED_EVENT,
					event.Id))
			}
		}
		return strings2reply(msg.Channel, answer), nil
	} else if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_MAILTEST)) {
		answer := []string{EVENTBOT_MAILTEST_NOTAUTHORIZED}
		if b.isFromOwner(msg) {
			mailtext := "To: " + b.OwnerEmailAddress + "\r\n" +
				"From: " + b.MailSender.FromAddress + "\r\n" +
				"Date: " + (time.Now().Format(time.RFC822)) + "\r\n" +
				"Subject: her0ld mail test\r\n" +
				"\r\n" +
				"Testing mail. If you can read this everything should be working.\r\n"
			go b.MailSender.SendPlainTextMail(mailtext, b.OwnerEmailAddress)
			answer = []string{EVENTBOT_MAILTEST_REPLY}
		}
		return strings2reply(msg.Channel, answer), nil
	} else if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_MAILREMINDER)) {
		answer := []string{EVENTBOT_MAILREMINDER_NOTAUTHORIZED}
		if b.isFromOwner(msg) {
			switch b.SendEventList() {
			case NO_EVENT_TODAY:
				answer = []string{EVENTBOT_MAILREMINDER_NONE_AVAILABLE}
			case SEND_ERROR:
				answer = []string{EVENTBOT_MAILREMINDER_SEND_ERROR}
			case SEND_SUCCESS:
				answer = []string{EVENTBOT_MAILREMINDER_REPLY}
			default:
				log.Println("Unknown return code from SendEventList - WTF?")
			}
		}
		return strings2reply(msg.Channel, answer), nil
	} else if strings.HasPrefix(msg.Message, EVENTBOT_PREFIX) {
		// invalid command (!event foo)
		answer := []string{EVENTBOT_INVALID_COMMAND}
		return strings2reply(msg.Channel, answer), nil
	} else { // something else
		return nil, nil
	}
}

func (b *EventBot) ProcessQueryEvent(msg InboundMessage) ([]OutboundMessage, error) {
	b.NumMessagesHandled += 1
	reply := make([]OutboundMessage, 1)
	reply[0] = OutboundMessage{Destination: msg.Channel, Message: "Not implemented"}
	return reply, nil
}

func (b *EventBot) GetName() string {
	return b.BotName
}

func (b *EventBot) GetHelpLines() []string {
	return strings.Split(EVENTBOT_HELP_TEXT, "\n")
}
<commit_msg>implemented ordering of events<commit_after>package her0ldbot

import (
	"bytes"
	"fmt"
	"github.com/gonium/her0ld"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
	"github.com/robfig/cron"
	"log"
	"net/smtp"
	"sort"
	"strconv"
	"strings"
	"text/template"
	"time"
)

const (
	EVENTBOT_INVALID_TIME_FORMAT = "Invalid time format, use e.g.  01.01.2016-16:00"
	EVENTBOT_TIME_FORMAT         = "02.01.2006-15:04"
	EVENTBOT_INVALID_COMMAND     = "invalid command - see !event help."
	EVENTBOT_PREFIX              = "!event"
	EVENTBOT_CMD_HELP            = "help"
	EVENTBOT_HELP_TEXT           = `* !event help - this text
* !event add <date> <description> - add an event
* !event list - list all upcoming events (with id)
* !event del <id> - remove the event with the given id
* !event today - show all events today`
	EVENTBOT_CMD_ADD                     = "add"
	EVENTBOT_CMD_ADD_SUCCESS             = "Recorded new event."
	EVENTBOT_CMD_LIST                    = "list"
	EVENTBOT_CMD_LIST_NONE_AVAILABLE     = "no upcoming events."
	EVENTBOT_CMD_TODAY                   = "today"
	EVENTBOT_CMD_TODAY_NONE_AVAILABLE    = "no events today."
	EVENTBOT_CMD_DELETE                  = "del"
	EVENTBOT_CMD_EVENT_UNKNOWN           = "Unknown event."
	EVENTBOT_CMD_DELETED_EVENT           = "Event %d deleted."
	EVENTBOT_CMD_MAILTEST                = "mailtest"
	EVENTBOT_MAILTEST_REPLY              = "Attempted to send test mail."
	EVENTBOT_MAILTEST_NOTAUTHORIZED      = "Only my owner can do this."
	EVENTBOT_CMD_MAILREMINDER            = "mailreminder"
	EVENTBOT_MAILREMINDER_REPLY          = "Attempted to send a reminder mail."
	EVENTBOT_MAILREMINDER_NOTAUTHORIZED  = "Only my owner can do this."
	EVENTBOT_MAILREMINDER_SEND_ERROR     = "Failed to send reminder email, check log."
	EVENTBOT_MAILREMINDER_NONE_AVAILABLE = "No events for today found, not sending."
)

/********************************** Event *************************************/
type Event struct {
	Id          int `sql:"AUTO_INCREMENT" gorm:"primary_key"`
	Starttime   time.Time
	Description string
}

func (e *Event) String() string {
	return fmt.Sprintf("(%d) %s - %s", e.Id,
		e.Starttime.Format("Mon 2 Jan 2006, 15:04"),
		e.Description)
}

type EventList []Event

func (e EventList) String() string {
	var eventstrings []string
	for _, s := range e {
		eventstrings = append(eventstrings, s.String())
	}
	return strings.Join(eventstrings, "\n")
}

// Implementation of the sort.Interface for EventList, see
// https://gobyexample.com/sorting-by-functions
type ByDate EventList

func (el ByDate) Len() int {
	return len(el)
}
func (el ByDate) Swap(i, j int) {
	el[i], el[j] = el[j], el[i]
}
func (el ByDate) Less(i, j int) bool {
	return el[i].Starttime.Before(el[j].Starttime)
}

/********************************** MailSender ***********************************/
type MailSender struct {
	FromAddress string
	SMTPAuth    smtp.Auth
	SMTPServer  string
	SMTPPort    int
}

func NewMailSender(from, username, password, server string, port int) *MailSender {
	return &MailSender{
		FromAddress: from,
		SMTPAuth:    smtp.PlainAuth("", username, password, server),
		SMTPServer:  server,
		SMTPPort:    port,
	}
}

func (ms *MailSender) SendPlainTextMail(msg string, toadress string) {
	serveradress := ms.SMTPServer + ":" + strconv.Itoa(ms.SMTPPort)
	err := smtp.SendMail(serveradress,
		ms.SMTPAuth,
		ms.FromAddress,
		[]string{toadress},
		[]byte(msg))
	if err != nil {
		log.Println("ERROR: failed to send email, ", err.Error())
	}
}

/********************************** EventBot *************************************/
/* The EventBot maintains a list of events and attempts to
 * remind people. */
type EventBot struct {
	BotName               string
	TimeLocation          *time.Location
	NumMessagesHandled    int
	Db                    *gorm.DB
	MailSender            *MailSender
	OwnerNick             string
	OwnerEmailAddress     string
	RecipientAddress      string
	EventListMailTemplate string
	Cron                  *cron.Cron
}

func NewEventBot(name string, cfg her0ld.EventbotConfig, generalcfg her0ld.GeneralConfig) *EventBot {
	if cfg.DBFile == "" {
		log.Fatalf("Eventbot: No database file given - aborting")
	}
	db, err := gorm.Open("sqlite3", cfg.DBFile)
	// no sense in continuing w/o database
	if err != nil {
		log.Fatalf("Eventbot: Cannot open database - %s", err.Error())
	}
	db.AutoMigrate(&Event{})
	if cfg.Timezone == "" {
		log.Fatalf("Eventbot: No timezone configured - aborting.")
	}
	loc, err := time.LoadLocation(cfg.Timezone)
	if err != nil {
		log.Fatalf("Eventbot: Cannot load location based on timezone - %s", err.Error())
	}
	ms := NewMailSender(
		cfg.EmailSettings.FromAddress,
		cfg.EmailSettings.SMTPUsername,
		cfg.EmailSettings.SMTPPassword,
		cfg.EmailSettings.SMTPServer,
		cfg.EmailSettings.SMTPPort,
	)
	retval := &EventBot{
		BotName:               name,
		TimeLocation:          loc,
		NumMessagesHandled:    0,
		Db:                    db,
		MailSender:            ms,
		OwnerNick:             generalcfg.OwnerNick,
		OwnerEmailAddress:     generalcfg.OwnerEmailAddress,
		RecipientAddress:      cfg.EmailSettings.RecipientAddress,
		EventListMailTemplate: cfg.EmailSettings.EventListMailTemplate,
		Cron: cron.New(),
	}
	retval.Cron.AddFunc("0 0 1 * * *", func() {
		log.Println("Cron: Triggering event list email.")
		switch retval.SendEventList() {
		case NO_EVENT_TODAY:
			log.Printf("Cron: %s", EVENTBOT_MAILREMINDER_NONE_AVAILABLE)
		case SEND_ERROR:
			log.Printf("Cron: %s", EVENTBOT_MAILREMINDER_SEND_ERROR)
		case SEND_SUCCESS:
			log.Printf("Cron: %s", EVENTBOT_MAILREMINDER_REPLY)
		default:
			log.Println("Cron: Unknown return code from SendEventList - WTF?")
		}
	})
	retval.Cron.Start()
	return retval
}

type SendEventListStatus int

const (
	NO_EVENT_TODAY = iota
	SEND_SUCCESS   = iota
	SEND_ERROR     = iota
)

func (eb *EventBot) SendEventList() (status SendEventListStatus) {
	var todayEvents EventList
	todaytime := time.Now().Truncate(24 * time.Hour)
	tomorrow := time.Now().Add(24 * time.Hour).Truncate(24 * time.Hour)
	eb.Db.Where("starttime > ? and starttime < ?", todaytime, tomorrow).Find(&todayEvents)
	if len(todayEvents) == 0 {
		status = NO_EVENT_TODAY
	} else {
		var upcomingEvents EventList
		eb.Db.Where("starttime > ?", tomorrow).Find(&upcomingEvents)
		sort.Sort(ByDate(upcomingEvents))
		type TemplateData struct {
			From            string
			To              string
			Now             string
			Subject         string
			HighlightEvents string
			UpcomingEvents  string
		}
		emailTemplate := `From: {{.From}}
To: {{.To}}
Subject: {{.Subject}}
Date: {{.Now}}
Content-Type: text/plain; charset=UTF-8

` + eb.EventListMailTemplate
		var doc bytes.Buffer
		var err error
		// TODO: Load Template from configuration file
		context := &TemplateData{
			From:            eb.MailSender.FromAddress,
			To:              eb.RecipientAddress,
			Now:             time.Now().Format(time.RFC822),
			Subject:         "Reminder: Heute beim Chaos inKL.",
			HighlightEvents: todayEvents.String(),
			UpcomingEvents:  upcomingEvents.String(),
		}
		t := template.New("emailTemplate")
		if t, err = t.Parse(emailTemplate); err != nil {
			log.Print("error trying to parse mail template ", err)
			status = SEND_ERROR
		}
		if err = t.Execute(&doc, context); err != nil {
			log.Print("error trying to execute mail template ", err)
			status = SEND_ERROR
		} else {
			eb.MailSender.SendPlainTextMail(doc.String(), eb.RecipientAddress)
			status = SEND_SUCCESS
		}
	}
	return status
}

func (b *EventBot) isFromOwner(msg InboundMessage) bool {
	return msg.Nick == b.OwnerNick
}

func (b *EventBot) ProcessChannelEvent(msg InboundMessage) ([]OutboundMessage, error) {
	b.NumMessagesHandled += 1
	// look for commands
	//log.Printf("Processing message %s", msg.Message)
	if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_HELP)) {
		// help command
		answer := strings.Split(EVENTBOT_HELP_TEXT, "\n")
		return strings2reply(msg.Channel, answer), nil
	} else if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_ADD)) {
		// add new event command
		request := strings.Split(msg.Message, " ")
		// request[0|1] contain the add command - we need date and
		// description.
		if date, err := time.ParseInLocation(EVENTBOT_TIME_FORMAT,
			request[2], b.TimeLocation); err != nil {
			answer := []string{
				EVENTBOT_INVALID_TIME_FORMAT,
				fmt.Sprintf("Error was: %s", err.Error()),
			}
			return strings2reply(msg.Channel, answer), nil
		} else {
			description := strings.Join(request[3:], " ")
			newEvent := Event{
				Starttime:   date,
				Description: description,
			}
			b.Db.Create(&newEvent)
			answer := []string{EVENTBOT_CMD_ADD_SUCCESS}
			return strings2reply(msg.Channel, answer), nil
		}
	} else if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_LIST)) {
		var answer []string
		var events []Event
		b.Db.Where("starttime > ?", time.Now()).Find(&events)
		if len(events) == 0 {
			answer = append(answer, EVENTBOT_CMD_LIST_NONE_AVAILABLE)
		} else {
			sort.Sort(ByDate(events))
			for _, event := range events {
				answer = append(answer, event.String())
			}
		}
		return strings2reply(msg.Channel, answer), nil
	} else if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_TODAY)) {
		var answer []string
		var events []Event
		starttime := time.Now().Truncate(24 * time.Hour)
		endtime := time.Now().Add(24 * time.Hour).Truncate(24 * time.Hour)
		b.Db.Where("starttime > ? and starttime < ?", starttime, endtime).Find(&events)
		if len(events) == 0 {
			answer = append(answer, EVENTBOT_CMD_TODAY_NONE_AVAILABLE)
		} else {
			for _, event := range events {
				answer = append(answer, event.String())
			}
		}
		return strings2reply(msg.Channel, answer), nil
	} else if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_DELETE)) {
		request := strings.Split(msg.Message, " ")
		var answer []string
		var events []Event
		b.Db.Where("ID == ?", request[2]).Find(&events)
		if len(events) == 0 {
			answer = append(answer, EVENTBOT_CMD_EVENT_UNKNOWN)
		} else {
			for _, event := range events {
				b.Db.Delete(&event)
				answer = append(answer, fmt.Sprintf(EVENTBOT_CMD_DELETED_EVENT,
					event.Id))
			}
		}
		return strings2reply(msg.Channel, answer), nil
	} else if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_MAILTEST)) {
		answer := []string{EVENTBOT_MAILTEST_NOTAUTHORIZED}
		if b.isFromOwner(msg) {
			mailtext := "To: " + b.OwnerEmailAddress + "\r\n" +
				"From: " + b.MailSender.FromAddress + "\r\n" +
				"Date: " + (time.Now().Format(time.RFC822)) + "\r\n" +
				"Subject: her0ld mail test\r\n" +
				"\r\n" +
				"Testing mail. If you can read this everything should be working.\r\n"
			go b.MailSender.SendPlainTextMail(mailtext, b.OwnerEmailAddress)
			answer = []string{EVENTBOT_MAILTEST_REPLY}
		}
		return strings2reply(msg.Channel, answer), nil
	} else if strings.HasPrefix(msg.Message, fmt.Sprintf("%s %s",
		EVENTBOT_PREFIX, EVENTBOT_CMD_MAILREMINDER)) {
		answer := []string{EVENTBOT_MAILREMINDER_NOTAUTHORIZED}
		if b.isFromOwner(msg) {
			switch b.SendEventList() {
			case NO_EVENT_TODAY:
				answer = []string{EVENTBOT_MAILREMINDER_NONE_AVAILABLE}
			case SEND_ERROR:
				answer = []string{EVENTBOT_MAILREMINDER_SEND_ERROR}
			case SEND_SUCCESS:
				answer = []string{EVENTBOT_MAILREMINDER_REPLY}
			default:
				log.Println("Unknown return code from SendEventList - WTF?")
			}
		}
		return strings2reply(msg.Channel, answer), nil
	} else if strings.HasPrefix(msg.Message, EVENTBOT_PREFIX) {
		// invalid command (!event foo)
		answer := []string{EVENTBOT_INVALID_COMMAND}
		return strings2reply(msg.Channel, answer), nil
	} else { // something else
		return nil, nil
	}
}

func (b *EventBot) ProcessQueryEvent(msg InboundMessage) ([]OutboundMessage, error) {
	b.NumMessagesHandled += 1
	reply := make([]OutboundMessage, 1)
	reply[0] = OutboundMessage{Destination: msg.Channel, Message: "Not implemented"}
	return reply, nil
}

func (b *EventBot) GetName() string {
	return b.BotName
}

func (b *EventBot) GetHelpLines() []string {
	return strings.Split(EVENTBOT_HELP_TEXT, "\n")
}
<|endoftext|>


<commit_before>package migrate

import (
	"fmt"
	"log"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/codegangsta/cli"
	"github.com/marcosnils/cmt/cmd"
	"github.com/marcosnils/cmt/iptables"
	"github.com/marcosnils/cmt/validate"
)

var Command = cli.Command{
	Name:  "migrate",
	Usage: "Migrate running container",
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "src",
			Usage: "Source host where the container is running",
		},
		cli.StringFlag{
			Name:  "dst",
			Usage: "Target host to migrate the container",
		},
		cli.BoolFlag{
			Name:  "pre-dump",
			Usage: "Perform a pre-dump to minimize downtime",
		},
		cli.BoolFlag{
			Name:  "force",
			Usage: "Doesn't fail of validations related to different versions of executable binaries and cpu differences",
		},
		cli.StringFlag{
			Name:  "hook-pre-restore",
			Usage: "Command to run right before restoring process",
		},
		cli.StringFlag{
			Name:  "hook-post-restore",
			Usage: "Command to run right after a successful process restoration",
		},
		cli.StringFlag{
			Name:  "hook-failed-restore",
			Usage: "Command to run right after a failed process restoration",
		},
	},
	Action: func(c *cli.Context) {
		srcUrl := validate.ParseURL(c.String("src"))
		dstUrl := validate.ParseURL(c.String("dst"))

		log.Println("Performing validations")
		src, dst := validate.Validate(srcUrl, dstUrl, c.Bool("force"))

		log.Println("Preparing everything to do a checkpoint")
		containerId := getContainerId(srcUrl.Path)
		var imagesPath string
		var restoreCmd cmd.Cmd
		var migrateStart time.Time
		var downtime time.Duration

		if c.Bool("pre-dump") {
			// Process pre-dump
			predumpPath := fmt.Sprintf("%s/images/0", srcUrl.Path)
			prepareDir(src, predumpPath)

			checkpoint(src, containerId, predumpPath, true)

			srcTarFile := fmt.Sprintf("%s/predump.tar.gz", srcUrl.Path)
			prepareTar(src, srcTarFile, predumpPath)

			prepareDir(dst, fmt.Sprintf("%s/images/0", dstUrl.Path))

			log.Println("Copying predump image to dst")
			err := cmd.Scp(src.URL(srcTarFile), dst.URL(fmt.Sprintf("%s/images/0", dstUrl.Path)))
			if err != nil {
				log.Fatal("Error copying predump image files to dst", err)
			}

			dstTarFile := fmt.Sprintf("%s/images/0/predump.tar.gz", dstUrl.Path)
			unpackTar(dst, dstTarFile, fmt.Sprintf("%s/images/0", dstUrl.Path))

			// Process final image
			migrateStart = time.Now()

			imagesPath = fmt.Sprintf("%s/images/1", srcUrl.Path)
			log.Println("Performing the checkpoint")
			_, _, err = src.Run("sudo", "runc", "--id", containerId, "checkpoint", "--image-path", imagesPath, "--prev-images-dir", "../0", "--track-mem")
			if err != nil {
				log.Fatal("Error performing checkpoint:", err)
			}

			srcTarFile = fmt.Sprintf("%s/dump.tar.gz", srcUrl.Path)
			prepareTar(src, srcTarFile, imagesPath)
			prepareDir(dst, fmt.Sprintf("%s/images/1", dstUrl.Path))

			log.Println("Copying predump image to dst")
			err = cmd.Scp(src.URL(srcTarFile), dst.URL(fmt.Sprintf("%s/images/1", dstUrl.Path)))
			if err != nil {
				log.Fatal("Error copying predump image files to dst", err)
			}

			dstTarFile = fmt.Sprintf("%s/images/1/dump.tar.gz", dstUrl.Path)
			unpackTar(dst, dstTarFile, fmt.Sprintf("%s/images/1", dstUrl.Path))

			log.Println("Performing the restore")
			TriggerHook(c.String("hook-pre-restore"))
			configFilePath := fmt.Sprintf("%s/config.json", dstUrl.Path)
			runtimeFilePath := fmt.Sprintf("%s/runtime.json", dstUrl.Path)
			dstImagesPath := fmt.Sprintf("%s/images/1", dstUrl.Path)

			restoreCmd, err = dst.Start("sudo", "runc", "--id", containerId, "restore", "--tcp-established", "--image-path", dstImagesPath, "--config-file", configFilePath, "--runtime-file", runtimeFilePath)
			if err != nil {
				log.Fatal("Error performing restore:", err)
			}
		} else {
			imagesPath = fmt.Sprintf("%s/images", srcUrl.Path)
			prepareDir(src, imagesPath)

			migrateStart = time.Now()
			iptablesBefore, ipErr := getIPTables(src)

			if ipErr != nil {
				log.Fatal("Error capturing iptables rules. ", ipErr)
			}
			checkpoint(src, containerId, imagesPath, false)
			iptablesAfter, ipErr2 := getIPTables(src)

			if ipErr2 != nil {
				log.Fatal("Error capturing iptables rules. ", ipErr2)
			}

			iptablesRules := iptables.Diff(iptablesAfter, iptablesBefore)

			srcTarFile := fmt.Sprintf("%s/dump.tar.gz", srcUrl.Path)
			prepareTar(src, srcTarFile, imagesPath)

			prepareDir(dst, fmt.Sprintf("%s/images", dstUrl.Path))

			log.Println("Copying checkpoint image to dst")
			err := cmd.Scp(src.URL(srcTarFile), dst.URL(fmt.Sprintf("%s/images", dstUrl.Path)))
			if err != nil {
				log.Fatal("Error copying image files to dst", err)
			}

			dstTarFile := fmt.Sprintf("%s/images/dump.tar.gz", dstUrl.Path)
			unpackTar(dst, dstTarFile, fmt.Sprintf("%s/images", dstUrl.Path))

			log.Println("Performing the restore")
			// first thing to do, apply the iptables rules
			applyErr := applyIPTablesRules(dst, iptablesRules)
			if applyErr != nil {
				log.Fatal("Error applying IPTables rules. ", applyErr)
			}
			TriggerHook(c.String("hook-pre-restore"))
			configFilePath := fmt.Sprintf("%s/config.json", dstUrl.Path)
			runtimeFilePath := fmt.Sprintf("%s/runtime.json", dstUrl.Path)
			dstImagesPath := fmt.Sprintf("%s/images", dstUrl.Path)
			restoreCmd, err = dst.Start("sudo", "runc", "--id", containerId, "restore", "--tcp-established", "--image-path", dstImagesPath, "--config-file", configFilePath, "--runtime-file", runtimeFilePath)
			if err != nil {
				log.Fatal("Error performing restore:", err)
			}

		}

		var restoreSucceed bool
		var restoreError error
		var wg sync.WaitGroup
		wg.Add(1)

		go func() {
			restoreError = restoreCmd.Wait()
			wg.Done()
		}()

		go func() {
			log.Println("Waiting for container to start...")
			// We make a fast check so we don't wait for the first ticker internal
			if isRunning(containerId, dst) {
				restoreSucceed = true
				wg.Done()
				return
			}
			ticker := time.NewTicker(200 * time.Millisecond)
			go func() {
				for _ = range ticker.C {
					if isRunning(containerId, dst) {
						restoreSucceed = true
						break
					}

				}
				ticker.Stop()
				wg.Done()
			}()
		}()

		wg.Wait()

		downtime = time.Since(migrateStart)

		if restoreSucceed {
			log.Printf("Restore finished successfully, total downtime: %dms", downtime/time.Millisecond)
			TriggerHook(c.String("hook-post-restore"))
		} else {
			log.Println("Error performing restore:", restoreError)
			TriggerHook(c.String("hook-failed-restore"))
		}

	},
}

func applyIPTablesRules(host cmd.Cmd, rules []string) error {
	for _, rule := range rules {
		args := []string{"iptables"}
		args = append(args, strings.Fields(rule)...)
		_, _, err := host.Run("sudo", args...)
		if err != nil {
			return err
		}
	}
	return nil
}

func getIPTables(host cmd.Cmd) (string, error) {
	rules, _, err := host.Run("sudo", "iptables-save")
	if err != nil {
		return "", err
	}
	return rules, nil
}

func isRunning(containerId string, dstCmd cmd.Cmd) bool {
	_, _, err := dstCmd.Run("stat", fmt.Sprintf("/var/run/opencontainer/containers/%s", containerId))
	if err != nil {
		return false
	}

	return true
}

func unpackTar(cmd cmd.Cmd, tarFile, workDir string) {
	log.Println("Preparing image at destination host")
	_, _, err := cmd.Run("sudo", "tar", "-C", workDir, "-xvzf", tarFile)
	if err != nil {
		log.Fatal("Error uncompressing image in destination:", err)
	}
}

func prepareTar(cmd cmd.Cmd, tarFile, workDir string) {
	_, _, err := cmd.Run("sudo", "tar", "-czf", tarFile, "-C", fmt.Sprintf("%s/", workDir), ".")
	if err != nil {
		log.Fatal("Error compressing image in source:", err)
	}
}

func checkpoint(cmd cmd.Cmd, containerId, imagesPath string, predump bool) {
	log.Printf("Performing the checkpoint predump = %t\n", predump)
	args := []string{"runc", "--id", containerId, "checkpoint", "--tcp-established", "--track-mem", "--image-path", imagesPath}
	if predump {
		args = append(args, "--pre-dump")
	}
	_, _, err := cmd.Run("sudo", args...)
	if err != nil {
		log.Fatal("Error performing checkpoint:", err)
	}
}

func prepareDir(cmd cmd.Cmd, path string) {
	_, _, err := cmd.Run("mkdir", "-p", path)
	if err != nil {
		log.Fatal("Error preparing pre-dump dir:", err)
	}
}

func getContainerId(path string) string {
	_, id := filepath.Split(path)
	return id
}

func TriggerHook(command string) error {
	if command == "" {
		return nil
	}

	log.Printf("Running hook: %s\n", command)

	args := strings.Fields(command)
	c := cmd.NewLocal()
	stdout, stderr, err := c.Run(args[0], args[1:]...)

	log.Println(stdout, stderr)

	return err
}
<commit_msg>Remove iptables rules from source host<commit_after>package migrate

import (
	"fmt"
	"log"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/codegangsta/cli"
	"github.com/marcosnils/cmt/cmd"
	"github.com/marcosnils/cmt/iptables"
	"github.com/marcosnils/cmt/validate"
)

var Command = cli.Command{
	Name:  "migrate",
	Usage: "Migrate running container",
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "src",
			Usage: "Source host where the container is running",
		},
		cli.StringFlag{
			Name:  "dst",
			Usage: "Target host to migrate the container",
		},
		cli.BoolFlag{
			Name:  "pre-dump",
			Usage: "Perform a pre-dump to minimize downtime",
		},
		cli.BoolFlag{
			Name:  "force",
			Usage: "Doesn't fail of validations related to different versions of executable binaries and cpu differences",
		},
		cli.StringFlag{
			Name:  "hook-pre-restore",
			Usage: "Command to run right before restoring process",
		},
		cli.StringFlag{
			Name:  "hook-post-restore",
			Usage: "Command to run right after a successful process restoration",
		},
		cli.StringFlag{
			Name:  "hook-failed-restore",
			Usage: "Command to run right after a failed process restoration",
		},
	},
	Action: func(c *cli.Context) {
		srcUrl := validate.ParseURL(c.String("src"))
		dstUrl := validate.ParseURL(c.String("dst"))

		log.Println("Performing validations")
		src, dst := validate.Validate(srcUrl, dstUrl, c.Bool("force"))

		log.Println("Preparing everything to do a checkpoint")
		containerId := getContainerId(srcUrl.Path)
		var imagesPath string
		var restoreCmd cmd.Cmd
		var migrateStart time.Time
		var downtime time.Duration

		if c.Bool("pre-dump") {
			// Process pre-dump
			predumpPath := fmt.Sprintf("%s/images/0", srcUrl.Path)
			prepareDir(src, predumpPath)

			checkpoint(src, containerId, predumpPath, true)

			srcTarFile := fmt.Sprintf("%s/predump.tar.gz", srcUrl.Path)
			prepareTar(src, srcTarFile, predumpPath)

			prepareDir(dst, fmt.Sprintf("%s/images/0", dstUrl.Path))

			log.Println("Copying predump image to dst")
			err := cmd.Scp(src.URL(srcTarFile), dst.URL(fmt.Sprintf("%s/images/0", dstUrl.Path)))
			if err != nil {
				log.Fatal("Error copying predump image files to dst", err)
			}

			dstTarFile := fmt.Sprintf("%s/images/0/predump.tar.gz", dstUrl.Path)
			unpackTar(dst, dstTarFile, fmt.Sprintf("%s/images/0", dstUrl.Path))

			// Process final image
			migrateStart = time.Now()

			imagesPath = fmt.Sprintf("%s/images/1", srcUrl.Path)
			log.Println("Performing the checkpoint")
			_, _, err = src.Run("sudo", "runc", "--id", containerId, "checkpoint", "--image-path", imagesPath, "--prev-images-dir", "../0", "--track-mem")
			if err != nil {
				log.Fatal("Error performing checkpoint:", err)
			}

			srcTarFile = fmt.Sprintf("%s/dump.tar.gz", srcUrl.Path)
			prepareTar(src, srcTarFile, imagesPath)
			prepareDir(dst, fmt.Sprintf("%s/images/1", dstUrl.Path))

			log.Println("Copying predump image to dst")
			err = cmd.Scp(src.URL(srcTarFile), dst.URL(fmt.Sprintf("%s/images/1", dstUrl.Path)))
			if err != nil {
				log.Fatal("Error copying predump image files to dst", err)
			}

			dstTarFile = fmt.Sprintf("%s/images/1/dump.tar.gz", dstUrl.Path)
			unpackTar(dst, dstTarFile, fmt.Sprintf("%s/images/1", dstUrl.Path))

			log.Println("Performing the restore")
			TriggerHook(c.String("hook-pre-restore"))
			configFilePath := fmt.Sprintf("%s/config.json", dstUrl.Path)
			runtimeFilePath := fmt.Sprintf("%s/runtime.json", dstUrl.Path)
			dstImagesPath := fmt.Sprintf("%s/images/1", dstUrl.Path)

			restoreCmd, err = dst.Start("sudo", "runc", "--id", containerId, "restore", "--tcp-established", "--image-path", dstImagesPath, "--config-file", configFilePath, "--runtime-file", runtimeFilePath)
			if err != nil {
				log.Fatal("Error performing restore:", err)
			}
		} else {
			imagesPath = fmt.Sprintf("%s/images", srcUrl.Path)
			prepareDir(src, imagesPath)

			migrateStart = time.Now()
			iptablesBefore, ipErr := getIPTables(src)

			if ipErr != nil {
				log.Fatal("Error capturing iptables rules. ", ipErr)
			}
			checkpoint(src, containerId, imagesPath, false)
			iptablesAfter, ipErr2 := getIPTables(src)

			if ipErr2 != nil {
				log.Fatal("Error capturing iptables rules. ", ipErr2)
			}

			iptablesRules := iptables.Diff(iptablesAfter, iptablesBefore)

			srcTarFile := fmt.Sprintf("%s/dump.tar.gz", srcUrl.Path)
			prepareTar(src, srcTarFile, imagesPath)

			prepareDir(dst, fmt.Sprintf("%s/images", dstUrl.Path))

			log.Println("Copying checkpoint image to dst")
			err := cmd.Scp(src.URL(srcTarFile), dst.URL(fmt.Sprintf("%s/images", dstUrl.Path)))
			if err != nil {
				log.Fatal("Error copying image files to dst", err)
			}

			dstTarFile := fmt.Sprintf("%s/images/dump.tar.gz", dstUrl.Path)
			unpackTar(dst, dstTarFile, fmt.Sprintf("%s/images", dstUrl.Path))

			log.Println("Performing the restore")
			// first thing to do, apply the iptables rules
			applyErr := applyIPTablesRules(dst, iptablesRules)
			if applyErr != nil {
				log.Fatal("Error applying IPTables rules. ", applyErr)
			}
			TriggerHook(c.String("hook-pre-restore"))
			configFilePath := fmt.Sprintf("%s/config.json", dstUrl.Path)
			runtimeFilePath := fmt.Sprintf("%s/runtime.json", dstUrl.Path)
			dstImagesPath := fmt.Sprintf("%s/images", dstUrl.Path)
			restoreCmd, err = dst.Start("sudo", "runc", "--id", containerId, "restore", "--tcp-established", "--image-path", dstImagesPath, "--config-file", configFilePath, "--runtime-file", runtimeFilePath)
			if err != nil {
				log.Fatal("Error performing restore:", err)
			}

			// after the restore, we remove iptable rules from source host
			removeErr := removeIPTablesRules(src, iptablesRules)
			if removeErr != nil {
				log.Fatal("Error removing IPTables rules. ", removeErr)
			}
		}

		var restoreSucceed bool
		var restoreError error
		var wg sync.WaitGroup
		wg.Add(1)

		go func() {
			restoreError = restoreCmd.Wait()
			wg.Done()
		}()

		go func() {
			log.Println("Waiting for container to start...")
			// We make a fast check so we don't wait for the first ticker internal
			if isRunning(containerId, dst) {
				restoreSucceed = true
				wg.Done()
				return
			}
			ticker := time.NewTicker(200 * time.Millisecond)
			go func() {
				for _ = range ticker.C {
					if isRunning(containerId, dst) {
						restoreSucceed = true
						break
					}

				}
				ticker.Stop()
				wg.Done()
			}()
		}()

		wg.Wait()

		downtime = time.Since(migrateStart)

		if restoreSucceed {
			log.Printf("Restore finished successfully, total downtime: %dms", downtime/time.Millisecond)
			TriggerHook(c.String("hook-post-restore"))
		} else {
			log.Println("Error performing restore:", restoreError)
			TriggerHook(c.String("hook-failed-restore"))
		}

	},
}

func applyIPTablesRules(host cmd.Cmd, rules []string) error {
	for _, rule := range rules {
		args := []string{"iptables"}
		args = append(args, strings.Fields(rule)...)
		_, _, err := host.Run("sudo", args...)
		if err != nil {
			return err
		}
	}
	return nil
}

func removeIPTablesRules(host cmd.Cmd, rules []string) error {
	for _, rule := range rules {
		args := []string{"iptables"}
		args = append(args, strings.Fields(rule)...)
		args[0] = "-D"
		_, _, err := host.Run("sudo", args...)
		if err != nil {
			return err
		}
	}
	return nil
}

func getIPTables(host cmd.Cmd) (string, error) {
	rules, _, err := host.Run("sudo", "iptables-save")
	if err != nil {
		return "", err
	}
	return rules, nil
}

func isRunning(containerId string, dstCmd cmd.Cmd) bool {
	_, _, err := dstCmd.Run("stat", fmt.Sprintf("/var/run/opencontainer/containers/%s", containerId))
	if err != nil {
		return false
	}

	return true
}

func unpackTar(cmd cmd.Cmd, tarFile, workDir string) {
	log.Println("Preparing image at destination host")
	_, _, err := cmd.Run("sudo", "tar", "-C", workDir, "-xvzf", tarFile)
	if err != nil {
		log.Fatal("Error uncompressing image in destination:", err)
	}
}

func prepareTar(cmd cmd.Cmd, tarFile, workDir string) {
	_, _, err := cmd.Run("sudo", "tar", "-czf", tarFile, "-C", fmt.Sprintf("%s/", workDir), ".")
	if err != nil {
		log.Fatal("Error compressing image in source:", err)
	}
}

func checkpoint(cmd cmd.Cmd, containerId, imagesPath string, predump bool) {
	log.Printf("Performing the checkpoint predump = %t\n", predump)
	args := []string{"runc", "--id", containerId, "checkpoint", "--tcp-established", "--track-mem", "--image-path", imagesPath}
	if predump {
		args = append(args, "--pre-dump")
	}
	_, _, err := cmd.Run("sudo", args...)
	if err != nil {
		log.Fatal("Error performing checkpoint:", err)
	}
}

func prepareDir(cmd cmd.Cmd, path string) {
	_, _, err := cmd.Run("mkdir", "-p", path)
	if err != nil {
		log.Fatal("Error preparing pre-dump dir:", err)
	}
}

func getContainerId(path string) string {
	_, id := filepath.Split(path)
	return id
}

func TriggerHook(command string) error {
	if command == "" {
		return nil
	}

	log.Printf("Running hook: %s\n", command)

	args := strings.Fields(command)
	c := cmd.NewLocal()
	stdout, stderr, err := c.Run(args[0], args[1:]...)

	log.Println(stdout, stderr)

	return err
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package indexers

import (
	"github.com/btcsuite/btcd/blockchain"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/database"
	"github.com/btcsuite/btcutil"
	"github.com/btcsuite/btcutil/gcs/builder"
)

const (
	// cfIndexName is the human-readable name for the index.
	cfIndexName = "committed filter index"
)

// Committed filters come in two flavours: basic and extended. They are
// generated and dropped in pairs, and both are indexed by a block's hash.
// Besides holding different content, they also live in different buckets.
var (
	// cfBasicIndexKey is the name of the db bucket used to house the
	// block hash -> basic cf index (cf#0).
	cfBasicIndexKey = []byte("cf0byhashidx")
	// cfBasicHeaderKey is the name of the db bucket used to house the
	// block hash -> basic cf header index (cf#0).
	cfBasicHeaderKey = []byte("cf0headerbyhashidx")
	// cfExtendedIndexKey is the name of the db bucket used to house the
	// block hash -> extended cf index (cf#1).
	cfExtendedIndexKey = []byte("cf1byhashidx")
	// cfExtendedHeaderKey is the name of the db bucket used to house the
	// block hash -> extended cf header index (cf#1).
	cfExtendedHeaderKey = []byte("cf1headerbyhashidx")
)

// dbFetchBasicEntry() retrieves a block's basic filter. An entry's absence is
// not considered an error. The filter is returned serialized.
func dbFetchBasicEntry(dbTx database.Tx, h *chainhash.Hash) ([]byte, error) {
	idx := dbTx.Metadata().Bucket(cfBasicIndexKey)
	return idx.Get(h[:]), nil
}

// dbFetchExtendedEntry() retrieves a block's extended filter. An entry's
// absence is not considered an error. The filter is returned serialized.
func dbFetchExtendedEntry(dbTx database.Tx, h *chainhash.Hash) ([]byte, error) {
	idx := dbTx.Metadata().Bucket(cfExtendedIndexKey)
	return idx.Get(h[:]), nil
}

// dbFetchBasicHeader() retrieves a block's basic filter header. A filter's
// absence is not considered an error.
func dbFetchBasicHeader(dbTx database.Tx, h *chainhash.Hash) ([]byte, error) {
	idx := dbTx.Metadata().Bucket(cfBasicHeaderKey)
	return idx.Get(h[:]), nil
}

// dbFetchExtendedHeader() retrieves a block's extended filter header.
// A filter's absence is not considered an error.
func dbFetchExtendedHeader(dbTx database.Tx, h*chainhash.Hash) ([]byte, error) {
	idx := dbTx.Metadata().Bucket(cfExtendedHeaderKey)
	return idx.Get(h[:]), nil
}

// dbStoreBasicEntry() stores a block's basic filter.
func dbStoreBasicEntry(dbTx database.Tx, h *chainhash.Hash, f []byte) error {
	idx := dbTx.Metadata().Bucket(cfBasicIndexKey)
	return idx.Put(h[:], f)
}

// dbStoreBasicEntry() stores a block's extended filter.
func dbStoreExtendedEntry(dbTx database.Tx, h *chainhash.Hash, f []byte) error {
	idx := dbTx.Metadata().Bucket(cfExtendedIndexKey)
	return idx.Put(h[:], f)
}

// dbDeleteBasicEntry() deletes a block's basic filter.
func dbDeleteBasicEntry(dbTx database.Tx, h *chainhash.Hash) error {
	idx := dbTx.Metadata().Bucket(cfBasicIndexKey)
	return idx.Delete(h[:])
}

// dbDeleteExtendedEntry() deletes a block's extended filter.
func dbDeleteExtendedEntry(dbTx database.Tx, h *chainhash.Hash) error {
	idx := dbTx.Metadata().Bucket(cfExtendedIndexKey)
	return idx.Delete(h[:])
}

// CfIndex implements a committed filter (cf) by hash index.
type CfIndex struct {
	db database.DB
}

// Ensure the CfIndex type implements the Indexer interface.
var _ Indexer = (*CfIndex)(nil)

// Init initializes the hash-based cf index. This is part of the Indexer
// interface.
func (idx *CfIndex) Init() error {
	return nil // Nothing to do.
}

// Key returns the database key to use for the index as a byte slice. This is
// part of the Indexer interface.
func (idx *CfIndex) Key() []byte {
	return cfBasicIndexKey
}

// Name returns the human-readable name of the index. This is part of the
// Indexer interface.
func (idx *CfIndex) Name() string {
	return cfIndexName
}

// Create is invoked when the indexer manager determines the index needs to be
// created for the first time. It creates buckets for the two hash-based cf
// indexes (simple, extended).
func (idx *CfIndex) Create(dbTx database.Tx) error {
	meta := dbTx.Metadata()
	_, err := meta.CreateBucket(cfBasicIndexKey)
	if err != nil {
		return err
	}
	_, err = meta.CreateBucket(cfBasicHeaderKey)
	if err != nil {
		return err
	}
	_, err = meta.CreateBucket(cfExtendedIndexKey)
	if err != nil {
		return err
	}
	_, err = meta.CreateBucket(cfExtendedHeaderKey)
	return err
}

// makeBasicFilter() builds a block's basic filter, which consists of all
// outpoints and pkscript data pushes referenced by transactions within the
// block.
func makeBasicFilterForBlock(block *btcutil.Block) ([]byte, error) {
	b := builder.WithKeyHash(block.Hash())
	_, err := b.Key()
	if err != nil {
		return nil, err
	}
	for _, tx := range block.Transactions() {
		for _, txIn := range tx.MsgTx().TxIn {
			b.AddOutPoint(txIn.PreviousOutPoint)
		}
		for _, txOut := range tx.MsgTx().TxOut {
			b.AddScript(txOut.PkScript)
		}
	}
	f, err := b.Build()
	if err != nil {
		return nil, err
	}
	return f.Bytes(), nil
}

// makeExtendedFilter() builds a block's extended filter, which consists of
// all tx hashes and sigscript data pushes contained in the block.
func makeExtendedFilterForBlock(block *btcutil.Block) ([]byte, error) {
	b := builder.WithKeyHash(block.Hash())
	_, err := b.Key()
	if err != nil {
		return nil, err
	}
	for _, tx := range block.Transactions() {
		b.AddHash(tx.Hash())
		for _, txIn := range tx.MsgTx().TxIn {
			b.AddScript(txIn.SignatureScript)
		}
	}
	f, err := b.Build()
	if err != nil {
		return nil, err
	}
	return f.Bytes(), nil
}

// ConnectBlock is invoked by the index manager when a new block has been
// connected to the main chain. This indexer adds a hash-to-cf mapping for
// every passed block. This is part of the Indexer interface.
func (idx *CfIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
    view *blockchain.UtxoViewpoint) error {
	f, err := makeBasicFilterForBlock(block)
	if err != nil {
		return err
	}
	err = dbStoreBasicEntry(dbTx, block.Hash(), f)
	if err != nil {
		return err
	}
	f, err = makeExtendedFilterForBlock(block)
	if err != nil {
		return err
	}
	return dbStoreExtendedEntry(dbTx, block.Hash(), f)
}

// DisconnectBlock is invoked by the index manager when a block has been
// disconnected from the main chain.  This indexer removes the hash-to-cf
// mapping for every passed block. This is part of the Indexer interface.
func (idx *CfIndex) DisconnectBlock(dbTx database.Tx, block *btcutil.Block,
    view *blockchain.UtxoViewpoint) error {
	err := dbDeleteBasicEntry(dbTx, block.Hash())
	if err != nil {
		return err
	}
	return dbDeleteExtendedEntry(dbTx, block.Hash())
}

func (idx *CfIndex) FilterByBlockHash(hash *chainhash.Hash, extended bool) ([]byte, error) {
	var filterBytes []byte
	err := idx.db.View(func(dbTx database.Tx) error {
		var err error
		if extended {
			filterBytes, err = dbFetchExtendedEntry(dbTx, hash)
		} else {
			filterBytes, err = dbFetchBasicEntry(dbTx, hash)
		}
		return err
	})
	return filterBytes, err
}

// NewCfIndex returns a new instance of an indexer that is used to create a
// mapping of the hashes of all blocks in the blockchain to their respective
// committed filters.
//
// It implements the Indexer interface which plugs into the IndexManager that in
// turn is used by the blockchain package. This allows the index to be
// seamlessly maintained along with the chain.
func NewCfIndex(db database.DB) *CfIndex {
	return &CfIndex{db: db}
}

// DropCfIndex drops the CF index from the provided database if exists.
func DropCfIndex(db database.DB) error {
	return dropIndex(db, cfBasicIndexKey, cfIndexName)
}
<commit_msg>Generate and store filter headers<commit_after>// Copyright (c) 2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package indexers

import (
	"errors"
	"github.com/btcsuite/btcd/blockchain"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/database"
	"github.com/btcsuite/btcutil"
	"github.com/btcsuite/btcutil/gcs/builder"
	"github.com/btcsuite/fastsha256"
)

const (
	// cfIndexName is the human-readable name for the index.
	cfIndexName = "committed filter index"
)

// Committed filters come in two flavours: basic and extended. They are
// generated and dropped in pairs, and both are indexed by a block's hash.
// Besides holding different content, they also live in different buckets.
var (
	// cfBasicIndexKey is the name of the db bucket used to house the
	// block hash -> basic cf index (cf#0).
	cfBasicIndexKey = []byte("cf0byhashidx")
	// cfBasicHeaderKey is the name of the db bucket used to house the
	// block hash -> basic cf header index (cf#0).
	cfBasicHeaderKey = []byte("cf0headerbyhashidx")
	// cfExtendedIndexKey is the name of the db bucket used to house the
	// block hash -> extended cf index (cf#1).
	cfExtendedIndexKey = []byte("cf1byhashidx")
	// cfExtendedHeaderKey is the name of the db bucket used to house the
	// block hash -> extended cf header index (cf#1).
	cfExtendedHeaderKey = []byte("cf1headerbyhashidx")
)

// dbFetchFilter() retrieves a block's basic or extended filter. A filter's
// absence is not considered an error.
func dbFetchFilter(dbTx database.Tx, key []byte, h *chainhash.Hash) ([]byte, error) {
	idx := dbTx.Metadata().Bucket(key)
	return idx.Get(h[:]), nil
}

// dbFetchFilterHeader() retrieves a block's basic or extended filter header.
// A filter's absence is not considered an error.
func dbFetchFilterHeader(dbTx database.Tx, key []byte, h *chainhash.Hash) ([]byte, error) {
	idx := dbTx.Metadata().Bucket(key)
	fh := idx.Get(h[:])
	if len(fh) != fastsha256.Size {
		return nil, errors.New("invalid filter header length")
	}
	return fh, nil
}

// dbStoreFilter() stores a block's basic or extended filter.
func dbStoreFilter(dbTx database.Tx, key []byte, h *chainhash.Hash, f []byte) error {
	idx := dbTx.Metadata().Bucket(key)
	return idx.Put(h[:], f)
}

// dbStoreFilterHeader() stores a block's basic or extended filter header.
func dbStoreFilterHeader(dbTx database.Tx, key []byte, h *chainhash.Hash, fh []byte) error {
	if len(fh) != fastsha256.Size {
		return errors.New("invalid filter header length")
	}
	idx := dbTx.Metadata().Bucket(key)
	return idx.Put(h[:], fh)
}

// dbDeleteFilter() deletes a filter's basic or extended filter.
func dbDeleteFilter(dbTx database.Tx, key []byte, h *chainhash.Hash) error {
	idx := dbTx.Metadata().Bucket(key)
	return idx.Delete(h[:])
}

// dbDeleteFilterHeader() deletes a filter's basic or extended filter header.
func dbDeleteFilterHeader(dbTx database.Tx, key []byte, h *chainhash.Hash) error {
	idx := dbTx.Metadata().Bucket(key)
	return idx.Delete(h[:])
}

// CfIndex implements a committed filter (cf) by hash index.
type CfIndex struct {
	db database.DB
}

// Ensure the CfIndex type implements the Indexer interface.
var _ Indexer = (*CfIndex)(nil)

// Init initializes the hash-based cf index. This is part of the Indexer
// interface.
func (idx *CfIndex) Init() error {
	return nil // Nothing to do.
}

// Key returns the database key to use for the index as a byte slice. This is
// part of the Indexer interface.
func (idx *CfIndex) Key() []byte {
	return cfBasicIndexKey
}

// Name returns the human-readable name of the index. This is part of the
// Indexer interface.
func (idx *CfIndex) Name() string {
	return cfIndexName
}

// Create() is invoked when the indexer manager determines the index needs to
// be created for the first time. It creates buckets for the two hash-based cf
// indexes (simple, extended).
func (idx *CfIndex) Create(dbTx database.Tx) error {
	meta := dbTx.Metadata()
	_, err := meta.CreateBucket(cfBasicIndexKey)
	if err != nil {
		return err
	}
	_, err = meta.CreateBucket(cfBasicHeaderKey)
	if err != nil {
		return err
	}
	_, err = meta.CreateBucket(cfExtendedIndexKey)
	if err != nil {
		return err
	}
	_, err = meta.CreateBucket(cfExtendedHeaderKey)
	return err
}

// makeBasicFilterForBlock() builds a block's basic filter, which consists of
// all outpoints and pkscript data pushes referenced by transactions within the
// block.
func makeBasicFilterForBlock(block *btcutil.Block) ([]byte, error) {
	b := builder.WithKeyHash(block.Hash())
	_, err := b.Key()
	if err != nil {
		return nil, err
	}
	for _, tx := range block.Transactions() {
		for _, txIn := range tx.MsgTx().TxIn {
			b.AddOutPoint(txIn.PreviousOutPoint)
		}
		for _, txOut := range tx.MsgTx().TxOut {
			b.AddScript(txOut.PkScript)
		}
	}
	f, err := b.Build()
	if err != nil {
		return nil, err
	}
	return f.Bytes(), nil
}

// makeExtendedFilterForBlock() builds a block's extended filter, which consists
// of all tx hashes and sigscript data pushes contained in the block.
func makeExtendedFilterForBlock(block *btcutil.Block) ([]byte, error) {
	b := builder.WithKeyHash(block.Hash())
	_, err := b.Key()
	if err != nil {
		return nil, err
	}
	for _, tx := range block.Transactions() {
		b.AddHash(tx.Hash())
		for _, txIn := range tx.MsgTx().TxIn {
			b.AddScript(txIn.SignatureScript)
		}
	}
	f, err := b.Build()
	if err != nil {
		return nil, err
	}
	return f.Bytes(), nil
}

// makeHeaderForFilter() implements the chaining logic between filters, where
// a filter's header is defined as sha256(sha256(filter) + previousFilterHeader).
func makeHeaderForFilter(f, pfh []byte) []byte {
	fhash := fastsha256.Sum256(f)
	chain := make([]byte, 0, 2*fastsha256.Size)
	chain = append(chain, fhash[:]...)
	chain = append(chain, pfh...)
	fh := fastsha256.Sum256(chain)
	return fh[:]
}

// storeFilter() stores a given filter, and performs the steps needed to
// generate the filter's header.
func storeFilter(dbTx database.Tx, block *btcutil.Block, f []byte, extended bool) error {
	// Figure out which buckets to use.
	fkey := cfBasicIndexKey
	hkey := cfBasicHeaderKey
	if extended {
		fkey = cfExtendedIndexKey
		hkey = cfExtendedHeaderKey
	}
	// Start by storing the filter.
	h := block.Hash()
	err := dbStoreFilter(dbTx, fkey, h, f)
	if err != nil {
		return err
	}
	// Then fetch the previous block's filter header.
	ph := &block.MsgBlock().Header.PrevBlock
	pfh, err := dbFetchFilterHeader(dbTx, hkey, ph)
	if err != nil {
		return err
	}
	// Construct the new block's filter header, and store it.
	fh := makeHeaderForFilter(f, pfh)
	return dbStoreFilterHeader(dbTx, cfBasicHeaderKey, h, fh)
}

// ConnectBlock() is invoked by the index manager when a new block has been
// connected to the main chain. This indexer adds a hash-to-cf mapping for
// every passed block. This is part of the Indexer interface.
func (idx *CfIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
    view *blockchain.UtxoViewpoint) error {
	f, err := makeBasicFilterForBlock(block)
	if err != nil {
		return err
	}
	err = storeFilter(dbTx, block, f, false)
	if err != nil {
		return err
	}
	f, err = makeExtendedFilterForBlock(block)
	if err != nil {
		return err
	}
	return storeFilter(dbTx, block, f, true)
}

// DisconnectBlock() is invoked by the index manager when a block has been
// disconnected from the main chain.  This indexer removes the hash-to-cf
// mapping for every passed block. This is part of the Indexer interface.
func (idx *CfIndex) DisconnectBlock(dbTx database.Tx, block *btcutil.Block,
    view *blockchain.UtxoViewpoint) error {
	err := dbDeleteFilter(dbTx, cfBasicIndexKey, block.Hash())
	if err != nil {
		return err
	}
	return dbDeleteFilter(dbTx, cfExtendedIndexKey, block.Hash())
}

func (idx *CfIndex) FilterByBlockHash(hash *chainhash.Hash, extended bool) ([]byte, error) {
	var filterBytes []byte
	err := idx.db.View(func(dbTx database.Tx) error {
		var err error
		key := cfBasicIndexKey
		if extended {
			key = cfExtendedIndexKey
		}
		filterBytes, err = dbFetchFilter(dbTx, key, hash)
		return err
	})
	return filterBytes, err
}

// NewCfIndex returns a new instance of an indexer that is used to create a
// mapping of the hashes of all blocks in the blockchain to their respective
// committed filters.
//
// It implements the Indexer interface which plugs into the IndexManager that in
// turn is used by the blockchain package. This allows the index to be
// seamlessly maintained along with the chain.
func NewCfIndex(db database.DB) *CfIndex {
	return &CfIndex{db: db}
}

// DropCfIndex drops the CF index from the provided database if exists.
func DropCfIndex(db database.DB) error {
	return dropIndex(db, cfBasicIndexKey, cfIndexName)
}
<|endoftext|>


<commit_before>// Copyright 2009 Dimiter Stanev, malkia@gmail.com.
// Copyright 2010 David Roundy, roundyd@physics.oregonstate.edu.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"os"
	"fmt"
	"strings"
	"io/ioutil"
	"go/parser"
	"go/ast"
	"go/scanner"
	"strconv"
	"path"
  "sort"
	"./got/buildit"
)

func getImports(filename string) (pkgname string,
	imports map[string]bool, names map[string]string, error os.Error) {
	source, error := ioutil.ReadFile(filename)
	if error != nil {
		return
	}
	file, error := parser.ParseFile(filename, source, nil, parser.ImportsOnly)
	if error != nil {
		return
	}
	dir, _ := path.Split(filename)
	pkgname = file.Name.Name()
	for _, importDecl := range file.Decls {
		importDecl, ok := importDecl.(*ast.GenDecl)
		if ok {
			for _, importSpec := range importDecl.Specs {
				importSpec, ok := importSpec.(*ast.ImportSpec)
				if ok {
					importPath, _ := strconv.Unquote(string(importSpec.Path.Value))
					if len(importPath) > 0 {
						if imports == nil {
							imports = make(map[string]bool)
							names = make(map[string]string)
						}
						if importPath[0] == '.' || strings.Index(importPath, "(") != -1 {
							nicepath := path.Join(dir, path.Clean(importPath))
							imports[nicepath] = true
							//names[path.Join(dir,path.Clean(importSpec.Name.String()))]=importPath
							names[importSpec.Name.String()]=nicepath
						}
					}
				}
			}
		}
	}
	return
}

func cleanbinname(f string) string {
	if f[0:4] == "src/" { return "bin/"+f[4:] }
	return f
}

func cleanpkgname(f string) string {
	if f[0:4] == "pkg/" { return f[4:] }
	return f
}

type maker struct {
}
func (maker) VisitDir(string, *os.Dir) bool { return true }
func (maker) VisitFile(f string, _ *os.Dir) {
	if endswith(f, ".gotgo.go") {
		fmt.Printf("# ignoring %s, since it's a generated file\n", f)
	} else if path.Ext(f) == ".go" {
		deps := make([]string, 1, 1000) // FIXME stupid hardcoded limit...x
		deps[0] = f
		pname, imports, names, err := getImports(f)
		if err != nil {
			fmt.Println("# error: ", err)
		}
		for i,_ := range imports {
			fmt.Printf("# %s imports %s\n", f, i)
			createGofile(i, names)
			deps = deps[0:len(deps)+1]
			deps[len(deps)-1] = i + ".$(O)"
		}
    sort.SortStrings(deps[1:]) // alphebatize all deps but first
		basename := f[0:len(f)-3]
		objname := basename+".$(O)"
		if pname == "main" {
			fmt.Printf("%s: %s\n\t@mkdir -p bin\n\t$(LD) -o $@ $<\n", cleanbinname(basename), objname)
			if cleanbinname(basename)[0:4] == "bin/" {
				installname := fmt.Sprintf("$(bindir)/%s",
					cleanbinname(basename)[4:])
				fmt.Printf("%s: %s\n\tcp $< $@\n", installname, cleanbinname(basename))
				installbins += " " + installname
			}
		}
		fmt.Print(objname+":")
		for _,d := range deps {
			fmt.Print(" "+d)
		}
		fmt.Print("\n")
		if basename[0:4] == "pkg/" && !endswith(basename,"gotgo.go") {
			// we want to install this as a package
			installname := fmt.Sprintf("$(pkgdir)/%s", objname)
			dir,_ := path.Split(installname)
			fmt.Printf("%s: %s\n\tmkdir -p %s\n\tcp $< $@\n\n",
				installname, objname, dir)
			installpkgs += " " + installname
		}
		fmt.Print("\n")
	} else if path.Ext(f) == ".got" {
		fmt.Printf("# found file %s to build...\n", f)
		// YUCK: the following code essentially duplicates the code in the
		// previous if case!
		deps := make([]string, 1, 1000) // FIXME stupid hardcoded limit...x
		_, imports, names, err := getImports(f)
		if err != nil {
			fmt.Println("# error: ", err)
		}
		for i,_ := range imports {
			fmt.Printf("# %s depends on %s\n", f, i)
			createGofile(i, names)
			deps = deps[0:len(deps)+1]
			deps[len(deps)-1] = i + ".$(O)"
		}
    sort.SortStrings(deps) // alphebatize all these deps
		fmt.Printf("%s: %s", f+"go", f)
		for _,d := range deps {
			fmt.Print(" "+d)
		}
		fmt.Print("\n")
		if f[0:4] == "pkg/" {
			// we want to install this as a got package!
			installname := fmt.Sprintf("$(pkgdir)/%s", f[4:]+"go")
			dir,_ := path.Split(installname)
			fmt.Printf("%s: %s\n\tmkdir -p %s\n\tcp $< $@\n\n",installname,f+"go",dir)
			installpkgs += " " + installname
		}
		fmt.Print("\n")
	}
}

func createGofile(sourcePath string, names map[string]string) {
	switch n := strings.Index(sourcePath, "("); n {
	case -1:
		// ignore non-templated import...
	default:
		basename := sourcePath[0:n]
		typesname := sourcePath[n+1:]
		gotname := basename + ".got"
		gotgoname := gotname + "go"
		goname := sourcePath + ".go"

		if fileexists(gotname) {
			// first scan the type parameters in the import
			var scan scanner.Scanner
			scan.Init(sourcePath, []byte(typesname), nil, 0)
			types, error := buildit.TypeList(&scan)
			if error != nil { return }

			fmt.Printf("%s: %s\n\t$<", goname, gotgoname)
			cuttable := ""
			for i,v := range path.Clean(goname) {
				if v == '/' { cuttable = path.Clean(goname)[0:i+1] }
			}
			relnames := make(map[string]string)
			for k,v := range names {
				if v[0:len(cuttable)] == cuttable {
					relnames[k] = "./" + v[len(cuttable):]
				} else {
					relnames[k] = v
				}
			}
			for _, a := range buildit.GetGotgoArguments(types, relnames) {
				fmt.Printf(" '%s'", a)
			}
			fmt.Printf(" > \"$@\"\n")
		} else {
			fmt.Printf("# I don't know how to make %s from %s\n", goname, gotname)
		}
	}
	return
}

type seeker struct {
}
func (seeker) VisitDir(string, *os.Dir) bool { return true }
func (seeker) VisitFile(f string, _ *os.Dir) {
	switch path.Ext(f) {
	case ".go":
		pname, _, _, _ := getImports(f)
		basename := f[0:len(f)-3]
		if pname == "main" && len(f) > 4 && f[0:4] != "test" {
			mybinfiles += " " + cleanbinname(basename)
		} else if len(f) > 4 && f[0:4] == "pkg/" {
			mypackages += " " + basename + ".a"
		}
	case ".got":
		if len(f) > 4 && f[0:4] == "pkg/" {
			mypackages += " " + f + "go"
		}
	}
}

var mybinfiles = ""
var mypackages = ""
var installbins = ""
var installpkgs = ""

func main() {
	path.Walk(".", seeker{}, nil)
	fmt.Printf(`

binaries: %s
packages: %s

include $(GOROOT)/src/Make.$(GOARCH)
ifndef GOBIN
GOBIN=$(HOME)/bin
endif

# ugly hack to deal with whitespaces in $GOBIN
nullstring :=
space := $(nullstring) # a space at the end
bindir=$(subst $(space),\ ,$(GOBIN))
pkgdir=$(subst $(space),\ ,$(GOROOT)/pkg/$(GOOS)_$(GOARCH))

.PHONY: test binaries install installbins installpkgs
.SUFFIXES: .$(O) .go .got .gotgo

`, mybinfiles, mypackages)
	fmt.Print(".go.$(O):\n\tcd `dirname \"$<\"`; $(GC) `basename \"$<\"`\n")
	if fileexists("gotgo.go") {
		fmt.Print(".got.gotgo:\n\t./gotgo \"$<\"\n\n")
	} else {
		fmt.Print(".got.gotgo:\n\tgotgo \"$<\"\n\n")
	}
	path.Walk(".", maker{}, nil)
	fmt.Printf("installbins: %s\n", installbins)
	fmt.Printf("installpkgs: %s\n", installpkgs)
}

func fileexists(f string) bool {
	x, err := os.Stat(f)
	return err == nil && x.IsRegular()
}

func endswith(s, end string) bool {
	return len(s) >= len(end) && s[len(s) - len(end):] == end
}
<commit_msg>untested code to use an installed .gotgo file.<commit_after>// Copyright 2009 Dimiter Stanev, malkia@gmail.com.
// Copyright 2010 David Roundy, roundyd@physics.oregonstate.edu.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"os"
	"fmt"
	"strings"
	"io/ioutil"
	"go/parser"
	"go/ast"
	"go/scanner"
	"strconv"
	"path"
  "sort"
	"./got/buildit"
)

func getImports(filename string) (pkgname string,
	imports map[string]bool, names map[string]string, error os.Error) {
	source, error := ioutil.ReadFile(filename)
	if error != nil {
		return
	}
	file, error := parser.ParseFile(filename, source, nil, parser.ImportsOnly)
	if error != nil {
		return
	}
	dir, _ := path.Split(filename)
	pkgname = file.Name.Name()
	for _, importDecl := range file.Decls {
		importDecl, ok := importDecl.(*ast.GenDecl)
		if ok {
			for _, importSpec := range importDecl.Specs {
				importSpec, ok := importSpec.(*ast.ImportSpec)
				if ok {
					importPath, _ := strconv.Unquote(string(importSpec.Path.Value))
					if len(importPath) > 0 {
						if imports == nil {
							imports = make(map[string]bool)
							names = make(map[string]string)
						}
						if importPath[0] == '.' || strings.Index(importPath, "(") != -1 {
							nicepath := path.Join(dir, path.Clean(importPath))
							imports[nicepath] = true
							//names[path.Join(dir,path.Clean(importSpec.Name.String()))]=importPath
							names[importSpec.Name.String()]=nicepath
						}
					}
				}
			}
		}
	}
	return
}

func cleanbinname(f string) string {
	if f[0:4] == "src/" { return "bin/"+f[4:] }
	return f
}

func cleanpkgname(f string) string {
	if f[0:4] == "pkg/" { return f[4:] }
	return f
}

type maker struct {
}
func (maker) VisitDir(string, *os.Dir) bool { return true }
func (maker) VisitFile(f string, _ *os.Dir) {
	if endswith(f, ".gotgo.go") {
		fmt.Printf("# ignoring %s, since it's a generated file\n", f)
	} else if path.Ext(f) == ".go" {
		deps := make([]string, 1, 1000) // FIXME stupid hardcoded limit...x
		deps[0] = f
		pname, imports, names, err := getImports(f)
		if err != nil {
			fmt.Println("# error: ", err)
		}
		for i,_ := range imports {
			fmt.Printf("# %s imports %s\n", f, i)
			createGofile(i, names)
			deps = deps[0:len(deps)+1]
			deps[len(deps)-1] = i + ".$(O)"
		}
    sort.SortStrings(deps[1:]) // alphebatize all deps but first
		basename := f[0:len(f)-3]
		objname := basename+".$(O)"
		if pname == "main" {
			fmt.Printf("%s: %s\n\t@mkdir -p bin\n\t$(LD) -o $@ $<\n", cleanbinname(basename), objname)
			if cleanbinname(basename)[0:4] == "bin/" {
				installname := fmt.Sprintf("$(bindir)/%s",
					cleanbinname(basename)[4:])
				fmt.Printf("%s: %s\n\tcp $< $@\n", installname, cleanbinname(basename))
				installbins += " " + installname
			}
		}
		fmt.Print(objname+":")
		for _,d := range deps {
			fmt.Print(" "+d)
		}
		fmt.Print("\n")
		if basename[0:4] == "pkg/" && !endswith(basename,"gotgo.go") {
			// we want to install this as a package
			installname := fmt.Sprintf("$(pkgdir)/%s", objname)
			dir,_ := path.Split(installname)
			fmt.Printf("%s: %s\n\tmkdir -p %s\n\tcp $< $@\n\n",
				installname, objname, dir)
			installpkgs += " " + installname
		}
		fmt.Print("\n")
	} else if path.Ext(f) == ".got" {
		fmt.Printf("# found file %s to build...\n", f)
		// YUCK: the following code essentially duplicates the code in the
		// previous if case!
		deps := make([]string, 1, 1000) // FIXME stupid hardcoded limit...x
		_, imports, names, err := getImports(f)
		if err != nil {
			fmt.Println("# error: ", err)
		}
		for i,_ := range imports {
			fmt.Printf("# %s depends on %s\n", f, i)
			createGofile(i, names)
			deps = deps[0:len(deps)+1]
			deps[len(deps)-1] = i + ".$(O)"
		}
    sort.SortStrings(deps) // alphebatize all these deps
		fmt.Printf("%s: %s", f+"go", f)
		for _,d := range deps {
			fmt.Print(" "+d)
		}
		fmt.Print("\n")
		if f[0:4] == "pkg/" {
			// we want to install this as a got package!
			installname := fmt.Sprintf("$(pkgdir)/%s", f[4:]+"go")
			dir,_ := path.Split(installname)
			fmt.Printf("%s: %s\n\tmkdir -p %s\n\tcp $< $@\n\n",installname,f+"go",dir)
			installpkgs += " " + installname
		}
		fmt.Print("\n")
	}
}

func createGofile(sourcePath string, names map[string]string) {
	switch n := strings.Index(sourcePath, "("); n {
	case -1:
		// ignore non-templated import...
	default:
		basename := sourcePath[0:n]
		typesname := sourcePath[n+1:]
		gotname := basename + ".got"
		gotgoname := gotname + "go"
		goname := sourcePath + ".go"
		pkggotname := path.Join(pkgdir,gotname)

		if fileexists(gotname) {
			fmt.Printf("%s: %s\n\t$<", goname, gotgoname)
		} else if fileexists(pkggotname) {
			fmt.Printf("# looks like we require %s as installed package...", gotname)
			fmt.Printf("%s: $(pkgdir)/%s\n\t$<", goname, gotgoname)
		} else {
			fmt.Printf("# I don't know how to make %s from %s\n", goname, gotname)
			return
		}
		// We've figured out what gotgo binary to use to create our go
		// file, and now just need the arguments.
		// Begin by scanning the type parameters in the import
		var scan scanner.Scanner
		scan.Init(sourcePath, []byte(typesname), nil, 0)
		types, error := buildit.TypeList(&scan)
		if error != nil { return }
		// Now we want to figure out how to modify the import path
		cuttable := ""
		for i,v := range path.Clean(goname) {
			if v == '/' { cuttable = path.Clean(goname)[0:i+1] }
		}
		relnames := make(map[string]string)
		for k,v := range names {
			if v[0:len(cuttable)] == cuttable {
				relnames[k] = "./" + v[len(cuttable):]
			} else {
				relnames[k] = v
			}
		}
		for _, a := range buildit.GetGotgoArguments(types, relnames) {
			fmt.Printf(" '%s'", a)
		}
		fmt.Printf(" > \"$@\"\n")
	}
	return
}

type seeker struct {
}
func (seeker) VisitDir(string, *os.Dir) bool { return true }
func (seeker) VisitFile(f string, _ *os.Dir) {
	switch path.Ext(f) {
	case ".go":
		pname, _, _, _ := getImports(f)
		basename := f[0:len(f)-3]
		if pname == "main" && len(f) > 4 && f[0:4] != "test" {
			mybinfiles += " " + cleanbinname(basename)
		} else if len(f) > 4 && f[0:4] == "pkg/" {
			mypackages += " " + basename + ".a"
		}
	case ".got":
		if len(f) > 4 && f[0:4] == "pkg/" {
			mypackages += " " + f + "go"
		}
	}
}

var mybinfiles = ""
var mypackages = ""
var installbins = ""
var installpkgs = ""
var pkgdir = path.Join(os.Getenv("GOROOT"),"pkg",
	os.Getenv("GOOS")+"_"+os.Getenv("GOARCH"))

func main() {
	path.Walk(".", seeker{}, nil)
	fmt.Printf(`

binaries: %s
packages: %s

include $(GOROOT)/src/Make.$(GOARCH)
ifndef GOBIN
GOBIN=$(HOME)/bin
endif

# ugly hack to deal with whitespaces in $GOBIN
nullstring :=
space := $(nullstring) # a space at the end
bindir=$(subst $(space),\ ,$(GOBIN))
pkgdir=$(subst $(space),\ ,$(GOROOT)/pkg/$(GOOS)_$(GOARCH))

.PHONY: test binaries install installbins installpkgs
.SUFFIXES: .$(O) .go .got .gotgo

`, mybinfiles, mypackages)
	fmt.Print(".go.$(O):\n\tcd `dirname \"$<\"`; $(GC) `basename \"$<\"`\n")
	if fileexists("gotgo.go") {
		fmt.Print(".got.gotgo:\n\t./gotgo \"$<\"\n\n")
	} else {
		fmt.Print(".got.gotgo:\n\tgotgo \"$<\"\n\n")
	}
	path.Walk(".", maker{}, nil)
	fmt.Printf("installbins: %s\n", installbins)
	fmt.Printf("installpkgs: %s\n", installpkgs)
}

func fileexists(f string) bool {
	x, err := os.Stat(f)
	return err == nil && x.IsRegular()
}

func endswith(s, end string) bool {
	return len(s) >= len(end) && s[len(s) - len(end):] == end
}
<|endoftext|>