<commit_before>package sorg

import (
	"os"

	log "github.com/Sirupsen/logrus"
)

const (
	// Release is the asset version of the site. Bump when any assets are
	// updated to blow away any browser caches.
	Release = "1"
)

const (
	// ContentDir is the location of the site's content (articles, fragments,
	// assets, etc.).
	ContentDir = "./org"

	// LayoutsDir is the source directory for view layouts.
	LayoutsDir = "./layouts"

	// MainLayout is the site's main layout.
	MainLayout = LayoutsDir + "/main"

	// PagesDir is the source directory for one-off page content.
	PagesDir = "./pages"

	// TargetDir is the target location where the site will be built to.
	TargetDir = "./public"

	// TargetVersionedAssetsDir is the target directory where static assets are
	// placed which should be versioned by release number. Versioned assets are
	// those that might need to change on release like CSS or JS files.
	TargetVersionedAssetsDir = TargetDir + "/assets/" + Release

	// ViewsDir is the source directory for views.
	ViewsDir = "./views"
)

// A list of all directories that are in the built static site.
var targetDirs = []string{
	TargetDir,
	TargetDir + "/articles",
	TargetDir + "/assets",
	TargetDir + "/fragments",
	TargetDir + "/assets/photos",
	TargetDir + "/photos",
	TargetDir + "/reading",
	TargetDir + "/runs",
	TargetDir + "/twitter",
	TargetVersionedAssetsDir,
}

// CreateTargetDirs creates TargetDir and all other necessary directories for
// the build if they don't already exist.
func CreateTargetDirs() error {
	for _, targetDir := range targetDirs {
		err := os.MkdirAll(targetDir, 0755)
		if err != nil {
			return err
		}
	}

	return nil
}

// InitLog initializes logging for singularity programs.
func InitLog(verbose bool) {
	log.SetFormatter(&plainFormatter{})

	if verbose {
		log.SetLevel(log.DebugLevel)
	}
}

// plainFormatter is a logrus formatter that displays text in a much more
// simple fashion that's more suitable as CLI output.
type plainFormatter struct {
}

// Format takes a logrus.Entry and returns bytes that are suitable for log
// output.
func (f *plainFormatter) Format(entry *log.Entry) ([]byte, error) {
	bytes := []byte(entry.Message + "\n")

	if entry.Level == log.DebugLevel {
		bytes = append([]byte("DEBUG: "), bytes...)
	}

	return bytes, nil
}
<commit_msg>Reorder target directories<commit_after>package sorg

import (
	"os"

	log "github.com/Sirupsen/logrus"
)

const (
	// Release is the asset version of the site. Bump when any assets are
	// updated to blow away any browser caches.
	Release = "1"
)

const (
	// ContentDir is the location of the site's content (articles, fragments,
	// assets, etc.).
	ContentDir = "./org"

	// LayoutsDir is the source directory for view layouts.
	LayoutsDir = "./layouts"

	// MainLayout is the site's main layout.
	MainLayout = LayoutsDir + "/main"

	// PagesDir is the source directory for one-off page content.
	PagesDir = "./pages"

	// TargetDir is the target location where the site will be built to.
	TargetDir = "./public"

	// TargetVersionedAssetsDir is the target directory where static assets are
	// placed which should be versioned by release number. Versioned assets are
	// those that might need to change on release like CSS or JS files.
	TargetVersionedAssetsDir = TargetDir + "/assets/" + Release

	// ViewsDir is the source directory for views.
	ViewsDir = "./views"
)

// A list of all directories that are in the built static site.
var targetDirs = []string{
	TargetDir,
	TargetDir + "/articles",
	TargetDir + "/assets",
	TargetDir + "/assets/photos",
	TargetDir + "/fragments",
	TargetDir + "/photos",
	TargetDir + "/reading",
	TargetDir + "/runs",
	TargetDir + "/twitter",
	TargetVersionedAssetsDir,
}

// CreateTargetDirs creates TargetDir and all other necessary directories for
// the build if they don't already exist.
func CreateTargetDirs() error {
	for _, targetDir := range targetDirs {
		err := os.MkdirAll(targetDir, 0755)
		if err != nil {
			return err
		}
	}

	return nil
}

// InitLog initializes logging for singularity programs.
func InitLog(verbose bool) {
	log.SetFormatter(&plainFormatter{})

	if verbose {
		log.SetLevel(log.DebugLevel)
	}
}

// plainFormatter is a logrus formatter that displays text in a much more
// simple fashion that's more suitable as CLI output.
type plainFormatter struct {
}

// Format takes a logrus.Entry and returns bytes that are suitable for log
// output.
func (f *plainFormatter) Format(entry *log.Entry) ([]byte, error) {
	bytes := []byte(entry.Message + "\n")

	if entry.Level == log.DebugLevel {
		bytes = append([]byte("DEBUG: "), bytes...)
	}

	return bytes, nil
}
<|endoftext|>


<commit_before>package hopwatch

import (
	"bytes"
	"github.com/davecgh/go-spew/spew"
)

// Dump delegates to spew.Fdump, see https://github.com/davecgh/go-spew
func Dump(a ...interface{}) *Watchpoint {
	writer := new(bytes.Buffer)
	spew.Fdump(writer, a)
	wp := &Watchpoint{offset: 2}
	return wp.printcontent(string(writer.Bytes()))
}

// Dumpf delegates to spew.Fprintf, see https://github.com/davecgh/go-spew
func Dumpf(format string, a ...interface{}) *Watchpoint {
	writer := new(bytes.Buffer)
	_, err := spew.Fprintf(writer, format, a)
	if err != nil {
		return Printf("[hopwatch] error in spew.Fprintf:%v", err)
	}
	wp := &Watchpoint{offset: 2}
	return wp.printcontent(string(writer.Bytes()))
}
<commit_msg>applied dump improvement suggested by dave<commit_after>package hopwatch

import (
	"bytes"
	"github.com/davecgh/go-spew/spew"
)

// Dump delegates to spew.Fdump, see https://github.com/davecgh/go-spew
func Dump(a ...interface{}) *Watchpoint {
	writer := new(bytes.Buffer)
	spew.Fdump(writer, a...)
	wp := &Watchpoint{offset: 2}
	return wp.printcontent(string(writer.Bytes()))
}

// Dumpf delegates to spew.Fprintf, see https://github.com/davecgh/go-spew
func Dumpf(format string, a ...interface{}) *Watchpoint {
	writer := new(bytes.Buffer)
	_, err := spew.Fprintf(writer, format, a...)
	if err != nil {
		return Printf("[hopwatch] error in spew.Fprintf:%v", err)
	}
	wp := &Watchpoint{offset: 2}
	return wp.printcontent(string(writer.Bytes()))
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/gob"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/google/google-api-go-client/plus/v1"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
)

type authStatusType int

const (
	authStatusLoggedIn authStatusType = 0
)

func init() {
	gob.Register(authStatusLoggedIn)
}

type AuthenticationHandler struct {
	config *configType
	store  sessions.Store
}

func (a *AuthenticationHandler) sessionIsLoggedin(r *http.Request) bool {
	sess, _ := sessions.GetRegistry(r).Get(a.store, globalSessionName)
	if sess == nil || sess.Values[authStatusLoggedIn] == nil || !sess.Values[authStatusLoggedIn].(bool) {
		return false
	} else {
		return true
	}
}

func (a *AuthenticationHandler) VerifySession(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	if a.sessionIsLoggedin(r) {
		w.Write([]byte("true"))
	} else {
		w.Write([]byte("false"))
	}
}

func (a *AuthenticationHandler) VerifyGoogleToken(w http.ResponseWriter, r *http.Request) {
	var code string
	if codeB, err := ioutil.ReadAll(r.Body); err != nil {
		http.Error(w, "Failed to read code!", http.StatusBadRequest)
		return
	} else {
		code = string(codeB)
	}
	conf := &oauth2.Config{
		ClientID:     a.config.Auth.ClientID,
		ClientSecret: a.config.Auth.ClientSecret,
		RedirectURL:  "postmessage",
		Endpoint:     google.Endpoint,
	}
	tok, err := conf.Exchange(oauth2.NoContext, code)
	if err != nil {
		log.Print("Failed to exchange code, err ", err)
		http.Error(w, "Failed to talk to Google!", http.StatusInternalServerError)
		return
	}
	client := conf.Client(oauth2.NoContext, tok)

	plusService, err := plus.New(client)
	if err != nil {
		log.Panic("Shouldn't happen, err: ", err)
	}
	meGetter := plusService.People.Get("me")
	me, err := meGetter.Do()
	if err != nil {
		log.Print("Failed to get the user, err ", err)
		http.Error(w, "Failed to get user information!", http.StatusInternalServerError)
	}
	var primaryEmail string
	for _, emailInfo := range me.Emails {
		if emailInfo.Type == "account" {
			primaryEmail = emailInfo.Value
			break
		}
	}
	var validEmail bool
	for _, allowedEmail := range a.config.Auth.AllowedEmails {
		if allowedEmail == primaryEmail {
			validEmail = true
			break
		}
	}
	if validEmail {
		sess, err := sessions.GetRegistry(r).Get(a.store, globalSessionName)
		if sess == nil {
			log.Panicf("Failed to get session, err %s", err)
		}
		sess.Values[authStatusLoggedIn] = true
		w.Header().Set("Content-Type", "text/plain")
		w.WriteHeader(200)
		w.Write([]byte("true"))
	} else {
		w.Header().Set("Content-Type", "text/plain")
		w.WriteHeader(200)
		w.Write([]byte("false"))
	}
}

func (a *AuthenticationHandler) AdminFunc(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !a.sessionIsLoggedin(r) {
			http.Error(w, "Forbidden", http.StatusForbidden)
		} else {
			h(w, r)
		}
	}
}

func NewAuthenticationHandler(r *mux.Router, config *configType, store sessions.Store) *AuthenticationHandler {
	authHandler := &AuthenticationHandler{
		store:  store,
		config: config,
	}

	r.HandleFunc("/authentication/isLoggedIn", authHandler.VerifySession).Methods("GET")
	r.HandleFunc("/authentication/googletoken", authHandler.VerifyGoogleToken).Methods("POST")

	return authHandler
}
<commit_msg>Change import path for plus api.<commit_after>package main

import (
	"encoding/gob"
	"io/ioutil"
	"log"
	"net/http"

	"google.golang.org/api/plus/v1"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
)

type authStatusType int

const (
	authStatusLoggedIn authStatusType = 0
)

func init() {
	gob.Register(authStatusLoggedIn)
}

type AuthenticationHandler struct {
	config *configType
	store  sessions.Store
}

func (a *AuthenticationHandler) sessionIsLoggedin(r *http.Request) bool {
	sess, _ := sessions.GetRegistry(r).Get(a.store, globalSessionName)
	if sess == nil || sess.Values[authStatusLoggedIn] == nil || !sess.Values[authStatusLoggedIn].(bool) {
		return false
	} else {
		return true
	}
}

func (a *AuthenticationHandler) VerifySession(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	if a.sessionIsLoggedin(r) {
		w.Write([]byte("true"))
	} else {
		w.Write([]byte("false"))
	}
}

func (a *AuthenticationHandler) VerifyGoogleToken(w http.ResponseWriter, r *http.Request) {
	var code string
	if codeB, err := ioutil.ReadAll(r.Body); err != nil {
		http.Error(w, "Failed to read code!", http.StatusBadRequest)
		return
	} else {
		code = string(codeB)
	}
	conf := &oauth2.Config{
		ClientID:     a.config.Auth.ClientID,
		ClientSecret: a.config.Auth.ClientSecret,
		RedirectURL:  "postmessage",
		Endpoint:     google.Endpoint,
	}
	tok, err := conf.Exchange(oauth2.NoContext, code)
	if err != nil {
		log.Print("Failed to exchange code, err ", err)
		http.Error(w, "Failed to talk to Google!", http.StatusInternalServerError)
		return
	}
	client := conf.Client(oauth2.NoContext, tok)

	plusService, err := plus.New(client)
	if err != nil {
		log.Panic("Shouldn't happen, err: ", err)
	}
	meGetter := plusService.People.Get("me")
	me, err := meGetter.Do()
	if err != nil {
		log.Print("Failed to get the user, err ", err)
		http.Error(w, "Failed to get user information!", http.StatusInternalServerError)
	}
	var primaryEmail string
	for _, emailInfo := range me.Emails {
		if emailInfo.Type == "account" {
			primaryEmail = emailInfo.Value
			break
		}
	}
	var validEmail bool
	for _, allowedEmail := range a.config.Auth.AllowedEmails {
		if allowedEmail == primaryEmail {
			validEmail = true
			break
		}
	}
	if validEmail {
		sess, err := sessions.GetRegistry(r).Get(a.store, globalSessionName)
		if sess == nil {
			log.Panicf("Failed to get session, err %s", err)
		}
		sess.Values[authStatusLoggedIn] = true
		w.Header().Set("Content-Type", "text/plain")
		w.WriteHeader(200)
		w.Write([]byte("true"))
	} else {
		w.Header().Set("Content-Type", "text/plain")
		w.WriteHeader(200)
		w.Write([]byte("false"))
	}
}

func (a *AuthenticationHandler) AdminFunc(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !a.sessionIsLoggedin(r) {
			http.Error(w, "Forbidden", http.StatusForbidden)
		} else {
			h(w, r)
		}
	}
}

