<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/kybin/tor/cell"
	"github.com/kybin/tor/syntax"
	term "github.com/nsf/termbox-go"
)

var usage = `
  tor [flag...] file

file
  filename[:line[:offset]]

flag
`

func printUsage(f *flag.FlagSet) {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprint(os.Stderr, usage)
	f.PrintDefaults()
}

// sortArgs sorts args to make flags always placed ahead of file arg.
func sortArgs(args []string) {
	sort.Slice(args, func(i, j int) bool {
		iIsFlag := strings.HasPrefix(args[i], "-")
		jIsFlag := strings.HasPrefix(args[j], "-")
		if iIsFlag && !jIsFlag {
			return true
		}
		return false
	})
}

// parseFileArg parses farg. Which looks like "filepath:linenum(1):offset(1)"
// The correspond return values are (filepath, linenum(0), offset(0)).
//
// Offset from the arg treated as 1 based offsets.
// They will changed to 0 based offsets, means they are subtracted by 1.
//
// If linenum or offset is given but invalid, it's return value will be 0.
// When both of them are ungiven, their return value will be -1, -1.
func parseFileArg(farg string) (string, int, int) {
	// final ":" is invalid but ignore it is sufficient.
	if strings.HasSuffix(farg, ":") {
		farg = farg[:len(farg)-1]
	}

	finfo := strings.Split(farg, ":")
	f := finfo[0]
	if len(finfo) == 1 {
		return f, -1, -1
	}

	l, err := strconv.Atoi(finfo[1])
	if err != nil || l < 1 {
		l = 1
	}
	l -= 1 // to base 0.
	if len(finfo) == 2 {
		return f, l, 0
	}

	o, err := strconv.Atoi(finfo[2])
	if err != nil || o < 1 {
		o = 1
	}
	o -= 1 // to base 0.
	return f, l, o
}

func main() {
	flagset := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	var newFlag bool
	flagset.BoolVar(&newFlag, "new", false, "let tor to edit a new file.")

	args := os.Args[1:]
	sortArgs(args)
	flagset.Parse(args)

	fileArgs := flagset.Args()
	if len(fileArgs) != 1 {
		printUsage(flagset)
		os.Exit(1)
	}
	farg := fileArgs[0]

	f, initL, initB := parseFileArg(farg)
	if initL == -1 {
		initL, initB = loadLastPosition(f)
	}

	exist := true
	if _, err := os.Stat(f); os.IsNotExist(err) {
		exist = false
	}
	if !exist && !newFlag {
		fmt.Fprintln(os.Stderr, "file not exist. please retry with -new flag.")
		os.Exit(1)
	}

	var text *Text
	if exist {
		var err error
		text, err = open(f)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	} else {
		lines := make([]Line, 0)
		lines = append(lines, Line{""})
		text = &Text{lines: lines, tabToSpace: false, tabWidth: 4, edited: false}
	}

	err := term.Init()
	if err != nil {
		panic(err)
	}
	defer term.Close()
	term.SetInputMode(term.InputAlt)

	termw, termh := term.Size()
	win := NewWindow(cell.Pt{termh - 1, termw})
	cursor := NewCursor(text)
	cursor.GotoLine(initL)
	cursor.SetCloseToB(initB)
	selection := NewSelection(text)
	history := NewHistory()

	// create modes for handling events.
	mode := &ModeSelector{}
	mode.normal = &NormalMode{
		text:      text,
		cursor:    cursor,
		selection: selection,
		history:   history,
		f:         f,
		mode:      mode,
		copied:    loadConfig("copy"),
	}
	mode.find = &FindMode{
		mode: mode,
		str:  loadConfig("find"),
	}
	mode.replace = &ReplaceMode{
		mode: mode,
		str:  loadConfig("replace"),
	}
	mode.gotoline = &GotoLineMode{
		cursor: cursor,
		mode:   mode,
	}
	mode.exit = &ExitMode{
		f:      f,
		cursor: cursor,
		mode:   mode,
	}
	mode.current = mode.normal // start as normal mode.

	// get events from termbox.
	events := make(chan term.Event, 20)
	go func() {
		for {
			events <- term.PollEvent()
		}
	}()

	// mutex for commucation with termbox.
	mu := &sync.Mutex{}

	// Sync redraws terminal from buffer.
	// sometimes Flush is insufficient,
	// it is better to Sync frequently.
	go func() {
		ticker := time.NewTicker(time.Second)
		for {
			select {
			case <-ticker.C:
				mu.Lock()
				term.Sync()
				mu.Unlock()
			}
		}
	}()

	// parse syntax
	ext := filepath.Ext(f)
	var lang *syntax.Language
	if ext != "" {
		lang = syntax.Languages[ext[1:]]
	}
	var matches []syntax.Match
	if lang != nil {
		matches = lang.Parse(mode.normal.text.Bytes())
	}

	// main loop
	for {
		moved := win.Follow(cursor, 3)
		if lang != nil {
			if moved || (mode.current == mode.normal && mode.normal.dirty) {
				// recalculate syntax matches from window's top.
				// it will better to recalculate from edited position,
				// but seems little harder to implement.
				matches = lang.ParseRange(matches, mode.normal.text.Bytes(), cell.Pt{L: win.min.L, O: 0}, cell.Pt{L: win.Max().L + 1, O: 0})
				mode.normal.dirty = false
			}
		}

		mu.Lock()
		term.Clear(term.ColorDefault, term.ColorDefault)
		drawScreen(win, mode.normal.text, selection, lang, matches)
		if mode.current.Error() != "" {
			drawErrorStatus(mode.current.Error())
		} else {
			drawStatus(mode.current.Status())
		}
		if mode.current == mode.normal {
			winP := cursor.Position().Sub(win.min)
			term.SetCursor(winP.O, winP.L)
		} else {
			term.SetCursor(vlen(mode.current.Status(), mode.normal.text.tabWidth), termh)
		}
		term.Flush()
		mu.Unlock()

		// wait for keyboard input
		select {
		case ev := <-events:
			switch ev.Type {
			case term.EventKey:
				mode.current.Handle(ev)
			case term.EventResize:
				mu.Lock()
				term.Clear(term.ColorDefault, term.ColorDefault)
				termw, termh = term.Size()
				win.size = cell.Pt{termh - 1, termw}
				mu.Unlock()
			}
		}
	}
}
<commit_msg>fix create a new text part from main<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/kybin/tor/cell"
	"github.com/kybin/tor/syntax"
	term "github.com/nsf/termbox-go"
)

var usage = `
  tor [flag...] file

file
  filename[:line[:offset]]

flag
`

func printUsage(f *flag.FlagSet) {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprint(os.Stderr, usage)
	f.PrintDefaults()
}

// sortArgs sorts args to make flags always placed ahead of file arg.
func sortArgs(args []string) {
	sort.Slice(args, func(i, j int) bool {
		iIsFlag := strings.HasPrefix(args[i], "-")
		jIsFlag := strings.HasPrefix(args[j], "-")
		if iIsFlag && !jIsFlag {
			return true
		}
		return false
	})
}

// parseFileArg parses farg. Which looks like "filepath:linenum(1):offset(1)"
// The correspond return values are (filepath, linenum(0), offset(0)).
//
// Offset from the arg treated as 1 based offsets.
// They will changed to 0 based offsets, means they are subtracted by 1.
//
// If linenum or offset is given but invalid, it's return value will be 0.
// When both of them are ungiven, their return value will be -1, -1.
func parseFileArg(farg string) (string, int, int) {
	// final ":" is invalid but ignore it is sufficient.
	if strings.HasSuffix(farg, ":") {
		farg = farg[:len(farg)-1]
	}

	finfo := strings.Split(farg, ":")
	f := finfo[0]
	if len(finfo) == 1 {
		return f, -1, -1
	}

	l, err := strconv.Atoi(finfo[1])
	if err != nil || l < 1 {
		l = 1
	}
	l -= 1 // to base 0.
	if len(finfo) == 2 {
		return f, l, 0
	}

	o, err := strconv.Atoi(finfo[2])
	if err != nil || o < 1 {
		o = 1
	}
	o -= 1 // to base 0.
	return f, l, o
}