func NewAuthenticationHandler(r *mux.Router, config *configType, store sessions.Store) *AuthenticationHandler {
	authHandler := &AuthenticationHandler{
		store:  store,
		config: config,
	}

	r.HandleFunc("/authentication/isLoggedIn", authHandler.VerifySession).Methods("GET")
	r.HandleFunc("/authentication/googletoken", authHandler.VerifyGoogleToken).Methods("POST")

	return authHandler
}
<|endoftext|>


<commit_before>package registration

import (
	"fmt"
	"html/template"
	"net/http"
	"sync"

	"appengine"
	"appengine/taskqueue"
	"appengine/user"

	"model"
)

var (
	registrationForm    = template.Must(template.ParseFiles("registration/form.html"))
	newRegistrationPage = template.Must(template.ParseFiles("registration/registration-new.html"))
	classFullPage       = template.Must(template.ParseFiles("registration/full-class.html"))
	registrationConfirm = template.Must(template.ParseFiles("registration/registration-confirm.html"))
	teacherPage         = template.Must(template.ParseFiles("registration/teacher.html"))
	teacherRosterPage   = template.Must(template.ParseFiles("registration/roster.html"))
	teacherRegisterPage = template.Must(template.ParseFiles("registration/teacher-register.html"))
)

type requestVariable struct {
	lock sync.Mutex
	m    map[*http.Request]interface{}
}

func (v *requestVariable) Get(r *http.Request) interface{} {
	v.lock.Lock()
	defer v.lock.Unlock()
	if v.m == nil {
		return nil
	}
	return v.m[r]
}

func (v *requestVariable) Set(r *http.Request, val interface{}) {
	v.lock.Lock()
	defer v.lock.Unlock()
	if v.m == nil {
		v.m = map[*http.Request]interface{}{}
	}
	v.m[r] = val
}

type requestUser struct {
	*user.User
	*model.UserAccount
}

var (
	userVariable  = &requestVariable{}
	tokenVariable = &requestVariable{}
)

type appError struct {
	Error   error
	Message string
	Code    int
}

type handler func(w http.ResponseWriter, r *http.Request) *appError

func (fn handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if err := fn(w, r); err != nil {
		c := appengine.NewContext(r)
		c.Errorf("Error: %s", err.Error)
		http.Error(w, err.Message, err.Code)
	}
}

func postOnly(handler handler) handler {
	return func(w http.ResponseWriter, r *http.Request) *appError {
		if r.Method != "POST" {
			return &appError{fmt.Errorf("GET access to %s", r.URL), "Not Found", 404}
		}
		return handler(w, r)
	}
}

func needsUser(handler handler) handler {
	return func(w http.ResponseWriter, r *http.Request) *appError {
		c := appengine.NewContext(r)
		u := user.Current(c)
		if u == nil {
			return &appError{fmt.Errorf("No logged in user"), "An error occurred", http.StatusInternalServerError}
		}
		account, err := model.GetAccount(c, u)
		if err != nil {
			http.Redirect(w, r, "/login/account?continue="+r.URL.Path, http.StatusSeeOther)
			return nil
		}
		userVariable.Set(r, &requestUser{u, account})
		return handler(w, r)
	}
}

func getRequestUser(r *http.Request) *requestUser {
	return userVariable.Get(r).(*requestUser)
}

func xsrfProtected(handler handler) handler {
	return needsUser(func(w http.ResponseWriter, r *http.Request) *appError {
		u := userVariable.Get(r).(*requestUser)
		if u == nil {
			return &appError{fmt.Errorf("No user in request"), "An error ocurred", http.StatusInternalServerError}
		}
		c := appengine.NewContext(r)
		token, err := model.GetXSRFToken(c, u.AccountID)
		if err != nil {
			return &appError{
				fmt.Errorf("Could not get XSRF token for id %s: %s", u.AccountID, err),
				"An error occurred",
				http.StatusInternalServerError,
			}
		}
		tokenVariable.Set(r, token)
		if r.Method == "POST" && !token.Validate(r.FormValue("xsrf_token")) {
			return &appError{fmt.Errorf("Invalid XSRF token"), "Unauthorized", http.StatusUnauthorized}
		}
		return handler(w, r)
	})
}

func teachersOnly(handler handler) handler {
	return xsrfProtected(func(w http.ResponseWriter, r *http.Request) *appError {
		u := getRequestUser(r)
		if !(u.Role.IsStaff() || u.Role.CanTeach()) {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return nil
		}
		return handler(w, r)
	})
}

func init() {
	http.Handle("/registration", handler(xsrfProtected(registration)))
	http.Handle("/registration/new", handler(xsrfProtected(newRegistration)))
	http.Handle("/registration/teacher", handler(teachersOnly(teacher)))
	http.Handle("/registration/teacher/roster", handler(teachersOnly(teacherRoster)))
	http.Handle("/registration/teacher/register", handler(teachersOnly(teacherRegister)))
}

func filterRegisteredClasses(classes, registered []*model.Class) []*model.Class {
	if len(registered) == 0 || len(classes) == 0 {
		return classes
	}
	ids := map[int64]bool{}
	for _, r := range registered {
		ids[r.ID] = true
	}
	filtered := []*model.Class{}
	for _, c := range classes {
		if !ids[c.ID] {
			filtered = append(filtered, c)
		}
	}
	return filtered
}

func teacher(w http.ResponseWriter, r *http.Request) *appError {
	c := appengine.NewContext(r)
	scheduler := model.NewScheduler(c)
	u := getRequestUser(r)
	classes := scheduler.GetClassesForTeacher(u.UserAccount)
	if err := teacherPage.Execute(w, map[string]interface{}{
		"Classes": classes,
	}); err != nil {
		return &appError{err, "Not implemented", http.StatusNotFound}
	}
	return nil
}

func teacherRoster(w http.ResponseWriter, r *http.Request) *appError {
	fields, err := getRequiredFields(r, "class")
	if err != nil {
		return &appError{err, "Must specify a class", http.StatusBadRequest}
	}
	classID := mustParseInt(fields["class"], 64)
	c := appengine.NewContext(r)
	scheduler := model.NewScheduler(c)
	class := scheduler.GetClass(classID)
	if class == nil {
		return &appError{
			fmt.Errorf("Couldn't find class %d", classID),
			"Error looking up class",
			http.StatusInternalServerError,
		}
	}
	roster := model.NewRoster(c, class)
	registrations := roster.ListRegistrations()
	if err := teacherRosterPage.Execute(w, map[string]interface{}{
		"Class":         class,
		"Registrations": roster.GetStudents(registrations),
	}); err != nil {
		return &appError{err, "An error ocurred", http.StatusInternalServerError}
	}
	return nil
}

func teacherRegister(w http.ResponseWriter, r *http.Request) *appError {
	if r.Method == "POST" {
		fields, err := getRequiredFields(r, "xsrf_token", "class", "firstname", "lastname", "email")
		if err != nil {
			return &appError{err, "Missing required fields", http.StatusBadRequest}
		}
		c := appengine.NewContext(r)
		scheduler := model.NewScheduler(c)
		class := scheduler.GetClass(mustParseInt(fields["class"], 64))
		if class == nil {
			return &appError{
				fmt.Errorf("Couldn't find class %d", fields["class"]),
				"Error looking up class",
				http.StatusInternalServerError,
			}
		}
		account := &model.UserAccount{
			FirstName: fields["firstname"],
			LastName:  fields["lastname"],
			Email:     fields["email"],
		}
		if p := r.FormValue("phone"); p != "" {
			account.Phone = p
		}
		account.AccountID = "PAPERREGISTRATION|" + fields["email"]
		if err := model.StoreAccount(c, nil, account); err != nil {
			return &appError{err, "An error occurred", http.StatusInternalServerError}
		}
		roster := model.NewRoster(c, class)
		if _, err := roster.AddStudent(account.AccountID); err != nil {
			if err == model.ErrAlreadyRegistered {
				fmt.Fprintf(w, "Student with that email already registered for this class")
				return nil
			}
			return &appError{err, "Error registering student", http.StatusInternalServerError}
		}
		http.Redirect(w, r, fmt.Sprintf("/registration/teacher/roster?class=%d", class.ID), http.StatusSeeOther)
		return nil
	}
	token := tokenVariable.Get(r).(*model.AdminXSRFToken)
	if err := teacherRegisterPage.Execute(w, map[string]interface{}{
		"XSRFToken": token.Token,
		"ClassID":   r.FormValue("class"),
	}); err != nil {
		return &appError{err, "An error occurred", http.StatusInternalServerError}
	}
	return nil
}

func registration(w http.ResponseWriter, r *http.Request) *appError {
	c := appengine.NewContext(r)
	u := userVariable.Get(r).(*requestUser)
	scheduler := model.NewScheduler(c)
	classes := scheduler.ListOpenClasses(true)
	teachers := scheduler.GetTeacherNames(classes)
	registrar := model.NewRegistrar(c, u.AccountID)
	registered := registrar.ListRegisteredClasses()
	classes = filterRegisteredClasses(classes, registered)
	logout, err := user.LogoutURL(c, "/registration")
	if err != nil {
		return &appError{err, "An error occurred", http.StatusInternalServerError}
	}
	token := tokenVariable.Get(r).(*model.AdminXSRFToken)
	data := map[string]interface{}{
		"SessionClasses": classes,
		"XSRFToken":      token.Token,
		"LogoutURL":      logout,
		"Account":        u.UserAccount,
		"Registrations":  registered,
		"IsAdmin":        u.Role.IsStaff(),
		"Teachers":       teachers,
	}
	if err := registrationForm.Execute(w, data); err != nil {
		return &appError{err, "An error occurred", http.StatusInternalServerError}
	}
	return nil
}

func classFull(w http.ResponseWriter, r *http.Request, class string) *appError {
	if err := classFullPage.Execute(w, class); err != nil {
		return &appError{err, "An error occurred", http.StatusInternalServerError}
	}
	return nil
}

func newRegistration(w http.ResponseWriter, r *http.Request) *appError {
	u := userVariable.Get(r).(*requestUser)
	c := appengine.NewContext(r)
	if u.Confirmed.IsZero() {
		http.Redirect(w, r, "/registration", http.StatusSeeOther)
		return nil
	}
	classID := mustParseInt(r.FormValue("class"), 64)
	scheduler := model.NewScheduler(c)
	class := scheduler.GetClass(classID)
	teacher := scheduler.GetTeacher(class)
	if class == nil {
		return &appError{fmt.Errorf("Couldn't find class %d", classID),
			"An error occurred, please go back and try again",
			http.StatusInternalServerError}
	}
	if r.Method == "POST" {
		roster := model.NewRoster(c, class)
		if _, err := roster.AddStudent(u.AccountID); err != nil {
			if err == model.ErrClassFull {
				if err2 := classFullPage.Execute(w, map[string]interface{}{
					"Class": class,
				}); err2 != nil {
					return &appError{err, "An error occurred", http.StatusInternalServerError}
				}
				return nil
			}
			return &appError{
				fmt.Errorf("Error when registering student %s in class %d: %s", u.AccountID, class.ID, err),
				"An error occurred, please go back and try again.",
				http.StatusInternalServerError,
			}
		}
		t := taskqueue.NewPOSTTask("/task/email-confirmation", map[string][]string{
			"account": {u.AccountID},
			"class":   {fmt.Sprintf("%d", class.ID)},
		})
		if _, err := taskqueue.Add(c, t, ""); err != nil {
			return &appError{fmt.Errorf("Error enqueuing email task for registration: %s", err),
				"An error occurred, please go back and try again",
				http.StatusInternalServerError}
		}
		data := map[string]interface{}{
			"Email":   u.UserAccount.Email,
			"Class":   class,
			"Teacher": teacher,
		}
		if err := newRegistrationPage.Execute(w, data); err != nil {
			return &appError{err, "An error occurred; please go back and try again.", http.StatusInternalServerError}
		}
		return nil
	}
	token := tokenVariable.Get(r).(*model.AdminXSRFToken)
	if err := registrationConfirm.Execute(w, map[string]interface{}{
		"XSRFToken": token.Token,
		"Class":     class,
	}); err != nil {
		return &appError{err, "An error occurred", http.StatusInternalServerError}
	}
	return nil
}
<commit_msg>Disabled online registrations.<commit_after>package registration

import (
	"fmt"
	"html/template"
	"net/http"
	"sync"

	"appengine"
	"appengine/taskqueue"
	"appengine/user"

	"model"
)

var (
	registrationForm    = template.Must(template.ParseFiles("registration/form.html"))
	newRegistrationPage = template.Must(template.ParseFiles("registration/registration-new.html"))
	classFullPage       = template.Must(template.ParseFiles("registration/full-class.html"))
	registrationConfirm = template.Must(template.ParseFiles("registration/registration-confirm.html"))
	teacherPage         = template.Must(template.ParseFiles("registration/teacher.html"))
	teacherRosterPage   = template.Must(template.ParseFiles("registration/roster.html"))
	teacherRegisterPage = template.Must(template.ParseFiles("registration/teacher-register.html"))
)

type requestVariable struct {
	lock sync.Mutex
	m    map[*http.Request]interface{}
}

func (v *requestVariable) Get(r *http.Request) interface{} {
	v.lock.Lock()
	defer v.lock.Unlock()
	if v.m == nil {
		return nil
	}
	return v.m[r]
}