func main() {
	flagset := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	var newFlag bool
	flagset.BoolVar(&newFlag, "new", false, "let tor to edit a new file.")

	args := os.Args[1:]
	sortArgs(args)
	flagset.Parse(args)

	fileArgs := flagset.Args()
	if len(fileArgs) != 1 {
		printUsage(flagset)
		os.Exit(1)
	}
	farg := fileArgs[0]

	f, initL, initB := parseFileArg(farg)
	if initL == -1 {
		initL, initB = loadLastPosition(f)
	}

	// get text from file or make new.
	var text *Text
	if _, err := os.Stat(f); os.IsNotExist(err) {
		if !newFlag {
			fmt.Fprintln(os.Stderr, "file not exist. please retry with -new flag.")
			os.Exit(1)
		}
		lines := make([]Line, 0)
		lines = append(lines, Line{""})
		text = &Text{lines: lines, tabToSpace: false, tabWidth: 4, edited: false}
	} else {
		var err error
		text, err = open(f)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}

	err := term.Init()
	if err != nil {
		panic(err)
	}
	defer term.Close()
	term.SetInputMode(term.InputAlt)

	termw, termh := term.Size()
	win := NewWindow(cell.Pt{termh - 1, termw})
	cursor := NewCursor(text)
	cursor.GotoLine(initL)
	cursor.SetCloseToB(initB)
	selection := NewSelection(text)
	history := NewHistory()

	// create modes for handling events.
	mode := &ModeSelector{}
	mode.normal = &NormalMode{
		text:      text,
		cursor:    cursor,
		selection: selection,
		history:   history,
		f:         f,
		mode:      mode,
		copied:    loadConfig("copy"),
	}
	mode.find = &FindMode{
		mode: mode,
		str:  loadConfig("find"),
	}
	mode.replace = &ReplaceMode{
		mode: mode,
		str:  loadConfig("replace"),
	}
	mode.gotoline = &GotoLineMode{
		cursor: cursor,
		mode:   mode,
	}
	mode.exit = &ExitMode{
		f:      f,
		cursor: cursor,
		mode:   mode,
	}
	mode.current = mode.normal // start as normal mode.

	// get events from termbox.
	events := make(chan term.Event, 20)
	go func() {
		for {
			events <- term.PollEvent()
		}
	}()

	// mutex for commucation with termbox.
	mu := &sync.Mutex{}

	// Sync redraws terminal from buffer.
	// sometimes Flush is insufficient,
	// it is better to Sync frequently.
	go func() {
		ticker := time.NewTicker(time.Second)
		for {
			select {
			case <-ticker.C:
				mu.Lock()
				term.Sync()
				mu.Unlock()
			}
		}
	}()

	// parse syntax
	ext := filepath.Ext(f)
	var lang *syntax.Language
	if ext != "" {
		lang = syntax.Languages[ext[1:]]
	}
	var matches []syntax.Match
	if lang != nil {
		matches = lang.Parse(mode.normal.text.Bytes())
	}

	// main loop
	for {
		moved := win.Follow(cursor, 3)
		if lang != nil {
			if moved || (mode.current == mode.normal && mode.normal.dirty) {
				// recalculate syntax matches from window's top.
				// it will better to recalculate from edited position,
				// but seems little harder to implement.
				matches = lang.ParseRange(matches, mode.normal.text.Bytes(), cell.Pt{L: win.min.L, O: 0}, cell.Pt{L: win.Max().L + 1, O: 0})
				mode.normal.dirty = false
			}
		}

		mu.Lock()
		term.Clear(term.ColorDefault, term.ColorDefault)
		drawScreen(win, mode.normal.text, selection, lang, matches)
		if mode.current.Error() != "" {
			drawErrorStatus(mode.current.Error())
		} else {
			drawStatus(mode.current.Status())
		}
		if mode.current == mode.normal {
			winP := cursor.Position().Sub(win.min)
			term.SetCursor(winP.O, winP.L)
		} else {
			term.SetCursor(vlen(mode.current.Status(), mode.normal.text.tabWidth), termh)
		}
		term.Flush()
		mu.Unlock()

		// wait for keyboard input
		select {
		case ev := <-events:
			switch ev.Type {
			case term.EventKey:
				mode.current.Handle(ev)
			case term.EventResize:
				mu.Lock()
				term.Clear(term.ColorDefault, term.ColorDefault)
				termw, termh = term.Size()
				win.size = cell.Pt{termh - 1, termw}
				mu.Unlock()
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
	"net/url"
	"strings"

	"github.com/go-martini/martini"
	"github.com/martini-contrib/sessions"

	"github.com/tomsteele/dmv"

	"net/http/httputil"

	"github.com/boltdb/bolt"
	gsessions "github.com/gorilla/sessions"
	"github.com/yosssi/boltstore/reaper"
	bstore "github.com/yosssi/boltstore/store"

	"github.com/joeshaw/envdecode"
)

type Config struct {
	ClientId      string `env:"CLIENT_ID,required"`      // Google Client ID
	ClientSecret  string `env:"CLIENT_SECRET,required"`  // Google Client Secret
	SessionSecret string `env:"SESSION_SECRET,required"` // Random session encruption token
	DNSName       string `env:"DNS_NAME,required"`       // Public facing DNS Hostname

	SessionDBPath string `env:"SESSION_DB_PATH,default=./sessions.db"` // Path to session database, including db name
	CookieMaxAge  int    `env:"COOKIE_MAX_AGE,default=1440"`           // Cookie MaxAge, Defaults to 1 day
	CookieName    string `env:"COOKIE_NAME,default=sproxy_session"`    // The name of the cookie

	ProxyURL string `env:"PROXY_URL,default=http://localhost:8000/"` // URL to Proxy to

	CallBackPath string `env:"CALLBACK_PATH,default=/auth/callback/google"` // Callback URL
	AuthPath     string `env:"AUTH_PATH,default=/auth/google"`              // Auth Path

	HealthCheckPath string `env:"HEALTH_CHECK_PATH,default=/en-US/static/html/credit.html"` // Health Check path in splunk, this path is proxied w/o auth. The default is a static file served by the splunk web server

	EmailSuffix string `env:"EMAIL_SUFFIX,default=@heroku.com"` // Required email suffix. Emails w/o this suffix will not be let in
}

var (
	cfg              = Config{}
	oAuthScopes      = []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"}
	sessionOptions   = gsessions.Options{Secure: true, HttpOnly: true}
	boltStoreOptions = bstore.Config{SessionOptions: sessionOptions}
)

// Authorize the user based on email and a set OpenIDUser
func authorize(s sessions.Session, rw http.ResponseWriter, req *http.Request) {
	email := s.Get("email")
	if email == nil || !strings.HasSuffix(email.(string), cfg.EmailSuffix) {
		http.Redirect(rw, req, cfg.AuthPath, http.StatusFound)
		return
	}

	openIDUser := s.Get("OpenIDUser")
	if openIDUser != nil && openIDUser != "" {
		req.Header.Set("X-Openid-User", openIDUser.(string))
	} else {
		// No openIDUser set, so abort and restart the auth flow
		http.Redirect(rw, req, cfg.AuthPath, http.StatusFound)
	}
}

// Set the OpenIDUser and other session values based on the data from Google
func handleCallback(goog *dmv.Google, s sessions.Session, rw http.ResponseWriter, req *http.Request) {
	// Handle any errors.
	if len(goog.Errors) > 0 {
		http.Error(rw, "Oauth failure", http.StatusInternalServerError)
		return
	}

	s.Set("GoogleID", goog.Profile.ID)
	s.Set("email", goog.Profile.Email)

	parts := strings.SplitN(goog.Profile.Email, "@", 2)
	if len(parts) < 2 {
		http.Error(rw, "Unable to determine OpenIDUser from email `"+goog.Profile.Email+"`", http.StatusInternalServerError)
		return
	}
	s.Set("OpenIDUser", strings.ToLower(parts[0]))

	http.Redirect(rw, req, "/", http.StatusFound)
}

// Ensure that we're being proxied to ourselves via https
func enforceXForwardedProto(rw http.ResponseWriter, req *http.Request) {
	xff := req.Header.Get("X-Forwarded-Proto")
	if xff != "https" {
		u := new(url.URL)
		*u = *req.URL
		u.Scheme = "https"
		if u.Host == "" {
			u.Host = req.Host
		}

		http.Redirect(rw, req, u.String(), http.StatusFound)
	}
}

func main() {

	err := envdecode.Decode(&cfg)
	if err != nil {
		log.Fatal(err)
	}
	sessionOptions.MaxAge = cfg.CookieMaxAge

	googleOpts := &dmv.OAuth2Options{
		ClientID:     cfg.ClientId,
		ClientSecret: cfg.ClientSecret,
		RedirectURL:  "https://" + cfg.DNSName + cfg.CallBackPath,
		Scopes:       oAuthScopes,
	}

	sessionDB, err := bolt.Open(cfg.SessionDBPath, 0666, nil)
	if err != nil {
		log.Fatal(err)
	}

	defer sessionDB.Close()
	defer reaper.Quit(reaper.Run(sessionDB, reaper.Options{}))

	m := martini.Classic()

	pUrl, err := url.Parse(cfg.ProxyURL)

	if err != nil {
		log.Fatal(err)
	}

	store, err := bstore.New(sessionDB, boltStoreOptions, []byte(cfg.SessionSecret))
	if err != nil {
		log.Fatal(err)
	}

	// Inject a session when it's needed
	m.Use(sessions.Sessions(cfg.CookieName, store))

	proxy := httputil.NewSingleHostReverseProxy(pUrl)

	// Health Check URL, so just proxy w/o any processing
	m.Get(cfg.HealthCheckPath, proxy.ServeHTTP)

	// Google Auth
	m.Get(cfg.AuthPath, dmv.AuthGoogle(googleOpts))
	m.Get(cfg.CallBackPath, dmv.AuthGoogle(googleOpts), handleCallback)

	// Proxy the rest
	m.Get("/**", enforceXForwardedProto, authorize, proxy.ServeHTTP)
	m.Put("/**", enforceXForwardedProto, authorize, proxy.ServeHTTP)
	m.Post("/**", enforceXForwardedProto, authorize, proxy.ServeHTTP)
	m.Delete("/**", enforceXForwardedProto, authorize, proxy.ServeHTTP)
	m.Options("/**", enforceXForwardedProto, authorize, proxy.ServeHTTP)
	m.Run()
}
<commit_msg>Make lint/vet happy<commit_after>package main

import (
	"log"
	"net/http"
	"net/url"
	"strings"

	"github.com/go-martini/martini"
	"github.com/martini-contrib/sessions"

	"github.com/tomsteele/dmv"

	"net/http/httputil"

	"github.com/boltdb/bolt"
	gsessions "github.com/gorilla/sessions"
	"github.com/yosssi/boltstore/reaper"
	bstore "github.com/yosssi/boltstore/store"

	"github.com/joeshaw/envdecode"
)

type config struct {
	clientID      string `env:"CLIENT_ID,required"`      // Google Client ID
	clientSecret  string `env:"CLIENT_SECRET,required"`  // Google Client Secret
	sessionSecret string `env:"SESSION_SECRET,required"` // Random session encruption token
	dnsName       string `env:"DNS_NAME,required"`       // Public facing DNS Hostname

	sessionDBPath string `env:"SESSION_DB_PATH,default=./sessions.db"` // Path to session database, including db name
	cookieMaxAge  int    `env:"COOKIE_MAX_AGE,default=1440"`           // Cookie MaxAge, Defaults to 1 day
	cookieName    string `env:"COOKIE_NAME,default=sproxy_session"`    // The name of the cookie

	proxyURL string `env:"PROXY_URL,default=http://localhost:8000/"` // URL to Proxy to

	callBackPath string `env:"CALLBACK_PATH,default=/auth/callback/google"` // Callback URL
	authPath     string `env:"AUTH_PATH,default=/auth/google"`              // Auth Path

	healthCheckPath string `env:"HEALTH_CHECK_PATH,default=/en-US/static/html/credit.html"` // Health Check path in splunk, this path is proxied w/o auth. The default is a static file served by the splunk web server

	emailSuffix string `env:"EMAIL_SUFFIX,default=@heroku.com"` // Required email suffix. Emails w/o this suffix will not be let in
}

var (
	cfg              = config{}
	oAuthScopes      = []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"}
	sessionOptions   = gsessions.Options{Secure: true, HttpOnly: true}
	boltStoreOptions = bstore.Config{SessionOptions: sessionOptions}
)

// Authorize the user based on email and a set OpenIDUser
func authorize(s sessions.Session, rw http.ResponseWriter, req *http.Request) {
	email := s.Get("email")
	if email == nil || !strings.HasSuffix(email.(string), cfg.emailSuffix) {
		http.Redirect(rw, req, cfg.authPath, http.StatusFound)
		return
	}

	openIDUser := s.Get("OpenIDUser")
	if openIDUser != nil && openIDUser != "" {
		req.Header.Set("X-Openid-User", openIDUser.(string))
	} else {
		// No openIDUser set, so abort and restart the auth flow
		http.Redirect(rw, req, cfg.authPath, http.StatusFound)
	}
}

// Set the OpenIDUser and other session values based on the data from Google
func handleCallback(goog *dmv.Google, s sessions.Session, rw http.ResponseWriter, req *http.Request) {
	// Handle any errors.
	if len(goog.Errors) > 0 {
		http.Error(rw, "Oauth failure", http.StatusInternalServerError)
		return
	}

	s.Set("GoogleID", goog.Profile.ID)
	s.Set("email", goog.Profile.Email)

	parts := strings.SplitN(goog.Profile.Email, "@", 2)
	if len(parts) < 2 {
		http.Error(rw, "Unable to determine OpenIDUser from email `"+goog.Profile.Email+"`", http.StatusInternalServerError)
		return
	}
	s.Set("OpenIDUser", strings.ToLower(parts[0]))

	http.Redirect(rw, req, "/", http.StatusFound)
}

// Ensure that we're being proxied to ourselves via https
func enforceXForwardedProto(rw http.ResponseWriter, req *http.Request) {
	xff := req.Header.Get("X-Forwarded-Proto")
	if xff != "https" {
		u := new(url.URL)
		*u = *req.URL
		u.Scheme = "https"
		if u.Host == "" {
			u.Host = req.Host
		}

		http.Redirect(rw, req, u.String(), http.StatusFound)
	}
}

func main() {

	err := envdecode.Decode(&cfg)
	if err != nil {
		log.Fatal(err)
	}
	sessionOptions.MaxAge = cfg.cookieMaxAge

	googleOpts := &dmv.OAuth2Options{
		ClientID:     cfg.clientID,
		ClientSecret: cfg.clientSecret,
		RedirectURL:  "https://" + cfg.dnsName + cfg.callBackPath,
		Scopes:       oAuthScopes,
	}

	sessionDB, err := bolt.Open(cfg.sessionDBPath, 0666, nil)
	if err != nil {
		log.Fatal(err)
	}

	defer sessionDB.Close()
	defer reaper.Quit(reaper.Run(sessionDB, reaper.Options{}))

	m := martini.Classic()

	pURL, err := url.Parse(cfg.proxyURL)

	if err != nil {
		log.Fatal(err)
	}

	store, err := bstore.New(sessionDB, boltStoreOptions, []byte(cfg.sessionSecret))
	if err != nil {
		log.Fatal(err)
	}

	// Inject a session when it's needed
	m.Use(sessions.Sessions(cfg.cookieName, store))

	proxy := httputil.NewSingleHostReverseProxy(pURL)

	// Health Check URL, so just proxy w/o any processing
	m.Get(cfg.healthCheckPath, proxy.ServeHTTP)

	// Google Auth
	m.Get(cfg.authPath, dmv.AuthGoogle(googleOpts))
	m.Get(cfg.callBackPath, dmv.AuthGoogle(googleOpts), handleCallback)

	// Proxy the rest
	m.Get("/**", enforceXForwardedProto, authorize, proxy.ServeHTTP)
	m.Put("/**", enforceXForwardedProto, authorize, proxy.ServeHTTP)
	m.Post("/**", enforceXForwardedProto, authorize, proxy.ServeHTTP)
	m.Delete("/**", enforceXForwardedProto, authorize, proxy.ServeHTTP)
	m.Options("/**", enforceXForwardedProto, authorize, proxy.ServeHTTP)
	m.Run()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"math"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/jawher/mow.cli"
	"github.com/nlopes/slack"
)

const (
	NAME = "standup-slackbot"
	DESC = "A slackbot for standups"
)

func main() {
	app := cli.App(NAME, DESC)
	slackToken := app.String(cli.StringOpt{
		Name:   "slack-token",
		Desc:   "Slack API token",
		EnvVar: "SLACK_TOKEN",
	})
	standupChannelName := app.String(cli.StringOpt{
		Name:   "standup-channel",
		Desc:   "The Slack channel to use for standups",
		EnvVar: "STANDUP_CHANNEL",
	})
	standupTime := app.String(cli.StringOpt{
		Name:   "standup-time",
		Desc:   "The time standup should start in 24hr 00:00 format",
		EnvVar: "STANDUP_TIME",
	})
	standupLengthMins := app.Int(cli.IntOpt{
		Name:   "standup-length-mins",
		Desc:   "The standup length time in minutes",
		EnvVar: "STANDUP_LENGTH_MINS",
		Value:  60,
	})
	timeZone := app.String(cli.StringOpt{
		Name:   "time-zone",
		Desc:   "The timezone IANA format e.g. Europe/London",
		EnvVar: "TIME_ZONE",
		Value:  "Europe/London",
	})
	app.Action = func() {
		var lastStandupDay *int = nil
		tz, err := time.LoadLocation(*timeZone)
		if err != nil {
			log.Fatalf("Error getting location for timezone: %v", err)
		}
		for {
			<-time.After(1 * time.Minute)
			now := time.Now().In(tz)

			isWeekend := now.Weekday() < 1 || now.Weekday() > 5
			if isWeekend {
				continue
			}

			day := now.Day()

			standupAlreadyDone := lastStandupDay != nil && *lastStandupDay == day
			if standupAlreadyDone {
				continue
			}

			hour, mins, err := parseStandupStartTime(standupTime)
			if err != nil {
				log.Fatalf("Error parsing standup start time: %v", err)
			}

			notTimeYet := now.Hour() < *hour || now.Minute() < *mins
			if notTimeYet {
				continue
			}

			standupStartTime := time.Date(now.Year(), now.Month(), now.Day(), *hour, *mins, 0, 0, tz)
			standupDuration := time.Minute * time.Duration(*standupLengthMins)

			// this prevents us doing standup in the case where we have no prior state (due to a restart)
			// but have already done standup for the day
			standupEndTimePassed := lastStandupDay == nil && standupStartTime.Add(standupDuration).Before(now)
			if standupEndTimePassed {
				continue
			}

			if err := DoStandup(*slackToken, *standupChannelName, *standupLengthMins); err != nil {
				log.Fatalf("Error doing standup: %v", err)
			}

			lastStandupDay = &day
		}
	}
	app.Run(os.Args)
}

func parseStandupStartTime(standupTime *string) (*int, *int, error) {
	hoursAndMins := strings.Split(*standupTime, ":")
	hour, err := strconv.ParseInt(hoursAndMins[0], 10, 8)
	if err != nil {
		return nil, nil, fmt.Errorf("Could not parse hours from standup start time: %v", err)
	}
	mins, err := strconv.ParseInt(hoursAndMins[1], 10, 8)
	if err != nil {
		return nil, nil, fmt.Errorf("Could not parse mins from standup start time: %v", err)
	}
	hourInt := int(hour)
	minsInt := int(mins)
	return &hourInt, &minsInt, nil
}

func DoStandup(slackToken string, standupChannelName string, standupLengthMins int) error {
	baseParams := slack.NewPostMessageParameters()
	baseParams.Username = "Standup Bot"

	slackClient := &Slack{
		slack.New(slackToken),
		make(map[string]string),
		make(map[string]func(event *slack.MessageEvent)),
		sync.Mutex{},
		baseParams,
	}

	channelId, err := slackClient.GetChannelIdForChannel(standupChannelName)
	if err != nil {
		return fmt.Errorf("Could not get channel ID for channel %s: %v", standupChannelName, err)
	}

	members, err := slackClient.GetChannelMembers(*channelId)
	if err != nil {
		return fmt.Errorf("Error getting standup channel members: %v", err)
	}

	standup := NewStandup(slackClient, time.Now().Add(time.Minute*time.Duration(standupLengthMins)), members)
	results := standup.Start()

	for i := 0; i < 5; i++ {
		_, _, err = slackClient.apiClient.PostMessage(
			*channelId,
			"Standup is finished, keep up the good work team!",
			BuildSlackReport(baseParams, results),
		)
		if err == nil {
			break
		}
		log.Printf("Error posting standup result to Slack: %v", err)
		<-time.After(time.Duration(3*math.Pow(2, float64(i+1))) * time.Second)
	}
	return nil
}

func BuildSlackReport(baseParams slack.PostMessageParameters, questionnaires map[string]*StandupQuestionnaire) slack.PostMessageParameters {
	postParams := baseParams
	attachments := make([]slack.Attachment, len(questionnaires))
	for _, questionnaire := range questionnaires {
		attachment := slack.Attachment{
			Fallback:   "There is no fallback text...",
			AuthorIcon: questionnaire.Member.Profile.Image48,
			AuthorName: questionnaire.Member.RealName,
			Fields: []slack.AttachmentField{
				{
					Title: "What did you get done since last standup?",
					Short: false,
					Value: questionnaire.yesterday,
				},
				{
					Title: "What are you working on today?",
					Short: false,
					Value: questionnaire.today,
				},
				{
					Title: "When do you think you'll be finished?",
					Short: false,
					Value: questionnaire.finishedWhen,
				},
				{
					Title: "Is there anything blocking you?",
					Short: false,
					Value: questionnaire.blockers,
				},
			},
		}
		attachments = append(attachments, attachment)
	}
	postParams.Attachments = attachments
	return postParams
}
<commit_msg>introduces doStandupImmediately flag<commit_after>package main

import (
	"fmt"
	"log"
	"math"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/jawher/mow.cli"
	"github.com/nlopes/slack"
)

const (
	NAME = "standup-slackbot"
	DESC = "A slackbot for standups"
)

func main() {
	app := cli.App(NAME, DESC)
	slackToken := app.String(cli.StringOpt{
		Name:   "slack-token",
		Desc:   "Slack API token",
		EnvVar: "SLACK_TOKEN",
	})
	standupChannelName := app.String(cli.StringOpt{
		Name:   "standup-channel",
		Desc:   "The Slack channel to use for standups",
		EnvVar: "STANDUP_CHANNEL",
	})
	standupTime := app.String(cli.StringOpt{
		Name:   "standup-time",
		Desc:   "The time standup should start in 24hr 00:00 format",
		EnvVar: "STANDUP_TIME",
	})
	standupLengthMins := app.Int(cli.IntOpt{
		Name:   "standup-length-mins",
		Desc:   "The standup length time in minutes",
		EnvVar: "STANDUP_LENGTH_MINS",
		Value:  60,
	})
	timeZone := app.String(cli.StringOpt{
		Name:   "time-zone",
		Desc:   "The timezone IANA format e.g. Europe/London",
		EnvVar: "TIME_ZONE",
		Value:  "Europe/London",
	})
	doImmediately := app.Bool(cli.BoolOpt{
		Name: "do-standup-immediately",
		Value: false,
		EnvVar: "DO_STANDUP_IMMEDIATELY",
		Desc: "Should we do a standup immediately at launch?",
	})
	app.Action = func() {
		var lastStandupDay *int = nil
		tz, err := time.LoadLocation(*timeZone)
		if err != nil {
			log.Fatalf("Error getting location for timezone: %v", err)
		}

		for {
			if *doImmediately && lastStandupDay == nil {
				if err := DoStandup(*slackToken, *standupChannelName, *standupLengthMins); err != nil {
					log.Fatalf("Error doing standup: %v", err)
				}
				continue
			}

			<-time.After(1 * time.Minute)
			now := time.Now().In(tz)

			isWeekend := now.Weekday() < 1 || now.Weekday() > 5
			if isWeekend {
				continue
			}

			day := now.Day()

			standupAlreadyDone := lastStandupDay != nil && *lastStandupDay == day
			if standupAlreadyDone {
				continue
			}

			hour, mins, err := parseStandupStartTime(standupTime)
			if err != nil {
				log.Fatalf("Error parsing standup start time: %v", err)
			}

			notTimeYet := now.Hour() < *hour || now.Minute() < *mins
			if notTimeYet {
				continue
			}

			standupStartTime := time.Date(now.Year(), now.Month(), now.Day(), *hour, *mins, 0, 0, tz)
			standupDuration := time.Minute * time.Duration(*standupLengthMins)

			// this prevents us doing standup in the case where we have no prior state (due to a restart)
			// but have already done standup for the day
			standupEndTimePassed := lastStandupDay == nil && standupStartTime.Add(standupDuration).Before(now)
			if standupEndTimePassed {
				continue
			}

			if err := DoStandup(*slackToken, *standupChannelName, *standupLengthMins); err != nil {
				log.Fatalf("Error doing standup: %v", err)
			}

			lastStandupDay = &day
		}
	}
	app.Run(os.Args)
}

func parseStandupStartTime(standupTime *string) (*int, *int, error) {
	hoursAndMins := strings.Split(*standupTime, ":")
	hour, err := strconv.ParseInt(hoursAndMins[0], 10, 8)
	if err != nil {
		return nil, nil, fmt.Errorf("Could not parse hours from standup start time: %v", err)
	}
	mins, err := strconv.ParseInt(hoursAndMins[1], 10, 8)
	if err != nil {
		return nil, nil, fmt.Errorf("Could not parse mins from standup start time: %v", err)
	}
	hourInt := int(hour)
	minsInt := int(mins)
	return &hourInt, &minsInt, nil
}

func DoStandup(slackToken string, standupChannelName string, standupLengthMins int) error {
	baseParams := slack.NewPostMessageParameters()
	baseParams.Username = "Standup Bot"

	slackClient := &Slack{
		slack.New(slackToken),
		make(map[string]string),
		make(map[string]func(event *slack.MessageEvent)),
		sync.Mutex{},
		baseParams,
	}

	channelId, err := slackClient.GetChannelIdForChannel(standupChannelName)
	if err != nil {
		return fmt.Errorf("Could not get channel ID for channel %s: %v", standupChannelName, err)
	}

	members, err := slackClient.GetChannelMembers(*channelId)
	if err != nil {
		return fmt.Errorf("Error getting standup channel members: %v", err)
	}

	standup := NewStandup(slackClient, time.Now().Add(time.Minute*time.Duration(standupLengthMins)), members)
	results := standup.Start()

	for i := 0; i < 5; i++ {
		_, _, err = slackClient.apiClient.PostMessage(
			*channelId,
			"Standup is finished, keep up the good work team!",
			BuildSlackReport(baseParams, results),
		)
		if err == nil {
			break
		}
		log.Printf("Error posting standup result to Slack: %v", err)
		<-time.After(time.Duration(3*math.Pow(2, float64(i+1))) * time.Second)
	}
	return nil
}

func BuildSlackReport(baseParams slack.PostMessageParameters, questionnaires map[string]*StandupQuestionnaire) slack.PostMessageParameters {
	postParams := baseParams
	attachments := make([]slack.Attachment, len(questionnaires))
	for _, questionnaire := range questionnaires {
		attachment := slack.Attachment{
			Fallback:   "There is no fallback text...",
			AuthorIcon: questionnaire.Member.Profile.Image48,
			AuthorName: questionnaire.Member.RealName,
			Fields: []slack.AttachmentField{
				{
					Title: "What did you get done since last standup?",
					Short: false,
					Value: questionnaire.yesterday,
				},
				{
					Title: "What are you working on today?",
					Short: false,
					Value: questionnaire.today,
				},
				{
					Title: "When do you think you'll be finished?",
					Short: false,
					Value: questionnaire.finishedWhen,
				},
				{
					Title: "Is there anything blocking you?",
					Short: false,
					Value: questionnaire.blockers,
				},
			},
		}
		attachments = append(attachments, attachment)
	}
	postParams.Attachments = attachments
	return postParams
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"os"
	"syscall"
	"unsafe"
)

var errorlog *os.File
var logger *log.Logger

const (
	TAB_STOP = 4
)

const (
	CURSOR_UP    = 1000
	CURSOR_DOWN  = 1001
	CURSOR_LEFT  = 1002
	CURSOR_RIGHT = 1003
	PAGE_UP      = 1004
	PAGE_DOWN    = 1005
	HOME_KEY     = 1006
	END_KEY      = 1007
	DEL_KEY      = 1008
)

const (
	INSERT_MODE = 1
	CMD_MODE    = 2
)

type winsize struct {
	height uint16
	width  uint16
	x      uint16
	y      uint16
}

type erow struct {
	chars  string
	render string
	size   int
	rsize  int
}

type terminal int

func (t terminal) Read(buf []byte) (int, error) {
	return syscall.Read(int(t), buf)
}

func (t terminal) Write(s string) {
	b := bytes.NewBufferString(s)
	if _, err := syscall.Write(int(t), b.Bytes()); err != nil {
		logger.Fatal(err)
	}
}

type cursor struct {
	x, y int
}

type editor struct {
	reader    terminal
	orignial  syscall.Termios
	height    int
	width     int
	editorUI  *bytes.Buffer
	cursor    cursor
	mode      int
	filename  string
	rowOffSet int
	colOffSet int
	numOfRows int
	rows      []erow
	rx        int
}

func (e *editor) appendRow(r string) {
	buf := bytes.NewBufferString(r)
	buf.WriteByte('\000')
	row := erow{chars: buf.String()}
	row.size = len(row.chars)

	buf.Reset()
	raw := []byte(r)
	rsize := 0
	for x := 0; x < len(raw); x++ {
		if raw[x] == '\t' {
			for i := 0; i < TAB_STOP; i++ {
				rsize++
				buf.WriteByte(' ')
			}
		} else {
			rsize++
			buf.WriteByte(raw[x])
		}
	}
	buf.WriteByte('\000')
	row.rsize = rsize
	row.render = buf.String()

	e.rows = append(e.rows, row)
}

func cursorxToRx(row erow, cx int) int {
	rx := 0
	raw := []byte(row.chars)
	for x := 0; x < cx; x++ {
		if raw[x] == '\t' {
			rx += (TAB_STOP - 1) - (rx % TAB_STOP)
		}
		rx++
	}

	return rx
}

var goedit editor

func init() {
	errorlog, errr := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if errr != nil {
		logger.Fatal(errr)
	}

	logger = log.New(errorlog, "goedit: ", log.Lshortfile|log.LstdFlags)

	goedit = editor{}
	goedit.mode = CMD_MODE

	goedit.reader = terminal(syscall.Stdin)
	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(goedit.reader), syscall.TCGETS, uintptr(unsafe.Pointer(&goedit.orignial)), 0, 0, 0)
	if err != 0 {
		logger.Fatal(err)
	}

	winsize := winsize{}
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(goedit.reader), syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&winsize))); err != 0 {
		logger.Fatal(err)
	}
	goedit.height = int(winsize.height) - 1
	goedit.width = int(winsize.width)

	goedit.editorUI = bytes.NewBufferString("")
}

func openFile(filename string) {
	file, err := os.Open(filename)
	if err != nil {
		logger.Fatal(err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		goedit.appendRow(scanner.Text())
	}

	goedit.numOfRows = len(goedit.rows)
	goedit.filename = filename
}

func drawStatusBar() {
	status := fmt.Sprintf("%.20s - %d lines", goedit.filename, goedit.numOfRows)
	length := len(status)
	rstatus := fmt.Sprintf("%d,%d", goedit.cursor.y+1, goedit.rx+1)
	rlength := len(rstatus)
	goedit.editorUI.WriteString("\x1b[7m")
	goedit.editorUI.WriteString(status)
	for x := length; x < goedit.width; x++ {
		if goedit.width-x == rlength {
			goedit.editorUI.WriteString(rstatus)
			break
		} else {
			goedit.editorUI.WriteString(" ")
		}
	}

	goedit.editorUI.WriteString("\x1b[m")
}

func drawRows() {
	for x := 0; x < goedit.height; x++ {
		filerow := x + goedit.rowOffSet
		if filerow >= goedit.numOfRows {
			goedit.editorUI.WriteString("~")
		} else {
			length := goedit.rows[filerow].rsize - goedit.colOffSet
			if length < 0 {
				length = 0
			}

			if length > goedit.width {
				length = goedit.width
			}

			text := []byte(goedit.rows[filerow].render)
			goedit.editorUI.Write(text[goedit.colOffSet:length])
		}

		goedit.editorUI.WriteString("\x1b[K")
		goedit.editorUI.WriteString("\r\n")
	}
}

func scroll() {
	goedit.rx = 0
	if goedit.cursor.y < goedit.numOfRows {
		goedit.rx = cursorxToRx(goedit.rows[goedit.cursor.y], goedit.cursor.x)
	}

	if goedit.cursor.y < goedit.rowOffSet {
		goedit.rowOffSet = goedit.cursor.y
	}

	if goedit.cursor.y >= goedit.rowOffSet+goedit.height {
		goedit.rowOffSet = goedit.cursor.y - goedit.height + 1
	}

	if goedit.rx < goedit.colOffSet {
		goedit.colOffSet = goedit.rx
	}

	if goedit.rx >= goedit.colOffSet+goedit.width {
		goedit.colOffSet = goedit.rx - goedit.width + 1
	}
}

func rawMode() {
	argp := goedit.orignial
	argp.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON
	argp.Oflag &^= syscall.OPOST
	argp.Cflag |= syscall.CS8
	argp.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG
	argp.Cc[syscall.VMIN] = 0
	argp.Cc[syscall.VTIME] = 1

	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(goedit.reader), 0x5404, uintptr(unsafe.Pointer(&argp)), 0, 0, 0)
	if err != 0 {
		logger.Fatal(err)
	}
}