func (v *requestVariable) Set(r *http.Request, val interface{}) {
	v.lock.Lock()
	defer v.lock.Unlock()
	if v.m == nil {
		v.m = map[*http.Request]interface{}{}
	}
	v.m[r] = val
}

type requestUser struct {
	*user.User
	*model.UserAccount
}

var (
	userVariable  = &requestVariable{}
	tokenVariable = &requestVariable{}
)

type appError struct {
	Error   error
	Message string
	Code    int
}

type handler func(w http.ResponseWriter, r *http.Request) *appError

func (fn handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if err := fn(w, r); err != nil {
		c := appengine.NewContext(r)
		c.Errorf("Error: %s", err.Error)
		http.Error(w, err.Message, err.Code)
	}
}

func postOnly(handler handler) handler {
	return func(w http.ResponseWriter, r *http.Request) *appError {
		if r.Method != "POST" {
			return &appError{fmt.Errorf("GET access to %s", r.URL), "Not Found", 404}
		}
		return handler(w, r)
	}
}

func needsUser(handler handler) handler {
	return func(w http.ResponseWriter, r *http.Request) *appError {
		c := appengine.NewContext(r)
		u := user.Current(c)
		if u == nil {
			return &appError{fmt.Errorf("No logged in user"), "An error occurred", http.StatusInternalServerError}
		}
		account, err := model.GetAccount(c, u)
		if err != nil {
			http.Redirect(w, r, "/login/account?continue="+r.URL.Path, http.StatusSeeOther)
			return nil
		}
		userVariable.Set(r, &requestUser{u, account})
		return handler(w, r)
	}
}

func getRequestUser(r *http.Request) *requestUser {
	return userVariable.Get(r).(*requestUser)
}

func xsrfProtected(handler handler) handler {
	return needsUser(func(w http.ResponseWriter, r *http.Request) *appError {
		u := userVariable.Get(r).(*requestUser)
		if u == nil {
			return &appError{fmt.Errorf("No user in request"), "An error ocurred", http.StatusInternalServerError}
		}
		c := appengine.NewContext(r)
		token, err := model.GetXSRFToken(c, u.AccountID)
		if err != nil {
			return &appError{
				fmt.Errorf("Could not get XSRF token for id %s: %s", u.AccountID, err),
				"An error occurred",
				http.StatusInternalServerError,
			}
		}
		tokenVariable.Set(r, token)
		if r.Method == "POST" && !token.Validate(r.FormValue("xsrf_token")) {
			return &appError{fmt.Errorf("Invalid XSRF token"), "Unauthorized", http.StatusUnauthorized}
		}
		return handler(w, r)
	})
}

func teachersOnly(handler handler) handler {
	return xsrfProtected(func(w http.ResponseWriter, r *http.Request) *appError {
		u := getRequestUser(r)
		if !(u.Role.IsStaff() || u.Role.CanTeach()) {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return nil
		}
		return handler(w, r)
	})
}

func init() {
	http.Handle("/registration", handler(xsrfProtected(registration)))
	http.Handle("/registration/new", handler(xsrfProtected(newRegistration)))
	http.Handle("/registration/teacher", handler(teachersOnly(teacher)))
	http.Handle("/registration/teacher/roster", handler(teachersOnly(teacherRoster)))
	http.Handle("/registration/teacher/register", handler(teachersOnly(teacherRegister)))
}

func filterRegisteredClasses(classes, registered []*model.Class) []*model.Class {
	if len(registered) == 0 || len(classes) == 0 {
		return classes
	}
	ids := map[int64]bool{}
	for _, r := range registered {
		ids[r.ID] = true
	}
	filtered := []*model.Class{}
	for _, c := range classes {
		if !ids[c.ID] {
			filtered = append(filtered, c)
		}
	}
	return filtered
}

func teacher(w http.ResponseWriter, r *http.Request) *appError {
	c := appengine.NewContext(r)
	scheduler := model.NewScheduler(c)
	u := getRequestUser(r)
	classes := scheduler.GetClassesForTeacher(u.UserAccount)
	if err := teacherPage.Execute(w, map[string]interface{}{
		"Classes": classes,
	}); err != nil {
		return &appError{err, "Not implemented", http.StatusNotFound}
	}
	return nil
}

func teacherRoster(w http.ResponseWriter, r *http.Request) *appError {
	fields, err := getRequiredFields(r, "class")
	if err != nil {
		return &appError{err, "Must specify a class", http.StatusBadRequest}
	}
	classID := mustParseInt(fields["class"], 64)
	c := appengine.NewContext(r)
	scheduler := model.NewScheduler(c)
	class := scheduler.GetClass(classID)
	if class == nil {
		return &appError{
			fmt.Errorf("Couldn't find class %d", classID),
			"Error looking up class",
			http.StatusInternalServerError,
		}
	}
	roster := model.NewRoster(c, class)
	registrations := roster.ListRegistrations()
	if err := teacherRosterPage.Execute(w, map[string]interface{}{
		"Class":         class,
		"Registrations": roster.GetStudents(registrations),
	}); err != nil {
		return &appError{err, "An error ocurred", http.StatusInternalServerError}
	}
	return nil
}

func teacherRegister(w http.ResponseWriter, r *http.Request) *appError {
	if r.Method == "POST" {
		fields, err := getRequiredFields(r, "xsrf_token", "class", "firstname", "lastname", "email")
		if err != nil {
			return &appError{err, "Missing required fields", http.StatusBadRequest}
		}
		c := appengine.NewContext(r)
		scheduler := model.NewScheduler(c)
		class := scheduler.GetClass(mustParseInt(fields["class"], 64))
		if class == nil {
			return &appError{
				fmt.Errorf("Couldn't find class %d", fields["class"]),
				"Error looking up class",
				http.StatusInternalServerError,
			}
		}
		account := &model.UserAccount{
			FirstName: fields["firstname"],
			LastName:  fields["lastname"],
			Email:     fields["email"],
		}
		if p := r.FormValue("phone"); p != "" {
			account.Phone = p
		}
		account.AccountID = "PAPERREGISTRATION|" + fields["email"]
		if err := model.StoreAccount(c, nil, account); err != nil {
			return &appError{err, "An error occurred", http.StatusInternalServerError}
		}
		roster := model.NewRoster(c, class)
		if _, err := roster.AddStudent(account.AccountID); err != nil {
			if err == model.ErrAlreadyRegistered {
				fmt.Fprintf(w, "Student with that email already registered for this class")
				return nil
			}
			return &appError{err, "Error registering student", http.StatusInternalServerError}
		}
		http.Redirect(w, r, fmt.Sprintf("/registration/teacher/roster?class=%d", class.ID), http.StatusSeeOther)
		return nil
	}
	token := tokenVariable.Get(r).(*model.AdminXSRFToken)
	if err := teacherRegisterPage.Execute(w, map[string]interface{}{
		"XSRFToken": token.Token,
		"ClassID":   r.FormValue("class"),
	}); err != nil {
		return &appError{err, "An error occurred", http.StatusInternalServerError}
	}
	return nil
}

func registration(w http.ResponseWriter, r *http.Request) *appError {
	c := appengine.NewContext(r)
	u := userVariable.Get(r).(*requestUser)
	scheduler := model.NewScheduler(c)
	classes := scheduler.ListOpenClasses(true)
	teachers := scheduler.GetTeacherNames(classes)
	registrar := model.NewRegistrar(c, u.AccountID)
	registered := registrar.ListRegisteredClasses()
	classes = filterRegisteredClasses(classes, registered)
	logout, err := user.LogoutURL(c, "/registration")
	if err != nil {
		return &appError{err, "An error occurred", http.StatusInternalServerError}
	}
	token := tokenVariable.Get(r).(*model.AdminXSRFToken)
	data := map[string]interface{}{
		"SessionClasses": classes,
		"XSRFToken":      token.Token,
		"LogoutURL":      logout,
		"Account":        u.UserAccount,
		"Registrations":  registered,
		"IsAdmin":        u.Role.IsStaff(),
		"Teachers":       teachers,
	}
	if err := registrationForm.Execute(w, data); err != nil {
		return &appError{err, "An error occurred", http.StatusInternalServerError}
	}
	return nil
}

func classFull(w http.ResponseWriter, r *http.Request, class string) *appError {
	if err := classFullPage.Execute(w, class); err != nil {
		return &appError{err, "An error occurred", http.StatusInternalServerError}
	}
	return nil
}

func newRegistration(w http.ResponseWriter, r *http.Request) *appError {
	u := userVariable.Get(r).(*requestUser)
	c := appengine.NewContext(r)
	if u.Confirmed.IsZero() {
		http.Redirect(w, r, "/registration", http.StatusSeeOther)
		return nil
	}
	classID := mustParseInt(r.FormValue("class"), 64)
	scheduler := model.NewScheduler(c)
	class := scheduler.GetClass(classID)
	teacher := scheduler.GetTeacher(class)
	if class == nil {
		return &appError{fmt.Errorf("Couldn't find class %d", classID),
			"An error occurred, please go back and try again",
			http.StatusInternalServerError}
	}
	if r.Method == "POST" {
		return &appError{
			fmt.Errorf("Registrations disabled"),
			"Online registration is temporarily disabled",
			http.StatusNotFound,
		}
		roster := model.NewRoster(c, class)
		if _, err := roster.AddStudent(u.AccountID); err != nil {
			if err == model.ErrClassFull {
				if err2 := classFullPage.Execute(w, map[string]interface{}{
					"Class": class,
				}); err2 != nil {
					return &appError{err, "An error occurred", http.StatusInternalServerError}
				}
				return nil
			}
			return &appError{
				fmt.Errorf("Error when registering student %s in class %d: %s", u.AccountID, class.ID, err),
				"An error occurred, please go back and try again.",
				http.StatusInternalServerError,
			}
		}
		t := taskqueue.NewPOSTTask("/task/email-confirmation", map[string][]string{
			"account": {u.AccountID},
			"class":   {fmt.Sprintf("%d", class.ID)},
		})
		if _, err := taskqueue.Add(c, t, ""); err != nil {
			return &appError{fmt.Errorf("Error enqueuing email task for registration: %s", err),
				"An error occurred, please go back and try again",
				http.StatusInternalServerError}
		}
		data := map[string]interface{}{
			"Email":   u.UserAccount.Email,
			"Class":   class,
			"Teacher": teacher,
		}
		if err := newRegistrationPage.Execute(w, data); err != nil {
			return &appError{err, "An error occurred; please go back and try again.", http.StatusInternalServerError}
		}
		return nil
	}
	token := tokenVariable.Get(r).(*model.AdminXSRFToken)
	if err := registrationConfirm.Execute(w, map[string]interface{}{
		"XSRFToken": token.Token,
		"Class":     class,
	}); err != nil {
		return &appError{err, "An error occurred", http.StatusInternalServerError}
	}
	return nil
}
<|endoftext|>


<commit_before>package personal

import (
	"Yearning-go/src/engine"
	"Yearning-go/src/lib"
	"Yearning-go/src/model"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/cookieY/sqlx"
)

const (
	ORDER_POST_SUCCESS      = "工单已提交,请等待审核人审核！"
	ER_DB_CONNENT           = "数据库实例连接失败！请检查相关配置是否正确！"
	CUSTOM_INFO_SUCCESS     = "邮箱/真实姓名修改成功！刷新后显示最新信息!"
	CUSTOM_PASSWORD_SUCCESS = "密码修改成功！"
	ER_SQL_EMPTY            = "无查询语句！"
	BUF                     = 1<<20 - 1
	ER_RPC                  = "rpc调用失败"
)

type queryBind struct {
	Table    string `json:"table"`
	DataBase string `json:"data_base"`
	Source   string `json:"source"`
}

type QueryDeal struct {
	Ref struct {
		Type      string `msgpack:"type"` //0 conn 1 close
		Sql       string `msgpack:"sql"`
		Schema    string `msgpack:"schema"`
		SourceId  string `msgpack:"source_id"`
		HeartBeat uint8  `msgpack:"heartbeat"`
	}
	MultiSQLRunner []MultiSQLRunner
}

type MultiSQLRunner struct {
	SQL              string
	InsulateWordList map[string]struct{}
}

type Query struct {
	Field []map[string]interface{} `msgpack:"field"`
	Data  []map[string]interface{} `msgpack:"data"`
}

type QueryArgs struct {
	SQL              string
	Limit            uint64
	InsulateWordList string
}

func (q *QueryDeal) PreCheck(insulateWordList string) error {
	var rs []engine.Record
	if client := lib.NewRpc(); client != nil {
		if err := client.Call("Engine.Query", &QueryArgs{
			SQL:              q.Ref.Sql,
			Limit:            model.GloOther.Limit,
			InsulateWordList: insulateWordList,
		}, &rs); err != nil {
			return err
		}
		for _, i := range rs {
			if i.Error != "" {
				return errors.New(i.Error)
			}
			q.MultiSQLRunner = append(q.MultiSQLRunner, MultiSQLRunner{SQL: i.SQL, InsulateWordList: lib.MapOn(i.InsulateWordList)})
		}
		return nil
	}
	return errors.New(ER_RPC)
}