func resetMode() {
	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(goedit.reader), 0x5404, uintptr(unsafe.Pointer(&goedit.orignial)), 0, 0, 0)
	if err != 0 {
		logger.Fatal(err)
	}
}

func readKey() rune {
	var buf [1]byte

	for {
		n, err := goedit.reader.Read(buf[:])
		if err != nil {
			logger.Fatal(err)
		}

		if n == 1 {
			break
		}
	}

	if buf[0] == '\x1b' {
		var seq [2]byte
		n, err := goedit.reader.Read(seq[:])
		if err != nil {
			logger.Fatal(err)
		}

		if n != 2 {
			return '\x1b'
		}

		if seq[0] == '[' {
			if seq[1] >= '0' && seq[1] <= '9' {
				var tilde [1]byte
				n, err := goedit.reader.Read(tilde[:])
				if err != nil {
					logger.Fatal(err)
				}

				if n != 1 {
					return '\x1b'
				}

				if tilde[0] == '~' {
					switch seq[1] {
					case '1', '7':
						return HOME_KEY
					case '3':
						return DEL_KEY
					case '4', '8':
						return END_KEY
					case '5':
						return PAGE_UP
					case '6':
						return PAGE_DOWN
					}
				}
			} else {
				switch seq[1] {
				case 'A':
					return CURSOR_UP
				case 'B':
					return CURSOR_DOWN
				case 'C':
					return CURSOR_RIGHT
				case 'D':
					return CURSOR_LEFT
				case 'H':
					return HOME_KEY
				case 'F':
					return END_KEY
				}
			}
		} else if seq[0] == 'O' {
			switch seq[1] {
			case 'H':
				return HOME_KEY
			case 'F':
				return END_KEY
			}
		}

		return '\x1b'
	}

	return bytes.Runes(buf[:])[0]
}

func (e *editor) moveCursor(key rune) {
	switch key {
	case CURSOR_DOWN:
		if e.cursor.y < goedit.numOfRows {
			e.cursor.y++
		}
	case CURSOR_UP:
		if e.cursor.y != 0 {
			e.cursor.y--
		}
	case CURSOR_LEFT:
		if e.cursor.x != 0 {
			e.cursor.x--
		} else if e.cursor.y > 0 {
			e.cursor.y--
			e.cursor.x = e.rows[e.cursor.y].size
		}
	case CURSOR_RIGHT:
		if e.cursor.y < e.numOfRows && e.cursor.x < e.rows[e.cursor.y].size {
			e.cursor.x++
		} else if e.cursor.y < e.numOfRows && e.cursor.x == e.rows[e.cursor.y].size {
			e.cursor.y++
			e.cursor.x = 0
		}
	}

	if e.cursor.y < e.numOfRows {
		if e.cursor.x > e.rows[e.cursor.y].size {
			e.cursor.x = e.rows[e.cursor.y].size
		}
	}
}

func clearScreen() {
	scroll()
	goedit.editorUI.Reset()
	goedit.editorUI.WriteString("\x1b[?25l")
	goedit.editorUI.WriteString("\x1b[H")
	drawRows()
	drawStatusBar()
	goedit.editorUI.WriteString(fmt.Sprintf("\x1b[%d;%dH", (goedit.cursor.y-goedit.rowOffSet)+1, (goedit.rx-goedit.colOffSet)+1))
	goedit.editorUI.WriteString("\x1b[?25h")

	goedit.reader.Write(goedit.editorUI.String())
	goedit.editorUI.Reset()
}

func processKeyPress() {
	key := readKey()

	switch key {
	case ('q' & 0x1f):
		resetMode()
		os.Exit(0)
	case CURSOR_DOWN, CURSOR_UP, CURSOR_LEFT, CURSOR_RIGHT:
		goedit.moveCursor(key)
	case 'h':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_LEFT)
		}
	case 'j':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_DOWN)
		}
	case 'k':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_UP)
		}
	case 'l':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_RIGHT)
		}
	case 'i':
		if goedit.mode == CMD_MODE {
			goedit.mode = INSERT_MODE
		}
	case '\x1b':
		goedit.mode = CMD_MODE
	case PAGE_UP:
		goedit.cursor.y = goedit.rowOffSet
		for x := 0; x < goedit.height; x++ {
			goedit.moveCursor(CURSOR_UP)
		}
	case PAGE_DOWN:
		goedit.cursor.y = goedit.rowOffSet + goedit.height - 1
		if goedit.cursor.y > goedit.numOfRows {
			goedit.cursor.y = goedit.numOfRows
		}
		for x := 0; x < goedit.height; x++ {
			goedit.moveCursor(CURSOR_DOWN)
		}
	case HOME_KEY:
		goedit.cursor.x = 0
	case END_KEY:
		if goedit.cursor.y < goedit.numOfRows {
			goedit.cursor.x = goedit.rows[goedit.cursor.y].size
		}

	}
}

func main() {
	rawMode()
	if len(os.Args) == 2 {
		openFile(os.Args[1])
	}

	for {
		clearScreen()
		processKeyPress()
	}
}
<commit_msg>message bar added<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"os"
	"syscall"
	"unsafe"
)

var errorlog *os.File
var logger *log.Logger

const (
	TAB_STOP = 4
)

const (
	CURSOR_UP    = 1000
	CURSOR_DOWN  = 1001
	CURSOR_LEFT  = 1002
	CURSOR_RIGHT = 1003
	PAGE_UP      = 1004
	PAGE_DOWN    = 1005
	HOME_KEY     = 1006
	END_KEY      = 1007
	DEL_KEY      = 1008
)

const (
	INSERT_MODE = 1
	CMD_MODE    = 2
)

type winsize struct {
	height uint16
	width  uint16
	x      uint16
	y      uint16
}

type erow struct {
	chars  string
	render string
	size   int
	rsize  int
}

type terminal int

func (t terminal) Read(buf []byte) (int, error) {
	return syscall.Read(int(t), buf)
}

func (t terminal) Write(s string) {
	b := bytes.NewBufferString(s)
	if _, err := syscall.Write(int(t), b.Bytes()); err != nil {
		logger.Fatal(err)
	}
}

type cursor struct {
	x, y int
}

type editor struct {
	reader    terminal
	orignial  syscall.Termios
	height    int
	width     int
	editorUI  *bytes.Buffer
	cursor    cursor
	mode      int
	filename  string
	rowOffSet int
	colOffSet int
	numOfRows int
	rows      []erow
	rx        int
	editormsg string
}

func (e *editor) appendRow(r string) {
	buf := bytes.NewBufferString(r)
	buf.WriteByte('\000')
	row := erow{chars: buf.String()}
	row.size = len(row.chars)

	buf.Reset()
	raw := []byte(r)
	rsize := 0
	for x := 0; x < len(raw); x++ {
		if raw[x] == '\t' {
			for i := 0; i < TAB_STOP; i++ {
				rsize++
				buf.WriteByte(' ')
			}
		} else {
			rsize++
			buf.WriteByte(raw[x])
		}
	}
	buf.WriteByte('\000')
	row.rsize = rsize
	row.render = buf.String()

	e.rows = append(e.rows, row)
}

func cursorxToRx(row erow, cx int) int {
	rx := 0
	raw := []byte(row.chars)
	for x := 0; x < cx; x++ {
		if raw[x] == '\t' {
			rx += (TAB_STOP - 1) - (rx % TAB_STOP)
		}
		rx++
	}

	return rx
}

var goedit editor

func init() {
	errorlog, errr := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if errr != nil {
		logger.Fatal(errr)
	}

	logger = log.New(errorlog, "goedit: ", log.Lshortfile|log.LstdFlags)

	goedit = editor{}
	goedit.mode = CMD_MODE

	goedit.reader = terminal(syscall.Stdin)
	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(goedit.reader), syscall.TCGETS, uintptr(unsafe.Pointer(&goedit.orignial)), 0, 0, 0)
	if err != 0 {
		logger.Fatal(err)
	}

	winsize := winsize{}
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(goedit.reader), syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&winsize))); err != 0 {
		logger.Fatal(err)
	}
	goedit.height = int(winsize.height) - 2
	goedit.width = int(winsize.width)

	goedit.editorUI = bytes.NewBufferString("")
}

func openFile(filename string) {
	file, err := os.Open(filename)
	if err != nil {
		logger.Fatal(err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		goedit.appendRow(scanner.Text())
	}

	goedit.numOfRows = len(goedit.rows)
	goedit.filename = filename
}

func drawStatusBar() {
	status := fmt.Sprintf("%.20s - %d lines", goedit.filename, goedit.numOfRows)
	length := len(status)
	rstatus := fmt.Sprintf("%d,%d", goedit.cursor.y+1, goedit.rx+1)
	rlength := len(rstatus)
	goedit.editorUI.WriteString("\x1b[7m")
	goedit.editorUI.WriteString(status)
	for x := length; x < goedit.width; x++ {
		if goedit.width-x == rlength {
			goedit.editorUI.WriteString(rstatus)
			break
		} else {
			goedit.editorUI.WriteString(" ")
		}
	}

	goedit.editorUI.WriteString("\x1b[m")
	goedit.editorUI.WriteString("\r\n")
}

func drawMessageBar() {
	goedit.editorUI.WriteString("\x1b[K")
	goedit.editorUI.WriteString(goedit.editormsg)
}

func drawRows() {
	for x := 0; x < goedit.height; x++ {
		filerow := x + goedit.rowOffSet
		if filerow >= goedit.numOfRows {
			goedit.editorUI.WriteString("~")
		} else {
			length := goedit.rows[filerow].rsize - goedit.colOffSet
			if length < 0 {
				length = 0
			}

			if length > goedit.width {
				length = goedit.width
			}

			text := []byte(goedit.rows[filerow].render)
			goedit.editorUI.Write(text[goedit.colOffSet:length])
		}

		goedit.editorUI.WriteString("\x1b[K")
		goedit.editorUI.WriteString("\r\n")
	}
}

func scroll() {
	goedit.rx = 0
	if goedit.cursor.y < goedit.numOfRows {
		goedit.rx = cursorxToRx(goedit.rows[goedit.cursor.y], goedit.cursor.x)
	}

	if goedit.cursor.y < goedit.rowOffSet {
		goedit.rowOffSet = goedit.cursor.y
	}

	if goedit.cursor.y >= goedit.rowOffSet+goedit.height {
		goedit.rowOffSet = goedit.cursor.y - goedit.height + 1
	}

	if goedit.rx < goedit.colOffSet {
		goedit.colOffSet = goedit.rx
	}

	if goedit.rx >= goedit.colOffSet+goedit.width {
		goedit.colOffSet = goedit.rx - goedit.width + 1
	}
}

func rawMode() {
	argp := goedit.orignial
	argp.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON
	argp.Oflag &^= syscall.OPOST
	argp.Cflag |= syscall.CS8
	argp.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG
	argp.Cc[syscall.VMIN] = 0
	argp.Cc[syscall.VTIME] = 1

	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(goedit.reader), 0x5404, uintptr(unsafe.Pointer(&argp)), 0, 0, 0)
	if err != 0 {
		logger.Fatal(err)
	}
}

func resetMode() {
	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(goedit.reader), 0x5404, uintptr(unsafe.Pointer(&goedit.orignial)), 0, 0, 0)
	if err != 0 {
		logger.Fatal(err)
	}
}

func readKey() rune {
	var buf [1]byte

	for {
		n, err := goedit.reader.Read(buf[:])
		if err != nil {
			logger.Fatal(err)
		}

		if n == 1 {
			break
		}
	}

	if buf[0] == '\x1b' {
		var seq [2]byte
		n, err := goedit.reader.Read(seq[:])
		if err != nil {
			logger.Fatal(err)
		}

		if n != 2 {
			return '\x1b'
		}

		if seq[0] == '[' {
			if seq[1] >= '0' && seq[1] <= '9' {
				var tilde [1]byte
				n, err := goedit.reader.Read(tilde[:])
				if err != nil {
					logger.Fatal(err)
				}

				if n != 1 {
					return '\x1b'
				}

				if tilde[0] == '~' {
					switch seq[1] {
					case '1', '7':
						return HOME_KEY
					case '3':
						return DEL_KEY
					case '4', '8':
						return END_KEY
					case '5':
						return PAGE_UP
					case '6':
						return PAGE_DOWN
					}
				}
			} else {
				switch seq[1] {
				case 'A':
					return CURSOR_UP
				case 'B':
					return CURSOR_DOWN
				case 'C':
					return CURSOR_RIGHT
				case 'D':
					return CURSOR_LEFT
				case 'H':
					return HOME_KEY
				case 'F':
					return END_KEY
				}
			}
		} else if seq[0] == 'O' {
			switch seq[1] {
			case 'H':
				return HOME_KEY
			case 'F':
				return END_KEY
			}
		}

		return '\x1b'
	}

	return bytes.Runes(buf[:])[0]
}

func (e *editor) moveCursor(key rune) {
	switch key {
	case CURSOR_DOWN:
		if e.cursor.y < goedit.numOfRows {
			e.cursor.y++
		}
	case CURSOR_UP:
		if e.cursor.y != 0 {
			e.cursor.y--
		}
	case CURSOR_LEFT:
		if e.cursor.x != 0 {
			e.cursor.x--
		} else if e.cursor.y > 0 {
			e.cursor.y--
			e.cursor.x = e.rows[e.cursor.y].size
		}
	case CURSOR_RIGHT:
		if e.cursor.y < e.numOfRows && e.cursor.x < e.rows[e.cursor.y].size {
			e.cursor.x++
		} else if e.cursor.y < e.numOfRows && e.cursor.x == e.rows[e.cursor.y].size {
			e.cursor.y++
			e.cursor.x = 0
		}
	}

	if e.cursor.y < e.numOfRows {
		if e.cursor.x > e.rows[e.cursor.y].size {
			e.cursor.x = e.rows[e.cursor.y].size
		}
	}
}

func clearScreen() {
	scroll()
	goedit.editorUI.Reset()
	goedit.editorUI.WriteString("\x1b[?25l")
	goedit.editorUI.WriteString("\x1b[H")
	drawRows()
	drawStatusBar()
	drawMessageBar()
	goedit.editorUI.WriteString(fmt.Sprintf("\x1b[%d;%dH", (goedit.cursor.y-goedit.rowOffSet)+1, (goedit.rx-goedit.colOffSet)+1))
	goedit.editorUI.WriteString("\x1b[?25h")

	goedit.reader.Write(goedit.editorUI.String())
	goedit.editorUI.Reset()
}

func processKeyPress() {
	key := readKey()

	switch key {
	case ('q' & 0x1f):
		resetMode()
		os.Exit(0)
	case CURSOR_DOWN, CURSOR_UP, CURSOR_LEFT, CURSOR_RIGHT:
		goedit.moveCursor(key)
	case 'h':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_LEFT)
		}
	case 'j':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_DOWN)
		}
	case 'k':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_UP)
		}
	case 'l':
		if goedit.mode == CMD_MODE {
			goedit.moveCursor(CURSOR_RIGHT)
		}
	case 'i':
		if goedit.mode == CMD_MODE {
			goedit.mode = INSERT_MODE
		}
	case '\x1b':
		goedit.mode = CMD_MODE
	case PAGE_UP:
		goedit.cursor.y = goedit.rowOffSet
		for x := 0; x < goedit.height; x++ {
			goedit.moveCursor(CURSOR_UP)
		}
	case PAGE_DOWN:
		goedit.cursor.y = goedit.rowOffSet + goedit.height - 1
		if goedit.cursor.y > goedit.numOfRows {
			goedit.cursor.y = goedit.numOfRows
		}
		for x := 0; x < goedit.height; x++ {
			goedit.moveCursor(CURSOR_DOWN)
		}
	case HOME_KEY:
		goedit.cursor.x = 0
	case END_KEY:
		if goedit.cursor.y < goedit.numOfRows {
			goedit.cursor.x = goedit.rows[goedit.cursor.y].size
		}

	}
}

func main() {
	rawMode()
	if len(os.Args) == 2 {
		openFile(os.Args[1])
	}

	for {
		clearScreen()
		processKeyPress()
	}
}
<|endoftext|>


<commit_before>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/loadimpact/speedboat/loadtest"
	"github.com/loadimpact/speedboat/runner"
	"github.com/loadimpact/speedboat/runner/simple"
	"golang.org/x/net/context"
	"io/ioutil"
	"os"
	"path"
	"time"
)

func makeTest(c *cli.Context) (test loadtest.LoadTest, err error) {
	base := ""
	conf := loadtest.NewConfig()
	if len(c.Args()) > 0 {
		filename := c.Args()[0]
		base = path.Dir(filename)
		data, err := ioutil.ReadFile(filename)
		if err != nil {
			return test, err
		}

		loadtest.ParseConfig(data, &conf)
	}

	if c.IsSet("script") {
		conf.Script = c.String("script")
		base = ""
	}
	if c.IsSet("url") {
		conf.URL = c.String("url")
	}
	if c.IsSet("duration") {
		conf.Duration = c.Duration("duration").String()
	}
	if c.IsSet("vus") {
		conf.VUs = c.Int("vus")
	}

	test, err = conf.Compile()
	if err != nil {
		return test, err
	}

	if test.Script != "" {
		srcb, err := ioutil.ReadFile(path.Join(base, test.Script))
		if err != nil {
			return test, err
		}
		test.Source = string(srcb)
	}

	return test, nil
}

func action(c *cli.Context) {
	test, err := makeTest(c)
	if err != nil {
		log.WithError(err).Fatal("Configuration error")
	}

	r := simple.New()
	r.URL = test.URL

	timeout := time.Duration(0)
	for _, stage := range test.Stages {
		timeout += stage.Duration
	}

	ctx, _ := context.WithTimeout(context.Background(), timeout)
	scale := make(chan int, 1)
	scale <- test.Stages[0].VUs.Start
	for t := range runner.Run(ctx, r, scale) {
		log.WithField("t", t).Info("Test Metric")
	}
}

// Configure the global logger.
func configureLogging(c *cli.Context) {
	if c.GlobalBool("verbose") {
		log.SetLevel(log.DebugLevel)
	}
}

func main() {
	// Free up -v and -h for our own flags
	cli.VersionFlag.Name = "version"
	cli.HelpFlag.Name = "help, ?"

	// Bootstrap using action-registered commandline flags
	app := cli.NewApp()
	app.Name = "speedboat"
	app.Usage = "A next-generation load generator"
	app.Version = "0.0.1a1"
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "More verbose output",
		},
		cli.StringFlag{
			Name:  "script, s",
			Usage: "Script to run (do not use with --url)",
		},
		cli.StringFlag{
			Name:  "url",
			Usage: "URL to test (do not use with --script)",
		},
		cli.IntFlag{
			Name:  "vus, u",
			Usage: "Number of VUs to simulate",
			Value: 10,
		},
		cli.DurationFlag{
			Name:  "duration, d",
			Usage: "Test duration",
			Value: time.Duration(10) * time.Second,
		},
	}
	app.Before = func(c *cli.Context) error {
		configureLogging(c)
		return nil
	}
	app.Action = action
	app.Run(os.Args)
}
<commit_msg>Quiet<commit_after>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/loadimpact/speedboat/loadtest"
	"github.com/loadimpact/speedboat/runner"
	"github.com/loadimpact/speedboat/runner/simple"
	"golang.org/x/net/context"
	"io/ioutil"
	"os"
	"path"
	"time"
)

func makeTest(c *cli.Context) (test loadtest.LoadTest, err error) {
	base := ""
	conf := loadtest.NewConfig()
	if len(c.Args()) > 0 {
		filename := c.Args()[0]
		base = path.Dir(filename)
		data, err := ioutil.ReadFile(filename)
		if err != nil {
			return test, err
		}

		loadtest.ParseConfig(data, &conf)
	}

	if c.IsSet("script") {
		conf.Script = c.String("script")
		base = ""
	}
	if c.IsSet("url") {
		conf.URL = c.String("url")
	}
	if c.IsSet("duration") {
		conf.Duration = c.Duration("duration").String()
	}
	if c.IsSet("vus") {
		conf.VUs = c.Int("vus")
	}

	test, err = conf.Compile()
	if err != nil {
		return test, err
	}

	if test.Script != "" {
		srcb, err := ioutil.ReadFile(path.Join(base, test.Script))
		if err != nil {
			return test, err
		}
		test.Source = string(srcb)
	}

	return test, nil
}

func action(c *cli.Context) {
	test, err := makeTest(c)
	if err != nil {
		log.WithError(err).Fatal("Configuration error")
	}

	r := simple.New()
	r.URL = test.URL

	timeout := time.Duration(0)
	for _, stage := range test.Stages {
		timeout += stage.Duration
	}

	results := []runner.Result{}

	ctx, _ := context.WithTimeout(context.Background(), timeout)
	scale := make(chan int, 1)
	scale <- test.Stages[0].VUs.Start
	for res := range runner.Run(ctx, r, scale) {
		results = append(results, res)
	}

	log.WithField("results", len(results)).Info("Finished")
}

// Configure the global logger.
func configureLogging(c *cli.Context) {
	if c.GlobalBool("verbose") {
		log.SetLevel(log.DebugLevel)
	}
}

func main() {
	// Free up -v and -h for our own flags
	cli.VersionFlag.Name = "version"
	cli.HelpFlag.Name = "help, ?"

	// Bootstrap using action-registered commandline flags
	app := cli.NewApp()
	app.Name = "speedboat"
	app.Usage = "A next-generation load generator"
	app.Version = "0.0.1a1"
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "More verbose output",
		},
		cli.StringFlag{
			Name:  "script, s",
			Usage: "Script to run (do not use with --url)",
		},
		cli.StringFlag{
			Name:  "url",
			Usage: "URL to test (do not use with --script)",
		},
		cli.IntFlag{
			Name:  "vus, u",
			Usage: "Number of VUs to simulate",
			Value: 10,
		},
		cli.DurationFlag{
			Name:  "duration, d",
			Usage: "Test duration",
			Value: time.Duration(10) * time.Second,
		},
	}
	app.Before = func(c *cli.Context) error {
		configureLogging(c)
		return nil
	}
	app.Action = action
	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
)

var (
	tcpPort   = flag.Int("tcpport", 0, "Socket连接或者监听的端口")
	httpPort  = flag.Int("httpport", 0, "当mode为server时为服务端监听端口，当为mode为client时为转发至本地客户端的端口")
	rpMode    = flag.String("mode", "client", "启动模式，可选为client、server")
	svrAddr   = flag.String("svraddr", "127.0.0.1", "当mode为client时有效，为连接服务器的地址")
	verifyKey = flag.String("vkey", "", "用作客户端与服务端连接时的校验")
)