func (m *MultiSQLRunner) Run(source *model.CoreDataSource, schema string) (*Query, error) {
	query := new(Query)
	db, err := sqlx.Connect("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4", source.Username, lib.Decrypt(source.Password), source.IP, source.Port, schema))
	if err != nil {
		return nil, err
	}

	defer db.Close()

	rows, err := db.Queryx(m.SQL)

	if err != nil {
		return nil, err
	}

	cols, err := rows.Columns()

	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		results := make(map[string]interface{})
		_ = rows.MapScan(results)
		for key := range results {
			switch r := results[key].(type) {
			case []uint8:
				if len(r) > BUF {
					results[key] = "blob字段无法显示"
				} else {
					switch hex.EncodeToString(r) {
					case "01":
						results[key] = "true"
					case "00":
						results[key] = "false"
					default:
						results[key] = string(r)
					}
					if m.excludeFieldContext(key) {
						results[key] = "****脱敏字段"
					}
				}
			}
		}
		query.Data = append(query.Data, results)
	}

	ele := removeDuplicateElement(cols)

	for cv := range ele {
		query.Field = append(query.Field, map[string]interface{}{"title": ele[cv], "dataIndex": ele[cv], "width": 200, "resizable": true})
	}
	query.Field[0]["fixed"] = "left"

	return query, nil
}

func (m *MultiSQLRunner) excludeFieldContext(field string) bool {
	_, ok := m.InsulateWordList[field]
	return ok
}

func removeDuplicateElement(addrs []string) []string {
	result := make([]string, 0, len(addrs))
	temp := map[string]struct{}{}
	idx := 0
	for _, item := range addrs {
		if _, ok := temp[item]; !ok {
			temp[item] = struct{}{}
			result = append(result, item)
		} else {
			idx++
			item += fmt.Sprintf("(%v)", idx)
			result = append(result, item)
		}
	}
	return result
}
<commit_msg>新增查询省略显示<commit_after>package personal

import (
	"Yearning-go/src/engine"
	"Yearning-go/src/lib"
	"Yearning-go/src/model"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/cookieY/sqlx"
)

const (
	ORDER_POST_SUCCESS      = "工单已提交,请等待审核人审核！"
	ER_DB_CONNENT           = "数据库实例连接失败！请检查相关配置是否正确！"
	CUSTOM_INFO_SUCCESS     = "邮箱/真实姓名修改成功！刷新后显示最新信息!"
	CUSTOM_PASSWORD_SUCCESS = "密码修改成功！"
	ER_SQL_EMPTY            = "无查询语句！"
	BUF                     = 1<<20 - 1
	ER_RPC                  = "rpc调用失败"
)

type queryBind struct {
	Table    string `json:"table"`
	DataBase string `json:"data_base"`
	Source   string `json:"source"`
}

type QueryDeal struct {
	Ref struct {
		Type      string `msgpack:"type"` //0 conn 1 close
		Sql       string `msgpack:"sql"`
		Schema    string `msgpack:"schema"`
		SourceId  string `msgpack:"source_id"`
		HeartBeat uint8  `msgpack:"heartbeat"`
	}
	MultiSQLRunner []MultiSQLRunner
}

type MultiSQLRunner struct {
	SQL              string
	InsulateWordList map[string]struct{}
}

type Query struct {
	Field []map[string]interface{} `msgpack:"field"`
	Data  []map[string]interface{} `msgpack:"data"`
}

type QueryArgs struct {
	SQL              string
	Limit            uint64
	InsulateWordList string
}

func (q *QueryDeal) PreCheck(insulateWordList string) error {
	var rs []engine.Record
	if client := lib.NewRpc(); client != nil {
		if err := client.Call("Engine.Query", &QueryArgs{
			SQL:              q.Ref.Sql,
			Limit:            model.GloOther.Limit,
			InsulateWordList: insulateWordList,
		}, &rs); err != nil {
			return err
		}
		for _, i := range rs {
			if i.Error != "" {
				return errors.New(i.Error)
			}
			q.MultiSQLRunner = append(q.MultiSQLRunner, MultiSQLRunner{SQL: i.SQL, InsulateWordList: lib.MapOn(i.InsulateWordList)})
		}
		return nil
	}
	return errors.New(ER_RPC)
}

func (m *MultiSQLRunner) Run(source *model.CoreDataSource, schema string) (*Query, error) {
	query := new(Query)
	db, err := sqlx.Connect("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4", source.Username, lib.Decrypt(source.Password), source.IP, source.Port, schema))
	if err != nil {
		return nil, err
	}

	defer db.Close()

	rows, err := db.Queryx(m.SQL)

	if err != nil {
		return nil, err
	}

	cols, err := rows.Columns()

	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		results := make(map[string]interface{})
		_ = rows.MapScan(results)
		for key := range results {
			switch r := results[key].(type) {
			case []uint8:
				if len(r) > BUF {
					results[key] = "blob字段无法显示"
				} else {
					switch hex.EncodeToString(r) {
					case "01":
						results[key] = "true"
					case "00":
						results[key] = "false"
					default:
						results[key] = string(r)
					}
					if m.excludeFieldContext(key) {
						results[key] = "****脱敏字段"
					}
				}
			}
		}
		query.Data = append(query.Data, results)
	}

	ele := removeDuplicateElement(cols)

	for cv := range ele {
		query.Field = append(query.Field, map[string]interface{}{"title": ele[cv], "dataIndex": ele[cv], "width": 200, "resizable": true, "ellipsis": true})
	}
	query.Field[0]["fixed"] = "left"

	return query, nil
}

func (m *MultiSQLRunner) excludeFieldContext(field string) bool {
	_, ok := m.InsulateWordList[field]
	return ok
}

func removeDuplicateElement(addrs []string) []string {
	result := make([]string, 0, len(addrs))
	temp := map[string]struct{}{}
	idx := 0
	for _, item := range addrs {
		if _, ok := temp[item]; !ok {
			temp[item] = struct{}{}
			result = append(result, item)
		} else {
			idx++
			item += fmt.Sprintf("(%v)", idx)
			result = append(result, item)
		}
	}
	return result
}
<|endoftext|>


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

package reports

import (
	"context"
	"fmt"
	"strconv"
	"time"

	"github.com/trackit/jsonlog"

	"github.com/trackit/trackit/aws"
	"github.com/trackit/trackit/db"
)

// GenerateReport will generate a spreadsheet report for a given AWS account and for a given month
// It will iterate over available modules and generate a sheet for each module.
// Note: First sheet is removed since it is unused (Created by excelize)
// Report is then uploaded to an S3 bucket
// Note: File can be saved locally by using `saveSpreadsheetLocally` instead of `saveSpreadsheet`
func GenerateReport(ctx context.Context, aa aws.AwsAccount, aas []aws.AwsAccount, date time.Time) (errs map[string]error) {
	logger := jsonlog.LoggerFromContextOrDefault(ctx)
	now := time.Now()
	var reportType spreadsheetType
	var reportDate string
	if date.IsZero() {
		reportDate = fmt.Sprintf("%s%s", (now.Month() - 1).String(), strconv.Itoa(now.Year()))
	} else {
		reportDate = fmt.Sprintf("%s%s", (date.Month()).String(), strconv.Itoa(date.Year()))
	}
	if aas == nil {
		aas = []aws.AwsAccount{aa}
		reportType = RegularReport
		logger.Info("Generating spreadsheet for account", map[string]interface{}{
			"account": aa,
			"date":    reportDate,
		})
	} else {
		reportType = MasterReport
		logger.Info("Generating spreadsheet for accounts", map[string]interface{}{
			"masterAccount": aa,
			"accounts":      aas,
			"date":          reportDate,
		})
	}
	errs = make(map[string]error)
	file := createSpreadsheet(aa, reportDate)
	if tx, err := db.Db.BeginTx(ctx, nil); err == nil {
		for _, module := range modules {
			err := module.GenerateSheet(ctx, aas, date, tx, file.File)
			if err != nil {
				errs[module.ErrorName] = err
			}
		}
		file.File.DeleteSheet(file.File.GetSheetName(1))
		errs["speadsheetError"] = saveSpreadsheet(ctx, file, reportType)
	} else {
		errs["speadsheetError"] = err
	}
	return
}

// GenerateTagsReport will generate a spreadsheet tags report for a given AWS account and for a given month
// It will iterate over available modules and generate a sheet for each module.
// Note: First sheet is removed since it is unused (Created by excelize)
// Report is then uploaded to an S3 bucket
// Note: File can be saved locally by using `saveSpreadsheetLocally` instead of `saveSpreadsheet`
func GenerateTagsReport(ctx context.Context, aa aws.AwsAccount, aas []aws.AwsAccount, date time.Time) (errs map[string]error) {
	logger := jsonlog.LoggerFromContextOrDefault(ctx)
	now := time.Now()
	var reportDate string
	if date.IsZero() {
		reportDate = fmt.Sprintf("%s%s", (now.Month() - 1).String(), strconv.Itoa(now.Year()))
	} else {
		reportDate = fmt.Sprintf("%s%s", (date.Month()).String(), strconv.Itoa(date.Year()))
	}
	if aas == nil {
		aas = []aws.AwsAccount{aa}
		logger.Info("Generating spreadsheet tags for account", map[string]interface{}{
			"account": aa,
			"date":    reportDate,
		})
	} else {
		logger.Info("Generating spreadsheet tags for accounts", map[string]interface{}{
			"masterAccount": aa,
			"accounts":      aas,
			"date":          reportDate,
		})
	}
	errs = make(map[string]error)
	file := createSpreadsheet(aa, reportDate)
	if tx, err := db.Db.BeginTx(ctx, nil); err == nil {
		err := generateTagsUsageReportSheet(ctx, aas, date, tx, file.File)
		if err != nil {
			errs["tagsError"] = err
		}
		file.File.DeleteSheet(file.File.GetSheetName(1))
		errs["speadsheetError"] = saveSpreadsheet(ctx, file, TagsReport)
	} else {
		errs["speadsheetError"] = err
	}
	return
}
<commit_msg>fix report names bug due to the new year<commit_after>//   Copyright 2019 MSolution.IO
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package reports

import (
	"context"
	"fmt"
	"strconv"
	"time"

	"github.com/trackit/jsonlog"

	"github.com/trackit/trackit/aws"
	"github.com/trackit/trackit/db"
)

// GenerateReport will generate a spreadsheet report for a given AWS account and for a given month
// It will iterate over available modules and generate a sheet for each module.
// Note: First sheet is removed since it is unused (Created by excelize)
// Report is then uploaded to an S3 bucket
// Note: File can be saved locally by using `saveSpreadsheetLocally` instead of `saveSpreadsheet`
func GenerateReport(ctx context.Context, aa aws.AwsAccount, aas []aws.AwsAccount, date time.Time) (errs map[string]error) {
	logger := jsonlog.LoggerFromContextOrDefault(ctx)
	now := time.Now()
	var reportType spreadsheetType
	var reportDate string
	if date.IsZero() {
		if now.Month() != time.January {
			reportDate = fmt.Sprintf("%s%s", (now.Month() - 1).String(), strconv.Itoa(now.Year()))
		} else {
			reportDate = fmt.Sprintf("%s%s", time.December.String(), strconv.Itoa(now.Year() - 1))
		}
	} else {
		reportDate = fmt.Sprintf("%s%s", (date.Month()).String(), strconv.Itoa(date.Year()))
	}
	if aas == nil {
		aas = []aws.AwsAccount{aa}
		reportType = RegularReport
		logger.Info("Generating spreadsheet for account", map[string]interface{}{
			"account": aa,
			"date":    reportDate,
		})
	} else {
		reportType = MasterReport
		logger.Info("Generating spreadsheet for accounts", map[string]interface{}{
			"masterAccount": aa,
			"accounts":      aas,
			"date":          reportDate,
		})
	}
	errs = make(map[string]error)
	file := createSpreadsheet(aa, reportDate)
	if tx, err := db.Db.BeginTx(ctx, nil); err == nil {
		for _, module := range modules {
			err := module.GenerateSheet(ctx, aas, date, tx, file.File)
			if err != nil {
				errs[module.ErrorName] = err
			}
		}
		file.File.DeleteSheet(file.File.GetSheetName(1))
		errs["speadsheetError"] = saveSpreadsheet(ctx, file, reportType)
	} else {
		errs["speadsheetError"] = err
	}
	return
}

// GenerateTagsReport will generate a spreadsheet tags report for a given AWS account and for a given month
// It will iterate over available modules and generate a sheet for each module.
// Note: First sheet is removed since it is unused (Created by excelize)
// Report is then uploaded to an S3 bucket
// Note: File can be saved locally by using `saveSpreadsheetLocally` instead of `saveSpreadsheet`
func GenerateTagsReport(ctx context.Context, aa aws.AwsAccount, aas []aws.AwsAccount, date time.Time) (errs map[string]error) {
	logger := jsonlog.LoggerFromContextOrDefault(ctx)
	now := time.Now()
	var reportDate string
	if date.IsZero() {
		if now.Month() != time.January {
			reportDate = fmt.Sprintf("%s%s", (now.Month() - 1).String(), strconv.Itoa(now.Year()))
		} else {
			reportDate = fmt.Sprintf("%s%s", time.December.String(), strconv.Itoa(now.Year() - 1))
		}
	} else {
		reportDate = fmt.Sprintf("%s%s", (date.Month()).String(), strconv.Itoa(date.Year()))
	}
	if aas == nil {
		aas = []aws.AwsAccount{aa}
		logger.Info("Generating spreadsheet tags for account", map[string]interface{}{
			"account": aa,
			"date":    reportDate,
		})
	} else {
		logger.Info("Generating spreadsheet tags for accounts", map[string]interface{}{
			"masterAccount": aa,
			"accounts":      aas,
			"date":          reportDate,
		})
	}
	errs = make(map[string]error)
	file := createSpreadsheet(aa, reportDate)
	if tx, err := db.Db.BeginTx(ctx, nil); err == nil {
		err := generateTagsUsageReportSheet(ctx, aas, date, tx, file.File)
		if err != nil {
			errs["tagsError"] = err
		}
		file.File.DeleteSheet(file.File.GetSheetName(1))
		errs["speadsheetError"] = saveSpreadsheet(ctx, file, TagsReport)
	} else {
		errs["speadsheetError"] = err
	}
	return
}
<|endoftext|>


<commit_before>package repositories

import (
  "encoding/json"
  "fmt"
  "github.com/ricallinson/forgery"
  "github.com/spacedock-io/registry/models"
)

func GetTags(req *f.Request, res *f.Response) {
  namespace := req.Params["namespace"]
  repo := req.Params["repo"]
  tags, err := models.GetTags(namespace, repo)
  if err != nil {
    res.Send(err.Error(), 400)
    return
  }

  json, jsonErr := json.Marshal(tags)
  if jsonErr != nil {
    res.Send("Error sending data", 400)
    return
  }
  res.Send(json, 200)
}

func GetTag(req *f.Request, res *f.Response) {
  namespace := req.Params["namespace"]
  repo := req.Params["repo"]
  tag := req.Params["tag"]

  t, err := models.GetTag(namespace, repo, tag)
  if err != nil {
    res.Send(err.Error(), 400)
    return
  }

  json, jsonErr := json.Marshal(t)
  if jsonErr != nil {
    res.Send("Error sending data", 400)
    return
  }
  res.Send(json, 200)
}

func CreateTag(req *f.Request, res *f.Response) {
  namespace := req.Params["namespace"]
  repo := req.Params["repo"]
  tag := req.Params["tag"]
  // json := req.Map["json"].(map[string]interface{})
  // uuid, _ := json["uuid"].(string)

  fmt.Println("Creating tag")
  fmt.Printf("ns: %s, repo: %s, tag: %s\n", namespace, repo, tag)

  // err := models.CreateTag(namespace, repo, tag, uuid)
  // if err != nil {
  //   res.Send(err.Error(), 400)
  //   return
  // }

  res.Send("", 200)
}
<commit_msg>Try changing 400 status to 404<commit_after>package repositories

import (
  "encoding/json"
  "fmt"
  "github.com/ricallinson/forgery"
  "github.com/spacedock-io/registry/models"
)

func GetTags(req *f.Request, res *f.Response) {
  namespace := req.Params["namespace"]
  repo := req.Params["repo"]
  tags, err := models.GetTags(namespace, repo)
  if err != nil {
    res.Send(err.Error(), 404)
    return
  }

  json, jsonErr := json.Marshal(tags)
  if jsonErr != nil {
    res.Send("Error sending data", 404)
    return
  }
  res.Send(json, 200)
}

func GetTag(req *f.Request, res *f.Response) {
  namespace := req.Params["namespace"]
  repo := req.Params["repo"]
  tag := req.Params["tag"]

  t, err := models.GetTag(namespace, repo, tag)
  if err != nil {
    res.Send(err.Error(), 404)
    return
  }

  json, jsonErr := json.Marshal(t)
  if jsonErr != nil {
    res.Send("Error sending data", 404)
    return
  }
  res.Send(json, 200)
}

func CreateTag(req *f.Request, res *f.Response) {
  namespace := req.Params["namespace"]
  repo := req.Params["repo"]
  tag := req.Params["tag"]
  // json := req.Map["json"].(map[string]interface{})
  // uuid, _ := json["uuid"].(string)

  fmt.Println("Creating tag")
  fmt.Printf("ns: %s, repo: %s, tag: %s\n", namespace, repo, tag)

  // err := models.CreateTag(namespace, repo, tag, uuid)
  // if err != nil {
  //   res.Send(err.Error(), 400)
  //   return
  // }

  res.Send("", 200)
}
<|endoftext|>


<commit_before>package parser

import (
	"fmt"
	"net/http"
	"strings"

	"golang.org/x/net/html"
)

type Content struct {
	URL				   string
	HTMLTitle          string
	TwitterTitle       string
	OGTitle            string
	HDLTitle           string
	Description        string
	OGDescription      string
	LPDescription      string
	TwitterDescription string
}

func getUrlContent(urls []string) string {
	// TODO: figure out what to do with non-HTML content such as PDF, images, ...
	var returnText string

	for _, url := range urls {
		var content Content
		content.URL = url

		// open connection & get data
		resp, err := http.Get(url)
		defer resp.Body.Close()
		if err != nil {
			fmt.Printf("Couldn't load %s: %s", url, err)
			continue
		}

		z := html.NewTokenizer(resp.Body)
		for {
			tt := z.Next()
			// End of the document
			if tt == html.ErrorToken {
				break
			}
			if tt == html.StartTagToken || tt == html.SelfClosingTagToken || tt == html.EndTagToken {
				tagName, hasAttr := z.TagName()

				// parse title tag
				if strings.ToLower(string(tagName)) == "title" {
					// if its a title tag the next entry will be the text
					// TODO: check if this works with <title>acd</title>
					z.Next()
					content.HTMLTitle = strings.Trim(z.Token().String(), " \t\n\r")
				} // if title

				// Parse meta tags
				if strings.ToLower(string(tagName)) == "meta" {
					var bKey, bVal []byte
					keys := make(map[string]string)
					// repeat until z.TagAttr reports that there are no more
					// attributes left (hasAttr = false)
					for hasAttr {
						bKey, bVal, hasAttr = z.TagAttr()
						keys[string(bKey)] = string(bVal)
					}
					content = getMetaInformation(content, keys)
				} // if meta
			} // if html.StartTagToken

		} // for html.NewTokenizer

		returnText += buildHTMLblock(content)
	} // for _, url
	return returnText
}

func getMetaInformation(content Content, keys map[string]string) Content {
	// Assign now all the various values we will find in keys to the content object

	// Description: <meta itemprop="description" name="description" content="...">
	if keys["name"] == "description" {
		content.Description = keys["content"]
	}
	// LPDescription: <meta name="lp" content="..." />
	if keys["name"] == "lp" {
		content.LPDescription = keys["content"]
	}
	// TwitterDescription: <meta property="twitter:description" content="..." />
	if keys["property"] == "twitter:description" || keys["name"] == "twitter:description" {
		content.TwitterDescription = keys["content"]
	}
	// OGDescription: <meta content="..." property="og:description" />
	if keys["property"] == "og:description" {
		content.OGDescription = keys["content"]
	}
	// TwitterTitle: <meta content="..." property="twitter:title" />
	if keys["property"] == "twitter:title" || keys["name"] == "twitter:title" {
		content.TwitterTitle = keys["content"]
	}
	// OGTitle: <meta content="..." property="og:title" />
	if keys["property"] == "og:title" {
		content.OGTitle = keys["content"]
	}
	// HDLTitle: <meta name="hdl" content"..." />
	if keys["name"] == "hdl" {
		content.HDLTitle = keys["content"]
	}

	return content
}

func buildHTMLblock(content Content) string {
	var title, description string

	// get the title
	if len(content.TwitterTitle) > 0 {
		title = content.TwitterTitle
	} else if len(content.OGTitle) > 0 {
		title = content.OGTitle
	} else if len(content.HDLTitle) > 0 {
		title = content.HDLTitle
	} else {
		title = content.HTMLTitle
	}

	// get the description
	if len(content.TwitterDescription) > 0 {
		description = content.TwitterDescription
	} else if len(content.OGDescription) > 0 {
		description = content.OGDescription
	} else if len(content.LPDescription) > 0 {
		description = content.LPDescription
	} else {
		description = content.Description
	}

	title = "" + title + "
\n"
	footer := "
" + content.URL + "\n"

	return "
\n" + title + description + footer + "
\n"
}
<commit_msg>parse media header and add it to feed item<commit_after>package parser

import (
	"fmt"
	"net/http"
	"strings"

	"golang.org/x/net/html"
)

// TODO: there is also "<link itemprop=embedURL ..." but its much more
//		 difficult to parse since the hight & width value can occure multiple
//		 times if you look at the youtube html code
type Content struct {
	URL				   string
	HTMLTitle          string
	TwitterTitle       string
	OGTitle            string
	HDLTitle           string
	Description        string
	OGDescription      string
	LPDescription      string
	TwitterDescription string
	TwitterPlayer	string
	TwitterPlayerWidth string
	TwitterPlayerHeight string
	OGVideoURL string
	OGVideoWidth string
	OGVideoHeight string
}

func getUrlContent(urls []string) string {
	// TODO: figure out what to do with non-HTML content such as PDF, images, ...
	var returnText string

	for _, url := range urls {
		var content Content
		content.URL = url

		// open connection & get data
		resp, err := http.Get(url)
		defer resp.Body.Close()
		if err != nil {
			fmt.Printf("Couldn't load %s: %s", url, err)
			continue
		}

		z := html.NewTokenizer(resp.Body)
		for {
			tt := z.Next()
			// End of the document
			if tt == html.ErrorToken {
				break
			}
			if tt == html.StartTagToken || tt == html.SelfClosingTagToken || tt == html.EndTagToken {
				tagName, hasAttr := z.TagName()

				// parse title tag
				if strings.ToLower(string(tagName)) == "title" {
					// if its a title tag the next entry will be the text
					// TODO: check if this works with <title>acd</title>
					z.Next()
					content.HTMLTitle = strings.Trim(z.Token().String(), " \t\n\r")
				} // if title

				// Parse meta tags
				if strings.ToLower(string(tagName)) == "meta" {
					var bKey, bVal []byte
					keys := make(map[string]string)
					// repeat until z.TagAttr reports that there are no more
					// attributes left (hasAttr = false)
					for hasAttr {
						bKey, bVal, hasAttr = z.TagAttr()
						keys[string(bKey)] = string(bVal)
					}
					content = getMetaInformation(content, keys)
				} // if meta
			} // if html.StartTagToken

		} // for html.NewTokenizer

		returnText += buildHTMLblock(content)
	} // for _, url
	return returnText
}

func getMetaInformation(content Content, keys map[string]string) Content {
	// Assign now all the various values we will find in keys to the content object

	// Description: <meta itemprop="description" name="description" content="...">
	if keys["name"] == "description" {
		content.Description = keys["content"]
	}
	// LPDescription: <meta name="lp" content="..." />
	if keys["name"] == "lp" {
		content.LPDescription = keys["content"]
	}
	// TwitterDescription: <meta property="twitter:description" content="..." />
	if keys["property"] == "twitter:description" || keys["name"] == "twitter:description" {
		content.TwitterDescription = keys["content"]
	}
	// OGDescription: <meta content="..." property="og:description" />
	if keys["property"] == "og:description" {
		content.OGDescription = keys["content"]
	}
	// TwitterTitle: <meta content="..." property="twitter:title" />
	if keys["property"] == "twitter:title" || keys["name"] == "twitter:title" {
		content.TwitterTitle = keys["content"]
	}
	// OGTitle: <meta content="..." property="og:title" />
	if keys["property"] == "og:title" {
		content.OGTitle = keys["content"]
	}
	// HDLTitle: <meta name="hdl" content"..." />
	if keys["name"] == "hdl" {
		content.HDLTitle = keys["content"]
	}
	// TwitterPlayer: <meta name="twitter:player" content="...">
	if keys["name"] == "twitter:player" {
		content.TwitterPlayer = keys["content"]
	}
	// TwitterPlayerWidth: <meta name="twitter:player:width" content="...">
	if keys["name"] == "twitter:player:width" {
		content.TwitterPlayerWidth = keys["content"]
	}
	// TwitterPlayerHeight: <meta name="twitter:player:height" content="...">
	if keys["name"] == "twitter:player:height" {
		content.TwitterPlayerHeight = keys["content"]
	}
	// OGVideoURL: <meta property="og:video:url" content="...">
	if keys["property"] == "og:video:url" {
		content.OGVideoURL = keys["content"]
	}
	// OGVideoWidth: <meta property="og:video:width" content="...">
	if keys["property"] == "og:video:width" {
		content.OGVideoWidth = keys["content"]
	}
	// OGVideoHeight: <meta property="og:video:height" content="...">
	if keys["property"] == "og:video:height" {
		content.OGVideoHeight = keys["content"]
	}

	return content
}