func main() {
	flag.Parse()
	if *tcpPort <= 0 || *tcpPort >= 65536 {
		log.Fatalln("请输入正确的tcp端口。")
	}
	if *httpPort <= 0 || *httpPort >= 65536 {
		log.Fatalln("请输入正确的http端口。")
	}
	if *rpMode != "client" && *rpMode != "server" {
		log.Fatalln("请输入正确的服务启动模式")
	}
	if *rpMode == "server" && *tcpPort == *httpPort {
		log.Fatalln("tcp端口与http端口不能为同一个。")
	}
	if *verifyKey == "" {
		log.Fatalln("必须输入一个验证的key")
	}
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
	if *rpMode == "client" {
		log.Println("客户端启动，连接：", *svrAddr, "， 端口：", *tcpPort, "， 并开启http服务端，端口为：", *httpPort)
		cli := NewRPClient(fmt.Sprintf("%s:%d", *svrAddr, *tcpPort), *httpPort)
		if err := cli.Start(); err != nil {
			log.Fatalln(err)
		}
		defer cli.Close()
	} else if *rpMode == "server" {
		log.Println("服务端启动，监听tcp服务端端口：", *tcpPort, "， http服务端端口：", *httpPort)
		svr := NewRPServer(*tcpPort, *httpPort)
		if err := svr.Start(); err != nil {
			log.Fatalln(err)
		}
		defer svr.Close()
	}
}
<commit_msg>增加客户端重连机制<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"time"
)

var (
	tcpPort   = flag.Int("tcpport", 0, "Socket连接或者监听的端口")
	httpPort  = flag.Int("httpport", 0, "当mode为server时为服务端监听端口，当为mode为client时为转发至本地客户端的端口")
	rpMode    = flag.String("mode", "client", "启动模式，可选为client、server")
	svrAddr   = flag.String("svraddr", "127.0.0.1", "当mode为client时有效，为连接服务器的地址")
	verifyKey = flag.String("vkey", "", "用作客户端与服务端连接时的校验")
)

func main() {
	flag.Parse()
	if *tcpPort <= 0 || *tcpPort >= 65536 {
		log.Fatalln("请输入正确的tcp端口。")
	}
	if *httpPort <= 0 || *httpPort >= 65536 {
		log.Fatalln("请输入正确的http端口。")
	}
	if *rpMode != "client" && *rpMode != "server" {
		log.Fatalln("请输入正确的服务启动模式")
	}
	if *rpMode == "server" && *tcpPort == *httpPort {
		log.Fatalln("tcp端口与http端口不能为同一个。")
	}
	if *verifyKey == "" {
		log.Fatalln("必须输入一个验证的key")
	}
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
	if *rpMode == "client" {
	retry:
		log.Println("客户端启动，连接：", *svrAddr, "， 端口：", *tcpPort, "， 并开启http服务端，端口为：", *httpPort)
		cli := NewRPClient(fmt.Sprintf("%s:%d", *svrAddr, *tcpPort), *httpPort)
		if err := cli.Start(); err != nil {
			log.Println(err)
			// 重连
			log.Println("5秒后重新连接...")
			time.Sleep(time.Second * 5)
			goto retry
		}
		defer cli.Close()
	} else if *rpMode == "server" {
		log.Println("服务端启动，监听tcp服务端端口：", *tcpPort, "， http服务端端口：", *httpPort)
		svr := NewRPServer(*tcpPort, *httpPort)
		if err := svr.Start(); err != nil {
			log.Fatalln(err)
		}
		defer svr.Close()
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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 (
    "log"
    "flag"
    "runtime"
    "os"
    "regexp"
    fsnotify "gopkg.in/fsnotify.v1"
    "sync"
    "path/filepath"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

func processFile(files chan string, wg *sync.WaitGroup, db *SymbolsDB) {
    defer wg.Done()

    // start exploring files
    for {
        file, ok := <-files

        if !ok { return }

        log.Println("exploring", file)
        if db.NeedToProcessFile(file) {
            Parse(file, db)
        }
    }
}

func explorePathToParse(path string,
                        visitDir func(string),
                        visitC func(string)) {
    path = filepath.Clean(path)
    toExplore := []string{path}
    for len(toExplore) > 0 {
        // dequeue first path
        path := toExplore[0]
        toExplore = toExplore[1:]

        f, err := os.Open(path)
        if err != nil {
            log.Println(err, " opening ", path, ", ignoring")
            continue
        }

        info, err := f.Stat()
        if err != nil {
            log.Println(err, " stating ", path, ", ignoring")
            continue
        }

        // visit file
        if !info.IsDir() {
            // ignore non-C files
            validC, _ := regexp.MatchString(`.*\.[ch]`, path)
            if validC {
                visitC(path)
            }
            continue
        } else {
            visitDir(path)
        }

        // add all the files in the directory to explore
        dirFiles, err := f.Readdir(0)
        if err != nil {
            log.Println(err, " readdir ", path, ", ignoring")
            continue
        }

        for _, subf := range dirFiles {
            // ignore hidden files
            if subf.Name()[0] == '.' {
                continue
            }

            relPath := path + "/" + subf.Name()
            relPath = filepath.Clean(relPath)

            toExplore = append(toExplore, relPath)
        }
    }
}

func handleChange(event fsnotify.Event,
                  db *SymbolsDB,
                  watcher *fsnotify.Watcher,
                  files chan string)  {

    visitorDir := func(path string) {
        // add watcher to directory
        watcher.Add(path)
    }
    visitorC := func(path string) {
        // add watcher
        watcher.Add(path)
        // put file in channel
        files <- path
    }

    switch {
    case event.Op & (fsnotify.Write | fsnotify.Create | fsnotify.Chmod) != 0:
        explorePathToParse(event.Name, visitorDir, visitorC)
    case event.Op & (fsnotify.Remove | fsnotify.Rename) != 0:
        watcher.Remove(event.Name)
        db.RemoveFileReferences(event.Name)
    }
}

func main() {
    // path to symbols DB
    var dbFile string
    flag.StringVar(&dbFile, "db", ".navc_dbsymbols", "Path to symbols path")

    // number of parallel indexing threads
    var nIndexingThreads int
    flag.IntVar(&nIndexingThreads, "numThreads", runtime.NumCPU(),
                "Number of indexing threads")

    flag.Parse()

    // socket file for communication with daemon
    socketFile := "/tmp/navc.sock"

    // list of directores with source to index
    var indexDir []string
    if len(flag.Args()) > 0 {
        indexDir = flag.Args()
    } else {
        indexDir = []string{"."}
    }

    // open databased of symbols
    db := OpenSymbolsDB(dbFile)
    defer db.Close()

    // start threads to process files
    var wg sync.WaitGroup
    files := make(chan string, nIndexingThreads)
    wg.Add(nIndexingThreads)
    for i := 0; i < nIndexingThreads; i++ {
        go processFile(files, &wg, db)
    }

    // start file watcher
    watcher, _ := fsnotify.NewWatcher()
    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            select {
            case event, ok := <-watcher.Events:
                if !ok { return }
                handleChange(event, db, watcher, files)

                log.Println("event ", event, event.Name)
            case err, ok := <-watcher.Errors:
                if !ok { return }

                log.Println("watcher error: ", err)
            }
        }
    }()

    // explore all the paths in indexDir and process all files
    removedFilesSet := db.GetSetFilesInDB()
    visitorDir := func(path string) {
        // add watcher to directory
        watcher.Add(path)
    }
    visitorC := func(path string) {
        // update set of removed files
        delete(removedFilesSet, path)
        // add watcher
        watcher.Add(path)
        // put file in channel
        files <- path
    }
    for _, path := range indexDir {
        explorePathToParse(path, visitorDir, visitorC)
    }

    // remove from DB deleted files
    for path := range removedFilesSet {
        db.RemoveFileReferences(path)
    }

    // start serving requests
    lis, err := net.Listen("unix", socketFile)
    if err != nil {
        log.Fatal("error opening socket", err)
    }
    defer os.Remove(socketFile)
    handler := rpc.NewServer()
    handler.Register(&RequestHandler{db})
    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            conn, err := lis.Accept()
            if err != nil {
                log.Println("accepting connection:", err, "breaking")
                return
            }

            codec := jsonrpc.NewServerCodec(conn)
            err = handler.ServeRequest(codec)
            if err != nil {
                log.Println("handling request:", err, ", ignoring")
                continue
            }
            codec.Close()
        }
    }()

    // TODO: handle ctl-c event here for more graceful termination
    select {}

    // wait for threads to finish
    lis.Close()
    watcher.Close()
    close(files)
    wg.Wait()
}
<commit_msg>Handling of interrupt signal and better handling of error path<commit_after>/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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 (
    "log"
    "flag"
    "runtime"
    "os"
    "os/signal"
    "regexp"
    fsnotify "gopkg.in/fsnotify.v1"
    "sync"
    "path/filepath"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

func processFile(files chan string, wg *sync.WaitGroup, db *SymbolsDB) {
    defer wg.Done()

    // start exploring files
    for {
        file, ok := <-files

        if !ok { return }

        log.Println("exploring", file)
        if db.NeedToProcessFile(file) {
            Parse(file, db)
        }
    }
}

func explorePathToParse(path string,
                        visitDir func(string),
                        visitC func(string)) {
    path = filepath.Clean(path)
    toExplore := []string{path}
    for len(toExplore) > 0 {
        // dequeue first path
        path := toExplore[0]
        toExplore = toExplore[1:]

        f, err := os.Open(path)
        if err != nil {
            log.Println(err, " opening ", path, ", ignoring")
            continue
        }

        info, err := f.Stat()
        if err != nil {
            log.Println(err, " stating ", path, ", ignoring")
            continue
        }

        // visit file
        if !info.IsDir() {
            // ignore non-C files
            validC, _ := regexp.MatchString(`.*\.[ch]`, path)
            if validC {
                visitC(path)
            }
            continue
        } else {
            visitDir(path)
        }

        // add all the files in the directory to explore
        dirFiles, err := f.Readdir(0)
        if err != nil {
            log.Println(err, " readdir ", path, ", ignoring")
            continue
        }

        for _, subf := range dirFiles {
            // ignore hidden files
            if subf.Name()[0] == '.' {
                continue
            }

            relPath := path + "/" + subf.Name()
            relPath = filepath.Clean(relPath)

            toExplore = append(toExplore, relPath)
        }
    }
}

func handleChange(event fsnotify.Event,
                  db *SymbolsDB,
                  watcher *fsnotify.Watcher,
                  files chan string)  {

    visitorDir := func(path string) {
        // add watcher to directory
        watcher.Add(path)
    }
    visitorC := func(path string) {
        // add watcher
        watcher.Add(path)
        // put file in channel
        files <- path
    }

    switch {
    case event.Op & (fsnotify.Write | fsnotify.Create | fsnotify.Chmod) != 0:
        explorePathToParse(event.Name, visitorDir, visitorC)
    case event.Op & (fsnotify.Remove | fsnotify.Rename) != 0:
        watcher.Remove(event.Name)
        db.RemoveFileReferences(event.Name)
    }
}

func main() {
    // path to symbols DB
    var dbFile string
    flag.StringVar(&dbFile, "db", ".navc_dbsymbols", "Path to symbols path")

    // number of parallel indexing threads
    var nIndexingThreads int
    flag.IntVar(&nIndexingThreads, "numThreads", runtime.NumCPU(),
                "Number of indexing threads")

    flag.Parse()

    // socket file for communication with daemon
    socketFile := "/tmp/navc.sock"

    // list of directores with source to index
    var indexDir []string
    if len(flag.Args()) > 0 {
        indexDir = flag.Args()
    } else {
        indexDir = []string{"."}
    }

    // handle interrup and kill signals
    intr := make(chan os.Signal, 1)
    signal.Notify(intr, os.Interrupt, os.Kill)

    // start threads to process files
    var wg sync.WaitGroup
    defer wg.Wait()

    // open databased of symbols
    db := OpenSymbolsDB(dbFile)
    defer db.Close()

    // start indexing threads
    files := make(chan string, nIndexingThreads)
    defer close(files)

    wg.Add(nIndexingThreads)
    for i := 0; i < nIndexingThreads; i++ {
        go processFile(files, &wg, db)
    }

    // start file watcher
    watcher, _ := fsnotify.NewWatcher()
    defer watcher.Close()
    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            select {
            case event, ok := <-watcher.Events:
                if !ok { return }
                handleChange(event, db, watcher, files)

                log.Println("event ", event, event.Name)
            case err, ok := <-watcher.Errors:
                if !ok { return }

                log.Println("watcher error: ", err)
            }
        }
    }()

    // explore all the paths in indexDir and process all files
    removedFilesSet := db.GetSetFilesInDB()
    visitorDir := func(path string) {
        // add watcher to directory
        watcher.Add(path)
    }
    visitorC := func(path string) {
        // update set of removed files
        delete(removedFilesSet, path)
        // add watcher
        watcher.Add(path)
        // put file in channel
        files <- path
    }
    for _, path := range indexDir {
        explorePathToParse(path, visitorDir, visitorC)
    }

    // remove from DB deleted files
    for path := range removedFilesSet {
        db.RemoveFileReferences(path)
    }

    // start serving requests
    lis, err := net.Listen("unix", socketFile)
    if err != nil {
        log.Println("error opening socket", err)
        return
    }
    defer os.Remove(socketFile)
    defer lis.Close()

    handler := rpc.NewServer()
    handler.Register(&RequestHandler{db})
    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            conn, err := lis.Accept()
            if err != nil {
                log.Println("accepting connection (breaking):", err)
                return
            }

            codec := jsonrpc.NewServerCodec(conn)
            err = handler.ServeRequest(codec)
            if err != nil {
                log.Println("handling request (ignoring):", err)
            }
            codec.Close()
        }
    }()

    // wait until ctl-c is pressed
    select {
    case <-intr:
    }
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"net/url"
	"strconv"

	"github.com/gorilla/mux"
	"github.com/paked/gerrycode/communicator"
	"github.com/paked/models"
	"gopkg.in/mgo.v2/bson"
)