func buildHTMLblock(content Content) string {
	var title, description, mediaText string
	var media, mediaWidth, mediaHeight string

	// get the title
	if len(content.TwitterTitle) > 0 {
		title = content.TwitterTitle
	} else if len(content.OGTitle) > 0 {
		title = content.OGTitle
	} else if len(content.HDLTitle) > 0 {
		title = content.HDLTitle
	} else {
		title = content.HTMLTitle
	}

	// get the description
	if len(content.TwitterDescription) > 0 {
		description = content.TwitterDescription
	} else if len(content.OGDescription) > 0 {
		description = content.OGDescription
	} else if len(content.LPDescription) > 0 {
		description = content.LPDescription
	} else {
		description = content.Description
	}

	// get the media/video
	if len(content.TwitterPlayer) > 0 {
		media = content.TwitterPlayer
	} else if len(content.OGVideoURL) > 0 {
		media = content.OGVideoURL
	}
	// get media width
	if len(content.TwitterPlayerWidth) > 0 {
		mediaWidth = content.TwitterPlayerWidth
	} else if len(content.OGVideoWidth) > 0 {
		mediaWidth = content.OGVideoWidth
	}

	// get media height
	if len(content.TwitterPlayerHeight) > 0 {
		mediaHeight = content.TwitterPlayerHeight
	} else if len(content.OGVideoHeight) > 0 {
		mediaHeight = content.OGVideoHeight
	}

	if len(media) > 0 && len(mediaWidth) >0 && len(mediaHeight) > 0 {
		mediaText = "\n"
	}
	title = "" + title + "
\n"
	footer := "
" + content.URL + "\n"

	return "
\n" + title + description + footer + mediaText + "
\n"
}
<|endoftext|>


<commit_before>package fileformat

import (
	"bytes"
	"encoding/binary"
	"fmt"
)

var vtypers map[string]valueTyper

func init() {
	vtypers = map[string]valueTyper{
		"string": func(s interface{}) ([]byte, Tag_ValueType, error) {
			return []byte(s.(string)), Tag_STRING, nil
		},
		"float64": func(f interface{}) ([]byte, Tag_ValueType, error) {
			var (
				v   float64
				buf bytes.Buffer
			)
			err := binary.Write(&buf, binary.LittleEndian, v)
			return buf.Bytes(), Tag_DOUBLE, err
		},
	}
}

type valueTyper func(interface{}) ([]byte, Tag_ValueType, error)

func ValueType(i interface{}) ([]byte, Tag_ValueType, error) {
	t := fmt.Sprintf("%T", i)
	vt, ok := vtypers[t]
	if !ok {
		return nil, Tag_STRING, fmt.Errorf("unknown type: %s", t)
	}
	return vt(i)
}

// KeyValue retrieves key and value from a Tag.
func KeyValue(t *Tag) (string, interface{}, error) {
	switch t.GetType() {
	case Tag_STRING:
		return t.Key, string(t.GetValue()), nil
	case Tag_DOUBLE:
		var (
			buf = bytes.NewBuffer(t.GetValue())
			f   float64
		)
		err := binary.Read(buf, binary.LittleEndian, &f)
		return t.Key, f, err
	default:
		// TODO
		return t.Key, nil, nil
	}
}
<commit_msg>cugdf: support int<commit_after>package fileformat

import (
	"bytes"
	"encoding/binary"
	"fmt"
)

var vtypers map[string]valueTyper

func init() {
	vtypers = map[string]valueTyper{
		"string": func(s interface{}) ([]byte, Tag_ValueType, error) {
			return []byte(s.(string)), Tag_STRING, nil
		},
		"int": func(s interface{}) ([]byte, Tag_ValueType, error) {
			var buf = make([]byte, 8)
			binary.LittleEndian.PutUint64(buf, uint64(s.(int)))
			return buf, Tag_INT, nil
		},
		"float64": func(f interface{}) ([]byte, Tag_ValueType, error) {
			var (
				v   float64
				buf bytes.Buffer
			)
			// TODO: replace with math.Float64fromBits
			err := binary.Write(&buf, binary.LittleEndian, v)
			return buf.Bytes(), Tag_DOUBLE, err
		},
	}
}

type valueTyper func(interface{}) ([]byte, Tag_ValueType, error)

func ValueType(i interface{}) ([]byte, Tag_ValueType, error) {
	t := fmt.Sprintf("%T", i)
	vt, ok := vtypers[t]
	if !ok {
		return nil, Tag_STRING, fmt.Errorf("unknown type: %s", t)
	}
	return vt(i)
}

// KeyValue retrieves key and value from a Tag.
func KeyValue(t *Tag) (string, interface{}, error) {
	switch t.GetType() {
	case Tag_STRING:
		return t.Key, string(t.GetValue()), nil
	case Tag_INT:
		return t.Key, int(binary.LittleEndian.Uint64(t.GetValue())), nil
	case Tag_DOUBLE:
		var (
			buf = bytes.NewBuffer(t.GetValue())
			f   float64
		)
		err := binary.Read(buf, binary.LittleEndian, &f)
		return t.Key, f, err
	default:
		// TODO
		return t.Key, nil, nil
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2011 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"bytes"
	"fmt"
	"http"
	"io"
	"image"
	"image/jpeg"
	"image/png"
	"json"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"camli/blobref"
	"camli/blobserver"
	"camli/httputil"
	"camli/jsonconfig"
	"camli/misc/resize"
	"camli/misc/vfs" // TODO: ditch this once pkg http gets it
	"camli/schema"
	uistatic "camlistore.org/server/uistatic"
)

var _ = log.Printf

var staticFilePattern = regexp.MustCompile(`^([a-zA-Z0-9\-\_]+\.(html|js|css|png|jpg|gif))$`)
var identPattern = regexp.MustCompile(`^[a-zA-Z\_]+$`)

var uiFiles = uistatic.Files

// Download URL suffix:
//   $1: blobref (checked in download handler)
//   $2: optional "/filename" to be sent as recommended download name,
//       if sane looking
var downloadPattern = regexp.MustCompile(`^download/([^/]+)(/.*)?$`)
var thumbnailPattern = regexp.MustCompile(`^thumbnail/([^/]+)(/.*)?$`)

// UIHandler handles serving the UI and discovery JSON.
type UIHandler struct {
	// URL prefixes (path or full URL) to the primary blob and
	// search root.  Only used by the UI and thus necessary if UI
	// is true.
	BlobRoot     string
	SearchRoot   string
	JSONSignRoot string

	FilesDir string

	Storage blobserver.Storage // of BlobRoot
	Cache   blobserver.Storage // or nil
}

func defaultFilesDir() string {
	dir, _ := filepath.Split(os.Args[0])
	return filepath.Join(dir, "ui")
}

func init() {
	blobserver.RegisterHandlerConstructor("ui", newUiFromConfig)
}

func newUiFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err os.Error) {
	ui := &UIHandler{}
	ui.BlobRoot = conf.OptionalString("blobRoot", "")
	ui.SearchRoot = conf.OptionalString("searchRoot", "")
	ui.JSONSignRoot = conf.OptionalString("jsonSignRoot", "")
	cachePrefix := conf.OptionalString("cache", "")
	ui.FilesDir = conf.OptionalString("staticFiles", defaultFilesDir())
	if err = conf.Validate(); err != nil {
		return
	}

	checkType := func(key string, htype string) {
		v := conf.OptionalString(key, "")
		if v == "" {
			return
		}
		ct := ld.GetHandlerType(v)
		if ct == "" {
			err = fmt.Errorf("UI handler's %q references non-existant %q", key, v)
		} else if ct != htype {
			err = fmt.Errorf("UI handler's %q references %q of type %q; expected type %q", key, v,
				ct, htype)
		}
	}
	checkType("searchRoot", "search")
	checkType("jsonSignRoot", "jsonsign")
	if err != nil {
		return
	}

	if ui.BlobRoot != "" {
		bs, err := ld.GetStorage(ui.BlobRoot)
		if err != nil {
			return nil, fmt.Errorf("UI handler's blobRoot of %q error: %v", ui.BlobRoot, err)
		}
		ui.Storage = bs
	}

	if cachePrefix != "" {
		bs, err := ld.GetStorage(cachePrefix)
		if err != nil {
			return nil, fmt.Errorf("UI handler's cache of %q error: %v", cachePrefix, err)
		}
		ui.Cache = bs
	}

	fi, sterr := os.Stat(ui.FilesDir)
	if sterr != nil || !fi.IsDirectory() {
		err = fmt.Errorf("UI handler's \"staticFiles\" of %q is invalid", ui.FilesDir)
		return
	}
	return ui, nil
}

func camliMode(req *http.Request) string {
	// TODO-GO: this is too hard to get at the GET Query args on a
	// POST request.
	m, err := http.ParseQuery(req.URL.RawQuery)
	if err != nil {
		return ""
	}
	if mode, ok := m["camli.mode"]; ok && len(mode) > 0 {
		return mode[0]
	}
	return ""
}

func wantsDiscovery(req *http.Request) bool {
	return req.Method == "GET" &&
		(req.Header.Get("Accept") == "text/x-camli-configuration" ||
			camliMode(req) == "config")
}

func wantsUploadHelper(req *http.Request) bool {
	return req.Method == "POST" && camliMode(req) == "uploadhelper"
}

func wantsPermanode(req *http.Request) bool {
	return req.Method == "GET" && blobref.Parse(req.FormValue("p")) != nil
}

func wantsBlobInfo(req *http.Request) bool {
	return req.Method == "GET" && blobref.Parse(req.FormValue("b")) != nil
}

func (ui *UIHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	base := req.Header.Get("X-PrefixHandler-PathBase")
	suffix := req.Header.Get("X-PrefixHandler-PathSuffix")

	rw.Header().Set("Vary", "Accept")
	switch {
	case wantsDiscovery(req):
		ui.serveDiscovery(rw, req)
	case wantsUploadHelper(req):
		ui.serveUploadHelper(rw, req)
	case strings.HasPrefix(suffix, "download/"):
		ui.serveDownload(rw, req)
	case strings.HasPrefix(suffix, "thumbnail/"):
		ui.serveThumbnail(rw, req)
	default:
		file := ""
		if m := staticFilePattern.FindStringSubmatch(suffix); m != nil {
			file = m[1]
		} else {
			switch {
			case wantsPermanode(req):
				file = "permanode.html"
			case wantsBlobInfo(req):
				file = "blobinfo.html"
			case req.URL.Path == base:
				file = "index.html"
			default:
				http.Error(rw, "Illegal URL.", 404)
				return
			}
		}
		vfs.ServeFileFromFS(rw, req, uiFiles, file)
	}
}

func (ui *UIHandler) serveDiscovery(rw http.ResponseWriter, req *http.Request) {
	rw.Header().Set("Content-Type", "text/javascript")
	inCb := false
	if cb := req.FormValue("cb"); identPattern.MatchString(cb) {
		fmt.Fprintf(rw, "%s(", cb)
		inCb = true
	}
	bytes, _ := json.Marshal(map[string]interface{}{
		"blobRoot":     ui.BlobRoot,
		"searchRoot":   ui.SearchRoot,
		"jsonSignRoot": ui.JSONSignRoot,
		"uploadHelper": "?camli.mode=uploadhelper", // hack; remove with better javascript
		"downloadHelper": "./download/",
	})
	rw.Write(bytes)
	if inCb {
		rw.Write([]byte{')'})
	}
}

func (ui *UIHandler) storageSeekFetcher() (blobref.SeekFetcher, os.Error) {
	return blobref.SeekerFromStreamingFetcher(ui.Storage)
}

func (ui *UIHandler) serveDownload(rw http.ResponseWriter, req *http.Request) {
	if req.Method != "GET" && req.Method != "HEAD" {
		http.Error(rw, "Invalid download method", 400)
		return
	}
	if ui.Storage == nil {
		http.Error(rw, "No BlobRoot configured", 500)
		return
	}

	fetchSeeker, err := ui.storageSeekFetcher()
	if err != nil {
		http.Error(rw, err.String(), 500)
		return
	}

	suffix := req.Header.Get("X-PrefixHandler-PathSuffix")

	m := downloadPattern.FindStringSubmatch(suffix)
	if m == nil {
		httputil.ErrorRouting(rw, req)
		return
	}

	fbr := blobref.Parse(m[1])
	if fbr == nil {
		http.Error(rw, "Invalid blobref", 400)
		return
	}

	filename := m[2]
	if len(filename) > 0 {
		filename = filename[1:] // remove leading slash
	}

	fr, err := schema.NewFileReader(fetchSeeker, fbr)
	if err != nil {
		http.Error(rw, "Can't serve file: "+err.String(), 500)
		return
	}
	defer fr.Close()

	// TODO: fr.FileSchema() and guess a mime type?  For now:
	schema := fr.FileSchema()
	rw.Header().Set("Content-Type", "application/octet-stream")
	rw.Header().Set("Content-Length", fmt.Sprintf("%d", schema.Size))

	if req.Method == "HEAD" {
		vbr := blobref.Parse(req.FormValue("verifycontents"))
		if vbr == nil {
			return
		}
		hash := vbr.Hash()
		if hash == nil {
			return
		}
		io.Copy(hash, fr) // ignore errors, caught later
		if vbr.HashMatches(hash) {
			rw.Header().Set("X-Camli-Contents", vbr.String())
		}
		return
	}

	n, err := io.Copy(rw, fr)
	log.Printf("For %q request of %s: copied %d, %v", req.Method, req.URL.RawPath, n, err)
	if err != nil {
		log.Printf("error serving download of file schema %s: %v", fbr, err)
		return
	}
	if n != int64(schema.Size) {
		log.Printf("error serving download of file schema %s: sent %d, expected size of %d",
			fbr, n, schema.Size)
		return
	}
}