// Image represents a struct that can be described with numbers
type Image struct {
	ID  bson.ObjectId `bson:"_id" json:"id"`
	URL string        `bson:"url" json:"url"`
}

func (i Image) BID() bson.ObjectId {
	return i.ID
}

func (i Image) C() string {
	return "images"
}

// Number represents a number that can describe an Image
type Number struct {
	ID     bson.ObjectId `bson:"_id" json:"id"`
	For    bson.ObjectId `bson:"for" json:"for"`
	Number float64       `bson:"number" json:"number"`
}

func (n Number) BID() bson.ObjectId {
	return n.ID
}

func (n Number) C() string {
	return "numbers"
}

func main() {
	models.Init("localhost", "imagenumberdescriptions")

	r := mux.NewRouter()
	api := r.PathPrefix("/api").Subrouter()

	r.HandleFunc("/", homeHandler).Methods("GET")

	api.HandleFunc("/image/new", createImageHandler).Methods("POST")
	api.HandleFunc("/image/{image_id}/number/new", addNumberHandler).Methods("POST")

	http.Handle("/", r)

	fmt.Println("Listening on port 8080")
	fmt.Println(http.ListenAndServe("localhost:8080", nil))
}

// Serves the home page, a grand spanking "hello!"
//   GET /
func homeHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello!")
}

// Handler to create a new "image" object and store it in the database
//   POST /api/image/new?url=<URL>
func createImageHandler(w http.ResponseWriter, r *http.Request) {
	c := communicator.New(w)
	location := r.FormValue("url")

	u, err := url.Parse(location)
	if err != nil {
		c.Fail("Not a good URL!")
		return
	}

	location = u.String()

	var i Image
	if err := models.Restore(&i, bson.M{"url": location}); err == nil {
		c.Fail("An image with that URL already exists")
		return
	}

	i = Image{
		ID:  bson.NewObjectId(),
		URL: location,
	}

	if err := models.Persist(i); err != nil {
		c.Error("An error with the database has occured")
		return
	}

	c.OKWithData("Here is your image", i)
}

// A handler to add a number to describe an image
//   POST /image/{image_id}/number/new?number=<A NUMBER>
func addNumberHandler(w http.ResponseWriter, r *http.Request) {
	c := communicator.New(w)
	imageId := mux.Vars(r)["image_id"]
	numberString := r.FormValue("number")

	number, err := strconv.ParseFloat(numberString, 64)
	if err != nil {
		c.Fail("That is not a valid float!")
		return
	}

	if !bson.IsObjectIdHex(imageId) {
		c.Fail("Not valid image id!")
		return
	}

	n := Number{
		ID:     bson.NewObjectId(),
		For:    bson.ObjectIdHex(imageId),
		Number: number,
	}

	if err := models.Persist(n); err != nil {
		c.Error("Something bad happened in the database...")
		return
	}

	c.OKWithData("Here is your number", n)
}
<commit_msg>main: add handler to retrieve numbers for an image<commit_after>package main

import (
	"fmt"
	"net/http"
	"net/url"
	"strconv"

	"github.com/gorilla/mux"
	"github.com/paked/gerrycode/communicator"
	"github.com/paked/models"
	"gopkg.in/mgo.v2/bson"
)

// Image represents a struct that can be described with numbers
type Image struct {
	ID  bson.ObjectId `bson:"_id" json:"id"`
	URL string        `bson:"url" json:"url"`
}

func (i Image) BID() bson.ObjectId {
	return i.ID
}

func (i Image) C() string {
	return "images"
}

// Number represents a number that can describe an Image
type Number struct {
	ID     bson.ObjectId `bson:"_id" json:"id"`
	For    bson.ObjectId `bson:"for" json:"for"`
	Number float64       `bson:"number" json:"number"`
}

func (n Number) BID() bson.ObjectId {
	return n.ID
}

func (n Number) C() string {
	return "numbers"
}

func main() {
	models.Init("localhost", "imagenumberdescriptions")

	r := mux.NewRouter()
	api := r.PathPrefix("/api").Subrouter()

	r.HandleFunc("/", homeHandler).Methods("GET")

	api.HandleFunc("/image/new", createImageHandler).Methods("POST")
	api.HandleFunc("/image/{image_id}/number/new", addNumberHandler).Methods("POST")
	api.HandleFunc("/image/{image_id}/numbers", getNumbersHandler).Methods("GET")

	http.Handle("/", r)

	fmt.Println("Listening on port 8080")
	fmt.Println(http.ListenAndServe("localhost:8080", nil))
}

// Serves the home page, a grand spanking "hello!"
//   GET /
func homeHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello!")
}

// Handler to create a new "image" object and store it in the database
//   POST /api/image/new?url=<URL>
func createImageHandler(w http.ResponseWriter, r *http.Request) {
	c := communicator.New(w)
	location := r.FormValue("url")

	u, err := url.Parse(location)
	if err != nil {
		c.Fail("Not a good URL!")
		return
	}

	location = u.String()

	var i Image
	if err := models.Restore(&i, bson.M{"url": location}); err == nil {
		c.Fail("An image with that URL already exists")
		return
	}

	i = Image{
		ID:  bson.NewObjectId(),
		URL: location,
	}

	if err := models.Persist(i); err != nil {
		c.Error("An error with the database has occured")
		return
	}

	c.OKWithData("Here is your image", i)
}

// A handler to add a number to describe an image
//   POST /image/{image_id}/number/new?number=<A NUMBER>
func addNumberHandler(w http.ResponseWriter, r *http.Request) {
	c := communicator.New(w)
	imageId := mux.Vars(r)["image_id"]
	numberString := r.FormValue("number")

	number, err := strconv.ParseFloat(numberString, 64)
	if err != nil {
		c.Fail("That is not a valid float!")
		return
	}

	if !bson.IsObjectIdHex(imageId) {
		c.Fail("Not valid image id!")
		return
	}

	n := Number{
		ID:     bson.NewObjectId(),
		For:    bson.ObjectIdHex(imageId),
		Number: number,
	}

	if err := models.Persist(n); err != nil {
		c.Error("Something bad happened in the database...")
		return
	}

	c.OKWithData("Here is your number", n)
}

// getNumbersHandler is a handler to get all the numbers which describe an image
//   GET /api/image/<IMAGE_ID>/numbers
func getNumbersHandler(w http.ResponseWriter, r *http.Request) {
	c := communicator.New(w)
	imageID := mux.Vars(r)["image_id"]

	if !bson.IsObjectIdHex(imageID) {
		c.Fail("The image id you provided is not valid!")
		return
	}

	var numbers []Number
	number := Number{}

	iter, err := models.Fetch(number.C(), bson.M{"for": bson.ObjectIdHex(imageID)}, "_id")
	if err != nil {
		c.Error("Something went wrong during the fetching!")
		return
	}

	for iter.Next(&number) {
		numbers = append(numbers, number)
		fmt.Println(number)
	}

	c.OKWithData("Here are your numbers", numbers)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"net/mail"
	"net/smtp"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"time"
)

const Version = "0.1"

var flagHelp bool
var flagHost string
var flagInterval int64
var flagMaxErrors int64
var flagSMTP string
var flagSMTPFrom string
var flagSMTPTLS bool
var flagSMTPTo string
var flagVerbose bool

func sendMail() {
	if flagSMTP == "" {
		return
	}

	c, err := smtp.Dial(flagSMTP)

	if err != nil {
		v("Cannot open SMTP connection: %v\n", err)

		return
	}

	c.Mail(flagSMTPFrom)
	c.Rcpt(flagSMTPTo)

	wc, err := c.Data()

	if err != nil {
		v("Cannot open Data writer: %v\n", err)

		return
	}

	defer wc.Close()

	buf := bytes.NewBufferString(`Return-path: <` + flagSMTPFrom + `>
From: ` + flagSMTPFrom + `
To: ` + flagSMTPTo + `
Subject: ` + flagHost + ` is down
Content-Transfer-Encoding: 7Bit
Content-Type: text/plain; charset="us-ascii"

` + flagHost + ` is not reachable via ping
`)

	if _, err = buf.WriteTo(wc); err != nil {
		v("Cannot write mail body: %v\n", err)
	}

	v("Mail sent\n")
}

func v(format string, a ...interface{}) {
	if flagVerbose {
		fmt.Fprintf(os.Stderr, format, a...)
	}
}

func main() {

	flag.BoolVar(&flagHelp, "help", false, "Show this help")
	flag.StringVar(&flagHost, "host", "", "The host to ping")
	flag.Int64Var(&flagInterval, "interval", 60, "Ping interval in seconds")
	flag.Int64Var(&flagMaxErrors, "max-errors", 5, "How many pings can fail before a report is sent")
	flag.StringVar(&flagSMTP, "smtp", "", "The SMTP server + port for sending report mails")
	flag.StringVar(&flagSMTPFrom, "smtp-from", "", "From-mail address")
	flag.BoolVar(&flagSMTPTLS, "smtp-tls", false, "Use TLS for the SMTP connection")
	flag.StringVar(&flagSMTPTo, "smtp-to", "", "To-mail address")
	flag.BoolVar(&flagVerbose, "verbose", false, "Do verbose output")

	flag.Parse()

	if flagHost == "" || flagInterval <= 0 || flagMaxErrors <= 0 || flagHelp {
		fmt.Printf("glamor v%s\n", Version)
		fmt.Printf("usage:\n")
		fmt.Printf("\t%s -host <host> -interval <interval>\n", os.Args[0])
		fmt.Printf("options\n")
		flag.PrintDefaults()
		fmt.Printf("\n")

		if !flagHelp {
			panic("Wrong arguments")
		}
	}

	if _, err := mail.ParseAddress(flagSMTPFrom); err != nil {
		panic("smtp-from is not a valid mail address")
	} else if _, err := mail.ParseAddress(flagSMTPTo); err != nil {
		panic("smtp-to is not a valid mail address")
	}

	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)

	go func() {
		s := <-sig
		v("Catched signal \"%v\", will exit now\n", s)

		os.Exit(0)
	}()

	var sentMail = false
	var errors int64 = 0

	for {
		var cmd = exec.Command("ping", []string{"-c", "1", flagHost}...)

		out, err := cmd.CombinedOutput()

		if err != nil {
			v("Ping failed to %s: %v\n", flagHost, err)
		}

		if strings.Contains(string(out), "1 received") {
			errors--

			if errors < 100 {
				errors = 0
				sentMail = false

				v("Reset mail sent flag\n")
			}
		} else {
			errors++

			if errors >= flagMaxErrors {
				errors = 0

				v("Reached error count\n")

				if !sentMail {
					sendMail()

					sentMail = true
				}
			}
		}

		time.Sleep(time.Duration(flagInterval) * time.Second)
	}

	return
}
<commit_msg>bad english<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"net/mail"
	"net/smtp"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"time"
)

const Version = "0.1"

var flagHelp bool
var flagHost string
var flagInterval int64
var flagMaxErrors int64
var flagSMTP string
var flagSMTPFrom string
var flagSMTPTLS bool
var flagSMTPTo string
var flagVerbose bool

func sendMail() {
	if flagSMTP == "" {
		return
	}

	c, err := smtp.Dial(flagSMTP)

	if err != nil {
		v("Cannot open SMTP connection: %v\n", err)

		return
	}

	c.Mail(flagSMTPFrom)
	c.Rcpt(flagSMTPTo)

	wc, err := c.Data()

	if err != nil {
		v("Cannot open Data writer: %v\n", err)

		return
	}

	defer wc.Close()

	buf := bytes.NewBufferString(`Return-path: <` + flagSMTPFrom + `>
From: ` + flagSMTPFrom + `
To: ` + flagSMTPTo + `
Subject: ` + flagHost + ` is down
Content-Transfer-Encoding: 7Bit
Content-Type: text/plain; charset="us-ascii"

` + flagHost + ` is not reachable via ping
`)

	if _, err = buf.WriteTo(wc); err != nil {
		v("Cannot write mail body: %v\n", err)
	}

	v("Mail sent\n")
}

func v(format string, a ...interface{}) {
	if flagVerbose {
		fmt.Fprintf(os.Stderr, format, a...)
	}
}