func (ui *UIHandler) serveThumbnail(rw http.ResponseWriter, req *http.Request) {
	if ui.Storage == nil {
		http.Error(rw, "No BlobRoot configured", 500)
		return
	}

	fetchSeeker, err := ui.storageSeekFetcher()
	if err != nil {
		http.Error(rw, err.String(), 500)
		return
	}

	suffix := req.Header.Get("X-PrefixHandler-PathSuffix")
	m := thumbnailPattern.FindStringSubmatch(suffix)
	if m == nil {
		httputil.ErrorRouting(rw, req)
		return
	}

	query := req.URL.Query()
	width, err := strconv.Atoi(query.Get("mw"))
	if err != nil {
		http.Error(rw, "Invalid specified max width 'mw': "+err.String(), 500)
		return
	}
	height, err := strconv.Atoi(query.Get("mh"))
	if err != nil {
		http.Error(rw, "Invalid specified height 'mh': "+err.String(), 500)
		return
	}

	blobref := blobref.Parse(m[1])
	if blobref == nil {
		http.Error(rw, "Invalid blobref", 400)
		return
	}

	filename := m[2]
	if len(filename) > 0 {
		filename = filename[1:] // remove leading slash
	}

	fr, err := schema.NewFileReader(fetchSeeker, blobref)
	if err != nil {
		http.Error(rw, "Can't serve file: "+err.String(), 500)
		return
	}

	var buf bytes.Buffer
	n, err := io.Copy(&buf, fr)
	i, format, err := image.Decode(&buf)
	if err != nil {
		http.Error(rw, "Can't serve file: "+err.String(), 500)
		return
	}
	b := i.Bounds()
	// only do downscaling, otherwise just serve the original image
	if width < b.Dx() || height < b.Dy() {
		const huge = 2400
		// If it's gigantic, it's more efficient to downsample first
		// and then resize; resizing will smooth out the roughness.
		// (trusting the moustachio guys on that one).
		if b.Dx() > huge || b.Dy() > huge {
			w, h := width * 2, height * 2
			if b.Dx() > b.Dy() {
				w = b.Dx() * h / b.Dy()
			} else {
				h = b.Dy() * w / b.Dx()
			}
			i = resize.Resample(i, i.Bounds(), w, h)
			b = i.Bounds()
		}
		// conserve proportions. use the smallest of the two as the decisive one.
		if width > height {
			width = b.Dx() * height / b.Dy()
		} else {
			height = b.Dy() * width / b.Dx()
		}
		i = resize.Resize(i, b, width, height)
		// Encode as a new image
		buf.Reset()
		switch format {
		case "jpeg":
			err = jpeg.Encode(&buf, i, nil)
		default:
			err = png.Encode(&buf, i)
		}
		if err != nil {
			http.Error(rw, "Can't serve file: "+err.String(), 500)
			return
		}
	}
	ct := ""
	switch format {
	case "jpeg":
		ct = "image/jpeg"
	default:
		ct = "image/png"
	}
	rw.Header().Set("Content-Type", ct)
	size := buf.Len()
	rw.Header().Set("Content-Length", fmt.Sprintf("%d", size))
	n, err = io.Copy(rw, &buf)
	if err != nil {
		log.Printf("error serving thumbnail of file schema %s: %v", blobref, err)
		return
	}
	if n != int64(size) {
		log.Printf("error serving thumbnail of file schema %s: sent %d, expected size of %d",
			blobref, n, size)
		return
	}
}
<commit_msg>remove UI staticFiles config option, no longer needed.<commit_after>/*
Copyright 2011 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"bytes"
	"fmt"
	"http"
	"io"
	"image"
	"image/jpeg"
	"image/png"
	"json"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"

	"camli/blobref"
	"camli/blobserver"
	"camli/httputil"
	"camli/jsonconfig"
	"camli/misc/resize"
	"camli/misc/vfs" // TODO: ditch this once pkg http gets it
	"camli/schema"
	uistatic "camlistore.org/server/uistatic"
)

var _ = log.Printf

var staticFilePattern = regexp.MustCompile(`^([a-zA-Z0-9\-\_]+\.(html|js|css|png|jpg|gif))$`)
var identPattern = regexp.MustCompile(`^[a-zA-Z\_]+$`)

var uiFiles = uistatic.Files

// Download URL suffix:
//   $1: blobref (checked in download handler)
//   $2: optional "/filename" to be sent as recommended download name,
//       if sane looking
var downloadPattern = regexp.MustCompile(`^download/([^/]+)(/.*)?$`)
var thumbnailPattern = regexp.MustCompile(`^thumbnail/([^/]+)(/.*)?$`)

// UIHandler handles serving the UI and discovery JSON.
type UIHandler struct {
	// URL prefixes (path or full URL) to the primary blob and
	// search root.  Only used by the UI and thus necessary if UI
	// is true.
	BlobRoot     string
	SearchRoot   string
	JSONSignRoot string

	Storage blobserver.Storage // of BlobRoot
	Cache   blobserver.Storage // or nil
}

func init() {
	blobserver.RegisterHandlerConstructor("ui", newUiFromConfig)
}

func newUiFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err os.Error) {
	ui := &UIHandler{}
	ui.BlobRoot = conf.OptionalString("blobRoot", "")
	ui.SearchRoot = conf.OptionalString("searchRoot", "")
	ui.JSONSignRoot = conf.OptionalString("jsonSignRoot", "")
	cachePrefix := conf.OptionalString("cache", "")
	if err = conf.Validate(); err != nil {
		return
	}

	checkType := func(key string, htype string) {
		v := conf.OptionalString(key, "")
		if v == "" {
			return
		}
		ct := ld.GetHandlerType(v)
		if ct == "" {
			err = fmt.Errorf("UI handler's %q references non-existant %q", key, v)
		} else if ct != htype {
			err = fmt.Errorf("UI handler's %q references %q of type %q; expected type %q", key, v,
				ct, htype)
		}
	}
	checkType("searchRoot", "search")
	checkType("jsonSignRoot", "jsonsign")
	if err != nil {
		return
	}

	if ui.BlobRoot != "" {
		bs, err := ld.GetStorage(ui.BlobRoot)
		if err != nil {
			return nil, fmt.Errorf("UI handler's blobRoot of %q error: %v", ui.BlobRoot, err)
		}
		ui.Storage = bs
	}

	if cachePrefix != "" {
		bs, err := ld.GetStorage(cachePrefix)
		if err != nil {
			return nil, fmt.Errorf("UI handler's cache of %q error: %v", cachePrefix, err)
		}
		ui.Cache = bs
	}

	return ui, nil
}

func camliMode(req *http.Request) string {
	// TODO-GO: this is too hard to get at the GET Query args on a
	// POST request.
	m, err := http.ParseQuery(req.URL.RawQuery)
	if err != nil {
		return ""
	}
	if mode, ok := m["camli.mode"]; ok && len(mode) > 0 {
		return mode[0]
	}
	return ""
}

func wantsDiscovery(req *http.Request) bool {
	return req.Method == "GET" &&
		(req.Header.Get("Accept") == "text/x-camli-configuration" ||
			camliMode(req) == "config")
}

func wantsUploadHelper(req *http.Request) bool {
	return req.Method == "POST" && camliMode(req) == "uploadhelper"
}

func wantsPermanode(req *http.Request) bool {
	return req.Method == "GET" && blobref.Parse(req.FormValue("p")) != nil
}

func wantsBlobInfo(req *http.Request) bool {
	return req.Method == "GET" && blobref.Parse(req.FormValue("b")) != nil
}

func (ui *UIHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	base := req.Header.Get("X-PrefixHandler-PathBase")
	suffix := req.Header.Get("X-PrefixHandler-PathSuffix")

	rw.Header().Set("Vary", "Accept")
	switch {
	case wantsDiscovery(req):
		ui.serveDiscovery(rw, req)
	case wantsUploadHelper(req):
		ui.serveUploadHelper(rw, req)
	case strings.HasPrefix(suffix, "download/"):
		ui.serveDownload(rw, req)
	case strings.HasPrefix(suffix, "thumbnail/"):
		ui.serveThumbnail(rw, req)
	default:
		file := ""
		if m := staticFilePattern.FindStringSubmatch(suffix); m != nil {
			file = m[1]
		} else {
			switch {
			case wantsPermanode(req):
				file = "permanode.html"
			case wantsBlobInfo(req):
				file = "blobinfo.html"
			case req.URL.Path == base:
				file = "index.html"
			default:
				http.Error(rw, "Illegal URL.", 404)
				return
			}
		}
		vfs.ServeFileFromFS(rw, req, uiFiles, file)
	}
}

func (ui *UIHandler) serveDiscovery(rw http.ResponseWriter, req *http.Request) {
	rw.Header().Set("Content-Type", "text/javascript")
	inCb := false
	if cb := req.FormValue("cb"); identPattern.MatchString(cb) {
		fmt.Fprintf(rw, "%s(", cb)
		inCb = true
	}
	bytes, _ := json.Marshal(map[string]interface{}{
		"blobRoot":     ui.BlobRoot,
		"searchRoot":   ui.SearchRoot,
		"jsonSignRoot": ui.JSONSignRoot,
		"uploadHelper": "?camli.mode=uploadhelper", // hack; remove with better javascript
		"downloadHelper": "./download/",
	})
	rw.Write(bytes)
	if inCb {
		rw.Write([]byte{')'})
	}
}

func (ui *UIHandler) storageSeekFetcher() (blobref.SeekFetcher, os.Error) {
	return blobref.SeekerFromStreamingFetcher(ui.Storage)
}

func (ui *UIHandler) serveDownload(rw http.ResponseWriter, req *http.Request) {
	if req.Method != "GET" && req.Method != "HEAD" {
		http.Error(rw, "Invalid download method", 400)
		return
	}
	if ui.Storage == nil {
		http.Error(rw, "No BlobRoot configured", 500)
		return
	}

	fetchSeeker, err := ui.storageSeekFetcher()
	if err != nil {
		http.Error(rw, err.String(), 500)
		return
	}

	suffix := req.Header.Get("X-PrefixHandler-PathSuffix")

	m := downloadPattern.FindStringSubmatch(suffix)
	if m == nil {
		httputil.ErrorRouting(rw, req)
		return
	}

	fbr := blobref.Parse(m[1])
	if fbr == nil {
		http.Error(rw, "Invalid blobref", 400)
		return
	}

	filename := m[2]
	if len(filename) > 0 {
		filename = filename[1:] // remove leading slash
	}

	fr, err := schema.NewFileReader(fetchSeeker, fbr)
	if err != nil {
		http.Error(rw, "Can't serve file: "+err.String(), 500)
		return
	}
	defer fr.Close()

	// TODO: fr.FileSchema() and guess a mime type?  For now:
	schema := fr.FileSchema()
	rw.Header().Set("Content-Type", "application/octet-stream")
	rw.Header().Set("Content-Length", fmt.Sprintf("%d", schema.Size))

	if req.Method == "HEAD" {
		vbr := blobref.Parse(req.FormValue("verifycontents"))
		if vbr == nil {
			return
		}
		hash := vbr.Hash()
		if hash == nil {
			return
		}
		io.Copy(hash, fr) // ignore errors, caught later
		if vbr.HashMatches(hash) {
			rw.Header().Set("X-Camli-Contents", vbr.String())
		}
		return
	}

	n, err := io.Copy(rw, fr)
	log.Printf("For %q request of %s: copied %d, %v", req.Method, req.URL.RawPath, n, err)
	if err != nil {
		log.Printf("error serving download of file schema %s: %v", fbr, err)
		return
	}
	if n != int64(schema.Size) {
		log.Printf("error serving download of file schema %s: sent %d, expected size of %d",
			fbr, n, schema.Size)
		return
	}
}

func (ui *UIHandler) serveThumbnail(rw http.ResponseWriter, req *http.Request) {
	if ui.Storage == nil {
		http.Error(rw, "No BlobRoot configured", 500)
		return
	}

	fetchSeeker, err := ui.storageSeekFetcher()
	if err != nil {
		http.Error(rw, err.String(), 500)
		return
	}

	suffix := req.Header.Get("X-PrefixHandler-PathSuffix")
	m := thumbnailPattern.FindStringSubmatch(suffix)
	if m == nil {
		httputil.ErrorRouting(rw, req)
		return
	}

	query := req.URL.Query()
	width, err := strconv.Atoi(query.Get("mw"))
	if err != nil {
		http.Error(rw, "Invalid specified max width 'mw': "+err.String(), 500)
		return
	}
	height, err := strconv.Atoi(query.Get("mh"))
	if err != nil {
		http.Error(rw, "Invalid specified height 'mh': "+err.String(), 500)
		return
	}

	blobref := blobref.Parse(m[1])
	if blobref == nil {
		http.Error(rw, "Invalid blobref", 400)
		return
	}

	filename := m[2]
	if len(filename) > 0 {
		filename = filename[1:] // remove leading slash
	}

	fr, err := schema.NewFileReader(fetchSeeker, blobref)
	if err != nil {
		http.Error(rw, "Can't serve file: "+err.String(), 500)
		return
	}

	var buf bytes.Buffer
	n, err := io.Copy(&buf, fr)
	i, format, err := image.Decode(&buf)
	if err != nil {
		http.Error(rw, "Can't serve file: "+err.String(), 500)
		return
	}
	b := i.Bounds()
	// only do downscaling, otherwise just serve the original image
	if width < b.Dx() || height < b.Dy() {
		const huge = 2400
		// If it's gigantic, it's more efficient to downsample first
		// and then resize; resizing will smooth out the roughness.
		// (trusting the moustachio guys on that one).
		if b.Dx() > huge || b.Dy() > huge {
			w, h := width * 2, height * 2
			if b.Dx() > b.Dy() {
				w = b.Dx() * h / b.Dy()
			} else {
				h = b.Dy() * w / b.Dx()
			}
			i = resize.Resample(i, i.Bounds(), w, h)
			b = i.Bounds()
		}
		// conserve proportions. use the smallest of the two as the decisive one.
		if width > height {
			width = b.Dx() * height / b.Dy()
		} else {
			height = b.Dy() * width / b.Dx()
		}
		i = resize.Resize(i, b, width, height)
		// Encode as a new image
		buf.Reset()
		switch format {
		case "jpeg":
			err = jpeg.Encode(&buf, i, nil)
		default:
			err = png.Encode(&buf, i)
		}
		if err != nil {
			http.Error(rw, "Can't serve file: "+err.String(), 500)
			return
		}
	}
	ct := ""
	switch format {
	case "jpeg":
		ct = "image/jpeg"
	default:
		ct = "image/png"
	}
	rw.Header().Set("Content-Type", ct)
	size := buf.Len()
	rw.Header().Set("Content-Length", fmt.Sprintf("%d", size))
	n, err = io.Copy(rw, &buf)
	if err != nil {
		log.Printf("error serving thumbnail of file schema %s: %v", blobref, err)
		return
	}
	if n != int64(size) {
		log.Printf("error serving thumbnail of file schema %s: sent %d, expected size of %d",
			blobref, n, size)
		return
	}
}
<|endoftext|>


<commit_before>package util

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/rs/cors"
)

// NewCorsHandler creates a new http.Handler to support CORS
func NewCorsHandler(engine *gin.Engine, allowOrigins []string) http.Handler {
	mw := cors.New(cors.Options{
		AllowedOrigins:   allowOrigins,
		AllowCredentials: true,
		AllowedMethods:   []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
		AllowedHeaders:   []string{"Access-Control-Allow-Origin", "Content-Type", "Accept"},
		Debug:            false,
	})

	return mw.Handler(engine)
}
<commit_msg>Fix to receive http.Handler<commit_after>package util

import (
	"net/http"

	"github.com/rs/cors"
)

// NewCorsHandler creates a new http.Handler to support CORS
func NewCorsHandler(handler http.Handler, allowOrigins []string) http.Handler {
	mw := cors.New(cors.Options{
		AllowedOrigins:   allowOrigins,
		AllowCredentials: true,
		AllowedMethods:   []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
		AllowedHeaders:   []string{"Access-Control-Allow-Origin", "Content-Type", "Accept"},
		Debug:            false,
	})

	return mw.Handler(handler)
}
<|endoftext|>


<commit_before>package monitor

import (
	"sync/atomic"
	"time"

	"github.com/Comcast/webpa-common/logging"
	"github.com/Comcast/webpa-common/service"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/go-kit/kit/metrics/provider"
	"github.com/go-kit/kit/sd"
)

// Event carries the same information as go-kit's sd.Event, but with the extra Key that identifies
// which service key or path was updated.
type Event struct {
	// Key is the in-process identifier for the sd.Instancer that produced this event
	Key string

	// Instancer is the go-kit sd.Instancer which sent this event.  This instance can be used to enrich
	// logging via logging.Enrich.
	Instancer sd.Instancer

	// EventCount is the postive, ascending integer identifying this event's sequence, e.g. 1 refers to the first
	// service discovery event.  Useful for logging and certain types of logic, such as ignoring the initial instances from a monitor.
	EventCount int

	// Instances are the filtered instances that came from the sd.Instancer.  If this is set,
	// Err will be nil.
	Instances []string

	// Err is any service discovery error that occurred.  If this is set, Instances will be empty.
	Err error

	// Stopped is set to true if and only if this event is being sent to indicate the monitoring goroutine
	// has exited, either because of being explicitly stopped or because the environment was closed.
	Stopped bool
}

type Listener interface {
	MonitorEvent(Event)
}

type ListenerFunc func(Event)

func (lf ListenerFunc) MonitorEvent(e Event) {
	lf(e)
}

type Listeners []Listener

func (ls Listeners) MonitorEvent(e Event) {
	for _, v := range ls {
		v.MonitorEvent(e)
	}
}

// NewMetricsListener produces a monitor Listener that gathers metrics related to service discovery.
func NewMetricsListener(p provider.Provider) Listener {
	var (
		errorCount    = p.NewCounter(service.ErrorCount)
		lastError     = p.NewGauge(service.LastErrorTimestamp)
		updateCount   = p.NewCounter(service.UpdateCount)
		lastUpdate    = p.NewGauge(service.LastUpdateTimestamp)
		instanceCount = p.NewGauge(service.InstanceCount)
	)

	return ListenerFunc(func(e Event) {
		timestamp := float64(time.Now().Unix())

		if e.Err != nil {
			errorCount.With(service.ServiceLabel, e.Key).Add(1.0)
			lastError.With(service.ServiceLabel, e.Key).Set(timestamp)
		} else {
			updateCount.With(service.ServiceLabel, e.Key).Add(1.0)
			lastUpdate.With(service.ServiceLabel, e.Key).Set(timestamp)
		}

		instanceCount.With(service.ServiceLabel, e.Key).Set(float64(len(e.Instances)))
	})
}

// NewAccessorListener creates a service discovery Listener that dispatches accessor instances to a nested closure.
// Any error received from the event results in a nil Accessor together with that error being passed to the next closure.
// If the AccessorFactory is nil, DefaultAccessorFactory is used.  If the next closure is nil, this function panics.
//
// An UpdatableAccessor may directly be used to receive events by passing Update as the next closure:
//    ua := new(UpdatableAccessor)
//    l := NewAccessorListener(f, ua.Update)
func NewAccessorListener(f service.AccessorFactory, next func(service.Accessor, error)) Listener {
	if next == nil {
		panic("A next closure is required to receive Accessors")
	}

	if f == nil {
		f = service.DefaultAccessorFactory
	}

	return ListenerFunc(func(e Event) {
		switch {
		case e.Err != nil:
			next(nil, e.Err)

		case len(e.Instances) > 0:
			next(f(e.Instances), nil)

		default:
			next(service.EmptyAccessor(), nil)
		}
	})
}

const (
	stateDeregistered uint32 = 0
	stateRegistered   uint32 = 1
)

// NewRegistrarListener binds service registration to the lifecycle of a service discovery watch.
// Upon the first successful update, or on any successful update following one or more errors, the given
// registrar is registered.  Any error that follows a successful update, or on the first error, results
// in deregistration.
func NewRegistrarListener(logger log.Logger, r sd.Registrar, initiallyRegistered bool) Listener {
	if logger == nil {
		logger = logging.DefaultLogger()
	}

	if r == nil {
		panic("A registrar is required")
	}

	var state uint32 = stateDeregistered
	if initiallyRegistered {
		state = stateRegistered
	}

	return ListenerFunc(func(e Event) {
		var message string
		if e.Err != nil {
			message = "deregistering on service discovery error"
		} else if e.Stopped {
			message = "deregistering due to monitor being stopped"
		} else {
			if atomic.CompareAndSwapUint32(&state, stateDeregistered, stateRegistered) {
				logger.Log(level.Key(), level.InfoValue(), logging.MessageKey(), "registering on service discovery update")
				r.Register()
			}

			return
		}

		if atomic.CompareAndSwapUint32(&state, stateRegistered, stateDeregistered) {
			logger.Log(level.Key(), level.ErrorValue(), logging.MessageKey(), message, logging.ErrorKey(), e.Err, "stopped", e.Stopped)
			r.Deregister()
		}
	})
}
<commit_msg>Added a DelayListener decorator which uses capacitance<commit_after>package monitor

import (
	"sync/atomic"
	"time"

	"github.com/Comcast/webpa-common/capacitor"
	"github.com/Comcast/webpa-common/logging"
	"github.com/Comcast/webpa-common/service"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/go-kit/kit/metrics/provider"
	"github.com/go-kit/kit/sd"
)

// Event carries the same information as go-kit's sd.Event, but with the extra Key that identifies
// which service key or path was updated.
type Event struct {
	// Key is the in-process identifier for the sd.Instancer that produced this event
	Key string

	// Instancer is the go-kit sd.Instancer which sent this event.  This instance can be used to enrich
	// logging via logging.Enrich.
	Instancer sd.Instancer

	// EventCount is the postive, ascending integer identifying this event's sequence, e.g. 1 refers to the first
	// service discovery event.  Useful for logging and certain types of logic, such as ignoring the initial instances from a monitor.
	EventCount int

	// Instances are the filtered instances that came from the sd.Instancer.  If this is set,
	// Err will be nil.
	Instances []string

	// Err is any service discovery error that occurred.  If this is set, Instances will be empty.
	Err error

	// Stopped is set to true if and only if this event is being sent to indicate the monitoring goroutine
	// has exited, either because of being explicitly stopped or because the environment was closed.
	Stopped bool
}

type Listener interface {
	MonitorEvent(Event)
}

type ListenerFunc func(Event)

func (lf ListenerFunc) MonitorEvent(e Event) {
	lf(e)
}

type Listeners []Listener

func (ls Listeners) MonitorEvent(e Event) {
	for _, v := range ls {
		v.MonitorEvent(e)
	}
}

// DelayListener uses a capacitor to delay service discovery events
// for a given period of time.  If d is nonpositive, the given listener
// is returned undecorated.
func DelayListener(d time.Duration, l Listener) Listener {
	if d < 1 {
		return l
	}

	c := capacitor.New(
		capacitor.WithDelay(d),
	)

	return ListenerFunc(func(e Event) {
		c.Submit(func() {
			l.MonitorEvent(e)
		})
	})
}

// NewMetricsListener produces a monitor Listener that gathers metrics related to service discovery.
func NewMetricsListener(p provider.Provider) Listener {
	var (
		errorCount    = p.NewCounter(service.ErrorCount)
		lastError     = p.NewGauge(service.LastErrorTimestamp)
		updateCount   = p.NewCounter(service.UpdateCount)
		lastUpdate    = p.NewGauge(service.LastUpdateTimestamp)
		instanceCount = p.NewGauge(service.InstanceCount)
	)

	return ListenerFunc(func(e Event) {
		timestamp := float64(time.Now().Unix())

		if e.Err != nil {
			errorCount.With(service.ServiceLabel, e.Key).Add(1.0)
			lastError.With(service.ServiceLabel, e.Key).Set(timestamp)
		} else {
			updateCount.With(service.ServiceLabel, e.Key).Add(1.0)
			lastUpdate.With(service.ServiceLabel, e.Key).Set(timestamp)
		}

		instanceCount.With(service.ServiceLabel, e.Key).Set(float64(len(e.Instances)))
	})
}

// NewAccessorListener creates a service discovery Listener that dispatches accessor instances to a nested closure.
// Any error received from the event results in a nil Accessor together with that error being passed to the next closure.
// If the AccessorFactory is nil, DefaultAccessorFactory is used.  If the next closure is nil, this function panics.
//
// An UpdatableAccessor may directly be used to receive events by passing Update as the next closure:
//    ua := new(UpdatableAccessor)
//    l := NewAccessorListener(f, ua.Update)
func NewAccessorListener(f service.AccessorFactory, next func(service.Accessor, error)) Listener {
	if next == nil {
		panic("A next closure is required to receive Accessors")
	}

	if f == nil {
		f = service.DefaultAccessorFactory
	}

	return ListenerFunc(func(e Event) {
		switch {
		case e.Err != nil:
			next(nil, e.Err)

		case len(e.Instances) > 0:
			next(f(e.Instances), nil)

		default:
			next(service.EmptyAccessor(), nil)
		}
	})
}

const (
	stateDeregistered uint32 = 0
	stateRegistered   uint32 = 1
)

// NewRegistrarListener binds service registration to the lifecycle of a service discovery watch.
// Upon the first successful update, or on any successful update following one or more errors, the given
// registrar is registered.  Any error that follows a successful update, or on the first error, results
// in deregistration.
func NewRegistrarListener(logger log.Logger, r sd.Registrar, initiallyRegistered bool) Listener {
	if logger == nil {
		logger = logging.DefaultLogger()
	}

	if r == nil {
		panic("A registrar is required")
	}

	var state uint32 = stateDeregistered
	if initiallyRegistered {
		state = stateRegistered
	}

	return ListenerFunc(func(e Event) {
		var message string
		if e.Err != nil {
			message = "deregistering on service discovery error"
		} else if e.Stopped {
			message = "deregistering due to monitor being stopped"
		} else {
			if atomic.CompareAndSwapUint32(&state, stateDeregistered, stateRegistered) {
				logger.Log(level.Key(), level.InfoValue(), logging.MessageKey(), "registering on service discovery update")
				r.Register()
			}

			return
		}

		if atomic.CompareAndSwapUint32(&state, stateRegistered, stateDeregistered) {
			logger.Log(level.Key(), level.ErrorValue(), logging.MessageKey(), message, logging.ErrorKey(), e.Err, "stopped", e.Stopped)
			r.Deregister()
		}
	})
}
<|endoftext|>