func main() {
	flag.BoolVar(&flagHelp, "help", false, "Show this help")
	flag.StringVar(&flagHost, "host", "", "The host to ping")
	flag.Int64Var(&flagInterval, "interval", 60, "Ping interval in seconds")
	flag.Int64Var(&flagMaxErrors, "max-errors", 5, "How many pings can fail before a report is sent")
	flag.StringVar(&flagSMTP, "smtp", "", "The SMTP server + port for sending report mails")
	flag.StringVar(&flagSMTPFrom, "smtp-from", "", "From-mail address")
	flag.BoolVar(&flagSMTPTLS, "smtp-tls", false, "Use TLS for the SMTP connection")
	flag.StringVar(&flagSMTPTo, "smtp-to", "", "To-mail address")
	flag.BoolVar(&flagVerbose, "verbose", false, "Do verbose output")

	flag.Parse()

	if flagHost == "" || flagInterval <= 0 || flagMaxErrors <= 0 || flagHelp {
		fmt.Printf("glamor v%s\n", Version)
		fmt.Printf("usage:\n")
		fmt.Printf("\t%s -host <host> -interval <interval>\n", os.Args[0])
		fmt.Printf("options\n")
		flag.PrintDefaults()
		fmt.Printf("\n")

		if !flagHelp {
			panic("Wrong arguments")
		}
	}

	if _, err := mail.ParseAddress(flagSMTPFrom); err != nil {
		panic("smtp-from is not a valid mail address")
	} else if _, err := mail.ParseAddress(flagSMTPTo); err != nil {
		panic("smtp-to is not a valid mail address")
	}

	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)

	go func() {
		s := <-sig
		v("Caught signal \"%v\", will exit now\n", s)

		os.Exit(0)
	}()

	var sentMail = false
	var errors int64 = 0

	for {
		var cmd = exec.Command("ping", []string{"-c", "1", flagHost}...)

		out, err := cmd.CombinedOutput()

		if err != nil {
			v("Ping failed to %s: %v\n", flagHost, err)
		}

		if strings.Contains(string(out), "1 received") {
			errors--

			if errors < 100 {
				errors = 0
				sentMail = false

				v("Reset mail sent flag\n")
			}
		} else {
			errors++

			if errors >= flagMaxErrors {
				errors = 0

				v("Reached error count\n")

				if !sentMail {
					sendMail()

					sentMail = true
				}
			}
		}

		time.Sleep(time.Duration(flagInterval) * time.Second)
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/StackExchange/dnscontrol/commands"
	_ "github.com/StackExchange/dnscontrol/providers/_all"
)

//go:generate go run build/generate/generate.go build/generate/featureMatrix.go

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	os.Exit(commands.Run(versionString()))
}

// Version management. 2 Goals:
// 1. Someone who just does "go get" has at least some information.
// 2. If built with build/build.go, more specific build information gets put in.
// Update the number here manually each release, so at least we have a range for go-get people.
var (
	SHA       = ""
	Version   = "0.3.0"
	BuildTime = ""
)

// printVersion prints the version banner.
func versionString() string {
	var version string
	if SHA != "" {
		version = fmt.Sprintf("%s (%s)", Version, SHA)
	} else {
		version = fmt.Sprintf("%s-dev", Version) // no SHA. '0.x.y-dev' indicates it is run from source without build script.
	}
	if BuildTime != "" {
		i, err := strconv.ParseInt(BuildTime, 10, 64)
		if err == nil {
			tm := time.Unix(i, 0)
			version += fmt.Sprintf(" built %s", tm.Format(time.RFC822))
		}
	}
	return fmt.Sprintf("dnscontrol %s", version)
}
<commit_msg>Release v2.9<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/StackExchange/dnscontrol/commands"
	_ "github.com/StackExchange/dnscontrol/providers/_all"
)

//go:generate go run build/generate/generate.go build/generate/featureMatrix.go

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	os.Exit(commands.Run(versionString()))
}

// Version management. 2 Goals:
// 1. Someone who just does "go get" has at least some information.
// 2. If built with build/build.go, more specific build information gets put in.
// Update the number here manually each release, so at least we have a range for go-get people.
var (
	SHA       = ""
	Version   = "2.9"
	BuildTime = ""
)

// printVersion prints the version banner.
func versionString() string {
	var version string
	if SHA != "" {
		version = fmt.Sprintf("%s (%s)", Version, SHA)
	} else {
		version = fmt.Sprintf("%s-dev", Version) // no SHA. '0.x.y-dev' indicates it is run from source without build script.
	}
	if BuildTime != "" {
		i, err := strconv.ParseInt(BuildTime, 10, 64)
		if err == nil {
			tm := time.Unix(i, 0)
			version += fmt.Sprintf(" built %s", tm.Format(time.RFC822))
		}
	}
	return fmt.Sprintf("dnscontrol %s", version)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"github.com/ActiveState/tail"
	"github.com/op/go-logging"
	"github.com/rcrowley/go-metrics"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"
)

type Datasource struct {
	Name        string
	Create_date time.Time
	Params      string
}

type state struct {
	*sync.RWMutex // inherits locking methods
	Vals          []Datasource
}

// declare a globally scoped State variable, otherwise
// the request handlers can't get to it. If there is a better
// way to do this, plmk.
var State = &state{&sync.RWMutex{}, []Datasource{}}

func makeHandler(fn func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		fn(w, r)
	}
}

func jsonHandler(w http.ResponseWriter, r *http.Request) {
	m := metrics.GetOrRegisterTimer("www/jsonHandler", metrics.DefaultRegistry)
	m.Time(func() {
		var log = logging.MustGetLogger("example")

		log.Notice("jsonHandler: acquiring read-lock")
		State.RLock() // grab a lock, but then don't forget to
		log.Notice("jsonHandler: got read-lock")
		defer State.RUnlock() // unlock it again once we're done
		defer log.Notice("jsonHandler: releaseing read-lock")

		log.Info(fmt.Sprintf("Request for %s\n", r.URL.Path))
		js, err := json.Marshal(State.Vals)

		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		w.Write(js)
	})
}

func statsHandler(w http.ResponseWriter, r *http.Request) {
	m := metrics.GetOrRegisterTimer("www/statsHandler", metrics.DefaultRegistry)
	m.Time(func() {
		w.Header().Set("Content-Type", "application/json")

		metrics.WriteJSONOnce(metrics.DefaultRegistry, w)

	})
}
func parseTime(s string) time.Time {
	// 24/08/2014 20:59:54
	var t, _ = time.Parse("02/01/2006 15:04:05", s)
	return t
}

func addItemToState(ds Datasource) {
	m_ds := metrics.GetOrRegisterCounter("tail/datasources", metrics.DefaultRegistry)
	defer m_ds.Inc(1)

	var log = logging.MustGetLogger("example")
	log.Notice("addItemToState: acquiring write-lock")
	State.Lock()
	defer State.Unlock()
	defer log.Notice("addItemToState: released write-lock")
	log.Notice("addItemToState: got write-lock")
	State.Vals = append(State.Vals, ds)
}

func tailLogfile(c chan string) {
	m_lines := metrics.GetOrRegisterCounter("tail/input_lines", metrics.DefaultRegistry)

	var log = logging.MustGetLogger("example")

	var dataPath = regexp.MustCompile(`.*out:(.*) :: \[creates\] creating database file .*/whisper/(.*)\.wsp (.*)`)
	t, err := tail.TailFile("./creates.log", tail.Config{Follow: true, ReOpen: true, MustExist: true})
	if err == nil {
		for line := range t.Lines {
			m_lines.Inc(1)
			match := dataPath.FindStringSubmatch(line.Text)
			if len(match) > 0 {
				ds := fmt.Sprintf("%s", strings.Replace(match[2], `/`, `.`, -1))
				// log: 	  /opt/graphite/storage/whisper/big-imac-2011_local/collectd/memory/memory-inactive.wsp
				// real:          mac-mini-2014_local.collectd.memory.memory-active
				// found: 	  big-imac-2011_local.collectd.memory.memory-inactive
				tmp := Datasource{Name: ds, Create_date: parseTime(match[1]), Params: match[3]}
				addItemToState(tmp)
				log.Notice(fmt.Sprintf("Found new datasource, total: %v, newly added: %+v", len(State.Vals), tmp))
			}
		}
	}
	c <- fmt.Sprintf("%s", err)
}

func metricsRegister() {
	c := metrics.NewCounter()
	metrics.Register("foo", c)
	c.Inc(47)
}

func main() {
	error_channel := make(chan string)

	// Set up Logger
	// Setup logger https://github.com/op/go-logging/blob/master/examples/example.go
	//var log = logging.MustGetLogger("example")
	//var format = "%{color}%{time:15:04:05.000000} [%{pid}] ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}"
	var format = "%{color}%{time:15:04:05} [%{pid}] ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}"
	logging.SetFormatter(logging.MustStringFormatter(format))

	// Set up metrics registry
	go metrics.Log(
		metrics.DefaultRegistry,
		5e9, // Xe9 -> X seconds
		log.New(os.Stderr, "metrics ", log.Lmicroseconds),
	)

	// Set up web handlers in goroutines

	http.HandleFunc("/json/", makeHandler(jsonHandler))
	http.HandleFunc("/stats/", makeHandler(statsHandler))
	go http.ListenAndServe(":2934", nil)
	go tailLogfile(error_channel)

	//log.Notice("Graphite News -- Showing which new metrics are available since 2014\n")
	//log.Notice("Graphite News -- Serving UI on: http://localhost:2934\n")

	// Wait for errors to appear then shut down
	fmt.Println(<-error_channel)
}
<commit_msg>changed logging, request handling<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"github.com/ActiveState/tail"
	"github.com/rcrowley/go-metrics"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"
)

type Datasource struct {
	Name        string
	Create_date time.Time
	Params      string
}

type state struct {
	*sync.RWMutex // inherits locking methods
	Vals          []Datasource
}

const myLogFormat = log.Ldate | log.Ltime

// declare a globally scoped State variable, otherwise
// the request handlers can't get to it. If there is a better
// way to do this, plmk.
var State = &state{&sync.RWMutex{}, []Datasource{}}

func makeHandler(fn func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		l := log.New(os.Stdout, "http	", myLogFormat)
		l.Printf("Request: %+v %+v", r.URL, r)
		m := metrics.GetOrRegisterTimer(fmt.Sprintf("%s%s", r.Method, r.URL.Path), metrics.DefaultRegistry)
		m.Time(func() {
			fn(w, r)
		})
	}
}

func jsonHandler(w http.ResponseWriter, r *http.Request) {
	l := log.New(os.Stdout, "http	", myLogFormat)

	State.RLock() // grab a lock, but then don't forget to
	defer State.RUnlock() // unlock it again once we're done

	l.Printf("Request for %s\n", r.URL.Path)
	js, err := json.Marshal(State.Vals)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(js)
}

func statsHandler(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		metrics.WriteJSONOnce(metrics.DefaultRegistry, w)
}

func parseTime(s string) time.Time {
	// 24/08/2014 20:59:54
	var t, _ = time.Parse("02/01/2006 15:04:05", s)
	return t
}

func addItemToState(ds Datasource) {
	l := log.New(os.Stdout, "tail	", myLogFormat)
	m_ds := metrics.GetOrRegisterCounter("tail/datasources", metrics.DefaultRegistry)
	defer m_ds.Inc(1)

	// We're writing to shared datastructures, grab a Write-lock
	State.Lock()
	defer State.Unlock()

	State.Vals = append(State.Vals, ds)
	l.Printf("New datasource: %+v (total: %v)", ds.Name, len(State.Vals))
}

func tailLogfile(c chan string) {
	m_lines := metrics.GetOrRegisterCounter("tail/input_lines", metrics.DefaultRegistry)

	var dataPath = regexp.MustCompile(`.*out:(.*) :: \[creates\] creating database file .*/whisper/(.*)\.wsp (.*)`)
	t, err := tail.TailFile("./creates.log", tail.Config{Follow: true, ReOpen: true, MustExist: true})
	if err == nil {
		for line := range t.Lines {
			m_lines.Inc(1)
			match := dataPath.FindStringSubmatch(line.Text)
			if len(match) > 0 {
				ds := fmt.Sprintf("%s", strings.Replace(match[2], `/`, `.`, -1))
				tmp := Datasource{Name: ds, Create_date: parseTime(match[1]), Params: match[3]}
				addItemToState(tmp)
			}
		}
	}
	c <- fmt.Sprintf("%s", err)
}

func main() {
	error_channel := make(chan string)
	l := log.New(os.Stdout, "main	", myLogFormat)

	// Set up metrics registry
	go metrics.Log(
		metrics.DefaultRegistry,
		5e9, // Xe9 -> X seconds
		log.New(os.Stdout, "metrics	", myLogFormat))

	// Set up web handlers in goroutines

	http.HandleFunc("/json/", makeHandler(jsonHandler))
	http.HandleFunc("/stats/", makeHandler(statsHandler))
	go http.ListenAndServe(":2934", nil)
	go tailLogfile(error_channel)

	l.Println("Graphite News -- Showing which new metrics are available since 2014\n")
	l.Println("Graphite News -- Serving UI on: http://localhost:2934\n")

	// Wait for errors to appear then shut down
	l.Println(<-error_channel)
}
<|endoftext|>