<commit_before>package main

import (
	"container/ring"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
	"github.com/fatih/color"
	"github.com/lucagrulla/cw/cloudwatch"
	kingpin "gopkg.in/alecthomas/kingpin.v2"
)

var (
	timeFormat = "2006-01-02T15:04:05"
	version    = "2.1.3"

	kp = kingpin.New("cw", "The best way to tail AWS Cloudwatch Logs from your terminal.")

	awsProfile = kp.Flag("profile", "The target AWS profile. By default cw will use the default profile defined in the .aws/credentials file.").Short('p').String()
	awsRegion  = kp.Flag("region", "The target AWS region. By default cw will use the default region defined in the .aws/credentials file.").Short('r').String()
	noColor    = kp.Flag("no-color", "Disable coloured output.").Short('c').Default("false").Bool()
	debug      = kp.Flag("debug", "Enable debug logging.").Short('d').Default("false").Hidden().Bool()

	lsCommand = kp.Command("ls", "Show an entity.")

	lsGroups = lsCommand.Command("groups", "Show all groups.")

	lsStreams      = lsCommand.Command("streams", "Show all streams in a given log group.")
	lsLogGroupName = lsStreams.Arg("group", "The group name.").Required().String()

	tailCommand        = kp.Command("tail", "Tail log groups/streams.")
	logGroupStreamName = tailCommand.Arg("groupName[:logStreamPrefix]", "The log group and stream name, with group:prefix syntax."+
		"Stream name can be just the prefix. If no stream name is specified all stream names in the given group will be tailed."+
		"Multiple group/stream tuple can be passed. e.g. cw tail group1:prefix1 group2:prefix2 group3:prefix3.").
		Required().Strings()

	follow          = tailCommand.Flag("follow", "Don't stop when the end of streams is reached, but rather wait for additional data to be appended.").Short('f').Default("false").Bool()
	printTimestamp  = tailCommand.Flag("timestamp", "Print the event timestamp.").Short('t').Default("false").Bool()
	printEventID    = tailCommand.Flag("event-id", "Print the event Id.").Short('i').Default("false").Bool()
	printStreamName = tailCommand.Flag("stream-name", "Print the log stream name this event belongs to.").Short('s').Default("false").Bool()
	printGroupName  = tailCommand.Flag("group-name", "Print the log log group name this event belongs to.").Short('n').Default("false").Bool()
	startTime       = tailCommand.Flag("start", "The UTC start time. Passed as either date/time or human-friendly format."+
		" The human-friendly format accepts the number of hours and minutes prior to the present. "+
		"Denote hours with 'h' and minutes with 'm' i.e. 80m, 4h30m."+
		" If just time is used (format: hh[:mm]) it is expanded to today at the given time."+
		" Full available date/time format: 2017-02-27[T09[:00[:00]].").
		Short('b').Default(time.Now().UTC().Add(-30 * time.Second).Format(timeFormat)).String()
	endTime = tailCommand.Flag("end", "The UTC end time. Passed as either date/time or human-friendly format. "+
		"The human-friendly format accepts the number of hours and minutes prior to the present. "+
		"Denote hours with 'h' and minutes with 'm' i.e. 80m, 4h30m."+
		"If just time is used (format: hh[:mm]) it is expanded to today at the given time. Full available date/time format: 2017-02-27[T09[:00[:00]].").
		Short('e').Default("").String()
	local = tailCommand.Flag("local", "Treat date and time in Local timezone.").Short('l').Default("false").Bool()
	grep  = tailCommand.Flag("grep", "Pattern to filter logs by. See http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html for syntax.").
		Short('g').Default("").String()
	grepv = tailCommand.Flag("grepv", "Equivalent of grep --invert-match. Invert match pattern to filter logs by.").Short('v').Default("").String()
)

func timestampToTime(timeStamp *string) time.Time {
	var zone *time.Location
	if *local {
		zone = time.Local
	} else {
		zone = time.UTC
	}
	if regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`).MatchString(*timeStamp) {
		t, _ := time.ParseInLocation("2006-01-02", *timeStamp, zone)
		return t
	} else if regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}$`).MatchString(*timeStamp) {
		t, _ := time.ParseInLocation("2006-01-02T15", *timeStamp, zone)
		return t
	} else if regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$`).MatchString(*timeStamp) {
		t, _ := time.ParseInLocation("2006-01-02T15:04", *timeStamp, zone)
		return t
	} else if regexp.MustCompile(`^\d{1,2}$`).MatchString(*timeStamp) {
		y, m, d := time.Now().In(zone).Date()
		t, _ := strconv.Atoi(*timeStamp)
		return time.Date(y, m, d, t, 0, 0, 0, zone)
	} else if res := regexp.MustCompile(`^(?P<Hour>\d{1,2}):(?P<Minute>\d{2})$`).FindStringSubmatch(*timeStamp); res != nil {
		y, m, d := time.Now().Date()

		t, _ := strconv.Atoi(res[1])
		mm, _ := strconv.Atoi(res[2])

		return time.Date(y, m, d, t, mm, 0, 0, zone)
	} else if regexp.MustCompile(`^\d{1,}h$|^\d{1,}m$|^\d{1,}h\d{1,}m$`).MatchString(*timeStamp) {
		d, _ := time.ParseDuration(*timeStamp)

		t := time.Now().In(zone).Add(-d)
		y, m, dd := t.Date()
		return time.Date(y, m, dd, t.Hour(), t.Minute(), 0, 0, zone)
	}

	//TODO check even last scenario and if it's not a recognized pattern throw an error
	t, _ := time.ParseInLocation("2006-01-02T15:04:05", *timeStamp, zone)
	return t
}

type logEvent struct {
	logEvent cloudwatchlogs.FilteredLogEvent
	logGroup string
}

func formatLogMsg(ev logEvent, printTime *bool, printStreamName *bool, printGroupName *bool) string {
	msg := *ev.logEvent.Message
	if *printEventID {
		if *noColor {
			msg = fmt.Sprintf("%s - %s", *ev.logEvent.EventId, msg)
		} else {
			msg = fmt.Sprintf("%s - %s", color.YellowString(*ev.logEvent.EventId), msg)
		}
	}
	if *printStreamName {
		if *noColor {
			msg = fmt.Sprintf("%s - %s", *ev.logEvent.LogStreamName, msg)
		} else {
			msg = fmt.Sprintf("%s - %s", color.BlueString(*ev.logEvent.LogStreamName), msg)
		}
	}

	if *printGroupName {
		if *noColor {
			msg = fmt.Sprintf("%s - %s", ev.logGroup, msg)
		} else {
			msg = fmt.Sprintf("%s - %s", color.CyanString(ev.logGroup), msg)
		}
	}

	if *printTime {
		eventTimestamp := *ev.logEvent.Timestamp / 1000
		ts := time.Unix(eventTimestamp, 0).Format(timeFormat)
		if *noColor {
			msg = fmt.Sprintf("%s - %s", ts, msg)
		} else {
			msg = fmt.Sprintf("%s - %s", color.GreenString(ts), msg)
		}
	}
	return msg
}

func main() {
	kp.Version(version).Author("Luca Grulla")

	defer newVersionMsg(version, fetchLatestVersion(), *noColor)
	go versionCheckOnSigterm()

	cmd := kingpin.MustParse(kp.Parse(os.Args[1:]))
	c := cloudwatch.New(awsProfile, awsRegion, debug)
	switch cmd {
	case "ls groups":

		for msg := range c.LsGroups() {
			fmt.Println(*msg)
		}
	case "ls streams":
		for msg := range c.LsStreams(lsLogGroupName, nil) {
			fmt.Println(*msg)
		}
	case "tail":
		st := timestampToTime(startTime)
		var et time.Time
		if *endTime != "" {
			et = timestampToTime(endTime)
		}
		out := make(chan *logEvent)

		var wg sync.WaitGroup

		triggerChannels := make([]chan<- time.Time, len(*logGroupStreamName))

		for idx, gs := range *logGroupStreamName {
			trigger := make(chan time.Time, 1)
			go func(groupStream string) {
				tokens := strings.Split(groupStream, ":")
				var prefix string
				group := tokens[0]
				if len(tokens) > 1 && tokens[1] != "*" {
					prefix = tokens[1]
				}
				for c := range c.Tail(&group, &prefix, follow, &st, &et, grep, grepv, trigger) {
					out <- &logEvent{logEvent: *c, logGroup: group}
				}
				wg.Done()
			}(gs)
			triggerChannels[idx] = trigger
			wg.Add(1)
		}
		coordinator := &tailCoordinator{}
		coordinator.start(triggerChannels)

		go func() {
			wg.Wait()
			if *debug {
				fmt.Println("closing main channel...")
			}
			close(out)
		}()

		for logEv := range out {
			fmt.Println(formatLogMsg(*logEv, printTimestamp, printStreamName, printGroupName))
		}
	}
}

type tailCoordinator struct {
	targets *ring.Ring
}

func (f *tailCoordinator) start(targets []chan<- time.Time) {
	f.targets = ring.New(len(targets))
	for i := 0; i < f.targets.Len(); i++ {
		f.targets.Value = targets[i]
		f.targets = f.targets.Next()
	}
	//AWS API accepts 5 reqs/sec for account
	ticker := time.NewTicker(205 * time.Millisecond)
	go func() {
		for range ticker.C {
			x := f.targets.Value.(chan<- time.Time)
			x <- time.Now()
			f.targets = f.targets.Next()
		}
	}()

}

//TODO validate new group:prefix syntax
//TODO fix autocompletion
//TODO use Context for graceful shutdown of each tail activity
<commit_msg>Fix doc<commit_after>package main

import (
	"container/ring"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
	"github.com/fatih/color"
	"github.com/lucagrulla/cw/cloudwatch"
	kingpin "gopkg.in/alecthomas/kingpin.v2"
)

var (
	timeFormat = "2006-01-02T15:04:05"
	version    = "2.1.3"

	kp = kingpin.New("cw", "The best way to tail AWS Cloudwatch Logs from your terminal.")

	awsProfile = kp.Flag("profile", "The target AWS profile. By default cw will use the default profile defined in the .aws/credentials file.").Short('p').String()
	awsRegion  = kp.Flag("region", "The target AWS region. By default cw will use the default region defined in the .aws/credentials file.").Short('r').String()
	noColor    = kp.Flag("no-color", "Disable coloured output.").Short('c').Default("false").Bool()
	debug      = kp.Flag("debug", "Enable debug logging.").Short('d').Default("false").Hidden().Bool()

	lsCommand = kp.Command("ls", "Show an entity.")

	lsGroups = lsCommand.Command("groups", "Show all groups.")

	lsStreams      = lsCommand.Command("streams", "Show all streams in a given log group.")
	lsLogGroupName = lsStreams.Arg("group", "The group name.").Required().String()

	tailCommand        = kp.Command("tail", "Tail log groups/streams.")
	logGroupStreamName = tailCommand.Arg("groupName[:logStreamPrefix]", "The log group and stream name, with group:prefix syntax."+
		"Stream name can be just the prefix. If no stream name is specified all stream names in the given group will be tailed."+
		"Multiple group/stream tuple can be passed. e.g. cw tail group1:prefix1 group2:prefix2 group3:prefix3.").
		Required().Strings()

	follow          = tailCommand.Flag("follow", "Don't stop when the end of streams is reached, but rather wait for additional data to be appended.").Short('f').Default("false").Bool()
	printTimestamp  = tailCommand.Flag("timestamp", "Print the event timestamp.").Short('t').Default("false").Bool()
	printEventID    = tailCommand.Flag("event-id", "Print the event Id.").Short('i').Default("false").Bool()
	printStreamName = tailCommand.Flag("stream-name", "Print the log stream name this event belongs to.").Short('s').Default("false").Bool()
	printGroupName  = tailCommand.Flag("group-name", "Print the log group name this event belongs to.").Short('n').Default("false").Bool()
	startTime       = tailCommand.Flag("start", "The UTC start time. Passed as either date/time or human-friendly format."+
		" The human-friendly format accepts the number of hours and minutes prior to the present. "+
		"Denote hours with 'h' and minutes with 'm' i.e. 80m, 4h30m."+
		" If just time is used (format: hh[:mm]) it is expanded to today at the given time."+
		" Full available date/time format: 2017-02-27[T09[:00[:00]].").
		Short('b').Default(time.Now().UTC().Add(-30 * time.Second).Format(timeFormat)).String()
	endTime = tailCommand.Flag("end", "The UTC end time. Passed as either date/time or human-friendly format. "+
		"The human-friendly format accepts the number of hours and minutes prior to the present. "+
		"Denote hours with 'h' and minutes with 'm' i.e. 80m, 4h30m."+
		"If just time is used (format: hh[:mm]) it is expanded to today at the given time. Full available date/time format: 2017-02-27[T09[:00[:00]].").
		Short('e').Default("").String()
	local = tailCommand.Flag("local", "Treat date and time in Local timezone.").Short('l').Default("false").Bool()
	grep  = tailCommand.Flag("grep", "Pattern to filter logs by. See http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html for syntax.").
		Short('g').Default("").String()
	grepv = tailCommand.Flag("grepv", "Equivalent of grep --invert-match. Invert match pattern to filter logs by.").Short('v').Default("").String()
)

func timestampToTime(timeStamp *string) time.Time {
	var zone *time.Location
	if *local {
		zone = time.Local
	} else {
		zone = time.UTC
	}
	if regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`).MatchString(*timeStamp) {
		t, _ := time.ParseInLocation("2006-01-02", *timeStamp, zone)
		return t
	} else if regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}$`).MatchString(*timeStamp) {
		t, _ := time.ParseInLocation("2006-01-02T15", *timeStamp, zone)
		return t
	} else if regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$`).MatchString(*timeStamp) {
		t, _ := time.ParseInLocation("2006-01-02T15:04", *timeStamp, zone)
		return t
	} else if regexp.MustCompile(`^\d{1,2}$`).MatchString(*timeStamp) {
		y, m, d := time.Now().In(zone).Date()
		t, _ := strconv.Atoi(*timeStamp)
		return time.Date(y, m, d, t, 0, 0, 0, zone)
	} else if res := regexp.MustCompile(`^(?P<Hour>\d{1,2}):(?P<Minute>\d{2})$`).FindStringSubmatch(*timeStamp); res != nil {
		y, m, d := time.Now().Date()

		t, _ := strconv.Atoi(res[1])
		mm, _ := strconv.Atoi(res[2])

		return time.Date(y, m, d, t, mm, 0, 0, zone)
	} else if regexp.MustCompile(`^\d{1,}h$|^\d{1,}m$|^\d{1,}h\d{1,}m$`).MatchString(*timeStamp) {
		d, _ := time.ParseDuration(*timeStamp)

		t := time.Now().In(zone).Add(-d)
		y, m, dd := t.Date()
		return time.Date(y, m, dd, t.Hour(), t.Minute(), 0, 0, zone)
	}

	//TODO check even last scenario and if it's not a recognized pattern throw an error
	t, _ := time.ParseInLocation("2006-01-02T15:04:05", *timeStamp, zone)
	return t
}

type logEvent struct {
	logEvent cloudwatchlogs.FilteredLogEvent
	logGroup string
}

func formatLogMsg(ev logEvent, printTime *bool, printStreamName *bool, printGroupName *bool) string {
	msg := *ev.logEvent.Message
	if *printEventID {
		if *noColor {
			msg = fmt.Sprintf("%s - %s", *ev.logEvent.EventId, msg)
		} else {
			msg = fmt.Sprintf("%s - %s", color.YellowString(*ev.logEvent.EventId), msg)
		}
	}
	if *printStreamName {
		if *noColor {
			msg = fmt.Sprintf("%s - %s", *ev.logEvent.LogStreamName, msg)
		} else {
			msg = fmt.Sprintf("%s - %s", color.BlueString(*ev.logEvent.LogStreamName), msg)
		}
	}

	if *printGroupName {
		if *noColor {
			msg = fmt.Sprintf("%s - %s", ev.logGroup, msg)
		} else {
			msg = fmt.Sprintf("%s - %s", color.CyanString(ev.logGroup), msg)
		}
	}

	if *printTime {
		eventTimestamp := *ev.logEvent.Timestamp / 1000
		ts := time.Unix(eventTimestamp, 0).Format(timeFormat)
		if *noColor {
			msg = fmt.Sprintf("%s - %s", ts, msg)
		} else {
			msg = fmt.Sprintf("%s - %s", color.GreenString(ts), msg)
		}
	}
	return msg
}

func main() {
	kp.Version(version).Author("Luca Grulla")

	defer newVersionMsg(version, fetchLatestVersion(), *noColor)
	go versionCheckOnSigterm()

	cmd := kingpin.MustParse(kp.Parse(os.Args[1:]))
	c := cloudwatch.New(awsProfile, awsRegion, debug)
	switch cmd {
	case "ls groups":

		for msg := range c.LsGroups() {
			fmt.Println(*msg)
		}
	case "ls streams":
		for msg := range c.LsStreams(lsLogGroupName, nil) {
			fmt.Println(*msg)
		}
	case "tail":
		st := timestampToTime(startTime)
		var et time.Time
		if *endTime != "" {
			et = timestampToTime(endTime)
		}
		out := make(chan *logEvent)

		var wg sync.WaitGroup

		triggerChannels := make([]chan<- time.Time, len(*logGroupStreamName))

		for idx, gs := range *logGroupStreamName {
			trigger := make(chan time.Time, 1)
			go func(groupStream string) {
				tokens := strings.Split(groupStream, ":")
				var prefix string
				group := tokens[0]
				if len(tokens) > 1 && tokens[1] != "*" {
					prefix = tokens[1]
				}
				for c := range c.Tail(&group, &prefix, follow, &st, &et, grep, grepv, trigger) {
					out <- &logEvent{logEvent: *c, logGroup: group}
				}
				wg.Done()
			}(gs)
			triggerChannels[idx] = trigger
			wg.Add(1)
		}
		coordinator := &tailCoordinator{}
		coordinator.start(triggerChannels)

		go func() {
			wg.Wait()
			if *debug {
				fmt.Println("closing main channel...")
			}
			close(out)
		}()

		for logEv := range out {
			fmt.Println(formatLogMsg(*logEv, printTimestamp, printStreamName, printGroupName))
		}
	}
}

type tailCoordinator struct {
	targets *ring.Ring
}

func (f *tailCoordinator) start(targets []chan<- time.Time) {
	f.targets = ring.New(len(targets))
	for i := 0; i < f.targets.Len(); i++ {
		f.targets.Value = targets[i]
		f.targets = f.targets.Next()
	}
	//AWS API accepts 5 reqs/sec for account
	ticker := time.NewTicker(205 * time.Millisecond)
	go func() {
		for range ticker.C {
			x := f.targets.Value.(chan<- time.Time)
			x <- time.Now()
			f.targets = f.targets.Next()
		}
	}()

}

//TODO validate new group:prefix syntax
//TODO fix autocompletion
//TODO use Context for graceful shutdown of each tail activity
<|endoftext|>


<commit_before><commit_msg>Modify /urls condition<commit_after><|endoftext|>


<commit_before><commit_msg>捨て牌の領域を追加<commit_after><|endoftext|>


<commit_before>package main

import (
	"encoding/gob"
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/DaemonNews/dnews/src"
	"github.com/gorilla/csrf"
	"github.com/gorilla/feeds"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
)

// TODO change this secret
var insecure bool
var cookieSecret string
var crsfSecret string
var templ *template.Template
var store *sessions.CookieStore
var listen string

type response struct {
	Error string
	User  interface{}
	Data  interface{}
	CSRF  map[string]interface{}
}

var funcMap = template.FuncMap{
	"formatDate": dnews.FormatDate,
	"shortDate":  dnews.ShortDate,
	"printByte": func(b []byte) string {
		return string(b)
	},
	"joinTags": func(ts dnews.Tags) template.HTML {
		var s []string
		for _, t := range ts {

			s = append(s, fmt.Sprintf(`<a href="/tag/%s">%s</a>`, t.Name, t.Name))
		}
		return template.HTML(strings.Join(s, ", "))
	},
	"printHTML": func(b []byte) template.HTML {
		return template.HTML(string(b))
	},
}

func init() {
	var err error
	flag.BoolVar(&insecure, "i", false, "Insecure mode")
	flag.StringVar(&cookieSecret, "cookie", "something-very-secret", "Secret to use for cookie store")
	flag.StringVar(&crsfSecret, "crsf", "32-byte-long-auth-key", "Secret to use for cookie store")
	flag.StringVar(&listen, "http", ":8080", "Listen on")

	flag.Parse()

	store = sessions.NewCookieStore([]byte(cookieSecret))
	templ, err = template.New("dnews").Funcs(funcMap).ParseGlob("templates/*.html")
	if err != nil {
		log.Fatal(err)
	}

	gob.Register(&dnews.User{})
}

func renderTemplate(w http.ResponseWriter, r *http.Request, d *response, t string) {
	d.CSRF = map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
	}
	err := templ.ExecuteTemplate(w, t, d)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func grabUser(w http.ResponseWriter, r *http.Request) (*response, error) {
	session, err := store.Get(r, "session-name")
	if err != nil {
		return nil, err
	}

	uVal := session.Values["user"]
	if _, ok := uVal.(*dnews.User); !ok {
		uVal = &dnews.User{}
		session.Values["user"] = &uVal
		session.Save(r, w)
	}

	var data = response{}
	data.User = &uVal
	return &data, nil
}

func main() {

	router := mux.NewRouter()
	router.PathPrefix("/public/").Handler(http.StripPrefix("/public/", http.FileServer(http.Dir("public"))))

	router.HandleFunc("/feeds", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		renderTemplate(w, r, data, "feeds.html")
	})
	router.HandleFunc("/ml", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		renderTemplate(w, r, data, "ml.html")
	})
	router.HandleFunc("/archives", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		renderTemplate(w, r, data, "archives.html")
	})
	router.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
		query := r.FormValue("search")
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, fmt.Sprintf("Can't get user: %s", err.Error()), http.StatusInternalServerError)
			return
		}
		// TODO sanatize!
		a, err := dnews.SearchArticles(query, 100)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		data.Data = &a

		renderTemplate(w, r, data, "search_results.html")
	})

	router.HandleFunc("/feed/{type}", func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		feedType := vars["type"]
		now := time.Now()
		feed := &feeds.Feed{
			Title:       "Daemon.News",
			Link:        &feeds.Link{Href: "https://daemon.news"},
			Description: "*BSD News and Advocacy",
			Author:      &feeds.Author{Name: "The Daemon News Team", Email: "daemons@daemon.news"},
			Created:     now,
			Copyright:   "This work is copyright © Daemon.News",
		}

		a, err := dnews.GetNArticles(10)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		// TODO populate body?
		feed.Items = []*feeds.Item{}

		for _, article := range a {
			f := feeds.Item{}
			f.Title = article.Title
			f.Link = &feeds.Link{Href: fmt.Sprintf("http://daemon.news/article/%s", article.Slug)}
			f.Author = &feeds.Author{Name: article.Author.FName, Email: article.Author.Email}
			f.Created = article.Date

			feed.Items = append(feed.Items, &f)
		}

		switch feedType {
		case "atom":
			atom, err := feed.ToAtom()
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			fmt.Fprintf(w, atom)
		case "rss":
			rss, err := feed.ToRss()
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			fmt.Fprintf(w, rss)
		default:
			http.Error(w, "Invalid feed type!", http.StatusInternalServerError)
			return
		}

	})
	router.HandleFunc("/tag/{tag:[a-zA-Z0-9-]+}", func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		tag := vars["tag"]

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

		articles, err := dnews.GetArticlesByTag(tag)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		data.Data = articles
		renderTemplate(w, r, data, "index.html")

	})
	router.HandleFunc("/article/{slug:[a-zA-Z0-9-]+}", func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		slug := vars["slug"]

		article, err := dnews.GetArticle(slug)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		data.Data = article
		renderTemplate(w, r, data, "article.html")

	})
	router.HandleFunc("/article/raw/{slug:[a-zA-Z0-9-]+}", func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		slug := vars["slug"]

		article, err := dnews.GetRawArticle(slug)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		fmt.Fprintf(w, "%s", article.Body)
	})
	router.HandleFunc("/login/post", func(w http.ResponseWriter, r *http.Request) {
		session, err := store.Get(r, "session-name")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		user := r.FormValue("user")
		passwd := r.FormValue("passwd")

		if user == "" && passwd == "" {
			http.Redirect(w, r, "/", http.StatusFound)
		} else {
			u, err := dnews.Auth(user, passwd)

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

			if u.Authed {
				session.Values["user"] = u
				session.Save(r, w)
				http.Redirect(w, r, "/", http.StatusFound)
			}
		}
	})
	router.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		renderTemplate(w, r, data, "login.html")
	})
	router.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

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

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

		data.Data = struct {
			*dnews.Tags
			*dnews.Users
		}{
			&t,
			&u,
		}

		renderTemplate(w, r, data, "admin.html")
	})
	router.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
		session, err := store.Get(r, "session-name")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		session.Options = &sessions.Options{
			MaxAge: -1,
		}
		session.Save(r, w)
		http.Redirect(w, r, "/", http.StatusFound)

	})
	router.HandleFunc("/advocacy", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

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

		data.Data = &bugs

		renderTemplate(w, r, data, "advocacy.html")
	})
	router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		a, err := dnews.GetNArticles(10)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		data.Data = &a
		renderTemplate(w, r, data, "index.html")
	})

	loggedRouter := handlers.LoggingHandler(os.Stdout, router)

	// TODO change this secret
	if insecure {
		log.Fatal(http.ListenAndServe(listen,
			csrf.Protect([]byte("32-byte-long-auth-key"),
				csrf.Secure(false))(loggedRouter)))
	} else {
		log.Fatal(http.ListenAndServe(listen,
			csrf.Protect([]byte(crsfSecret))(loggedRouter)))

	}
}
<commit_msg>refactor db stuff, check for admin privs before loading admin page<commit_after>package main

import (
	"encoding/gob"
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/DaemonNews/dnews/src"
	"github.com/gorilla/csrf"
	"github.com/gorilla/feeds"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
)

// TODO change this secret
var insecure bool
var cookieSecret string
var crsfSecret string
var templ *template.Template
var store *sessions.CookieStore
var listen string

type response struct {
	Error string
	User  interface{}
	Data  interface{}
	CSRF  map[string]interface{}
}

var funcMap = template.FuncMap{
	"formatDate": dnews.FormatDate,
	"shortDate":  dnews.ShortDate,
	"printByte": func(b []byte) string {
		return string(b)
	},
	"joinTags": func(ts dnews.Tags) template.HTML {
		var s []string
		for _, t := range ts {

			s = append(s, fmt.Sprintf(`<a href="/tag/%s">%s</a>`, t.Name, t.Name))
		}
		return template.HTML(strings.Join(s, ", "))
	},
	"printHTML": func(b []byte) template.HTML {
		return template.HTML(string(b))
	},
}

func init() {
	var err error
	flag.BoolVar(&insecure, "i", false, "Insecure mode")
	flag.StringVar(&cookieSecret, "cookie", "something-very-secret", "Secret to use for cookie store")
	flag.StringVar(&crsfSecret, "crsf", "32-byte-long-auth-key", "Secret to use for cookie store")
	flag.StringVar(&listen, "http", ":8080", "Listen on")

	flag.Parse()

	store = sessions.NewCookieStore([]byte(cookieSecret))
	templ, err = template.New("dnews").Funcs(funcMap).ParseGlob("templates/*.html")
	if err != nil {
		log.Fatal(err)
	}

	gob.Register(&dnews.User{})
}

func renderTemplate(w http.ResponseWriter, r *http.Request, d *response, t string) {
	d.CSRF = map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
	}
	err := templ.ExecuteTemplate(w, t, d)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func grabUser(w http.ResponseWriter, r *http.Request) (*response, error) {
	session, err := store.Get(r, "session-name")
	if err != nil {
		return nil, err
	}

	uVal := session.Values["user"]

	var _, ok = uVal.(*dnews.User)

	if !ok {
		uVal = &dnews.User{}
		session.Values["user"] = &uVal
		session.Save(r, w)
	}

	var data = response{}
	data.User = &uVal
	return &data, nil
}

func main() {
	db, err := dnews.DBConnect()
	defer db.Close()

	if err != nil {
		log.Fatal(err)
	}
	router := mux.NewRouter()
	router.PathPrefix("/public/").Handler(
		http.StripPrefix("/public/",
			http.FileServer(http.Dir("public"))))

	router.HandleFunc("/feeds", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		renderTemplate(w, r, data, "feeds.html")
	})
	router.HandleFunc("/ml", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		renderTemplate(w, r, data, "ml.html")
	})
	router.HandleFunc("/archives", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		renderTemplate(w, r, data, "archives.html")
	})
	router.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
		query := r.FormValue("search")
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, fmt.Sprintf("Can't get user: %s", err.Error()), http.StatusInternalServerError)
			return
		}
		// TODO sanatize!
		a, err := dnews.SearchArticles(db, query, 100)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		data.Data = &a

		renderTemplate(w, r, data, "search_results.html")
	})

	router.HandleFunc("/feed/{type}", func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		feedType := vars["type"]
		now := time.Now()
		feed := &feeds.Feed{
			Title:       "Daemon.News",
			Link:        &feeds.Link{Href: "https://daemon.news"},
			Description: "*BSD News and Advocacy",
			Author:      &feeds.Author{Name: "The Daemon News Team", Email: "daemons@daemon.news"},
			Created:     now,
			Copyright:   "This work is copyright © Daemon.News",
		}

		a, err := dnews.GetNArticles(db, 10)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		// TODO populate body?
		feed.Items = []*feeds.Item{}

		for _, article := range a {
			f := feeds.Item{}
			f.Title = article.Title
			f.Link = &feeds.Link{Href: fmt.Sprintf("http://daemon.news/article/%s", article.Slug)}
			f.Author = &feeds.Author{Name: article.Author.FName, Email: article.Author.Email}
			f.Created = article.Date

			feed.Items = append(feed.Items, &f)
		}

		switch feedType {
		case "atom":
			atom, err := feed.ToAtom()
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			fmt.Fprintf(w, atom)
		case "rss":
			rss, err := feed.ToRss()
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			fmt.Fprintf(w, rss)
		default:
			http.Error(w, "Invalid feed type!", http.StatusInternalServerError)
			return
		}

	})
	router.HandleFunc("/tag/{tag:[a-zA-Z0-9-]+}", func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		tag := vars["tag"]

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

		articles, err := dnews.GetArticlesByTag(db, tag)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		data.Data = articles
		renderTemplate(w, r, data, "index.html")

	})
	router.HandleFunc("/article/{slug:[a-zA-Z0-9-]+}", func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		slug := vars["slug"]

		article, err := dnews.GetArticle(db, slug)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		data.Data = article
		renderTemplate(w, r, data, "article.html")

	})
	router.HandleFunc("/article/raw/{slug:[a-zA-Z0-9-]+}", func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		slug := vars["slug"]

		article, err := dnews.GetRawArticle(db, slug)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		fmt.Fprintf(w, "%s", article.Body)
	})
	router.HandleFunc("/login/post", func(w http.ResponseWriter, r *http.Request) {
		session, err := store.Get(r, "session-name")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		user := r.FormValue("user")
		passwd := r.FormValue("passwd")

		if user == "" && passwd == "" {
			http.Redirect(w, r, "/", http.StatusFound)
		} else {
			u, err := dnews.Auth(db, user, passwd)

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

			if u.Authed {
				session.Values["user"] = u
				session.Save(r, w)
				http.Redirect(w, r, "/", http.StatusFound)
			}
		}
	})
	router.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		renderTemplate(w, r, data, "login.html")
	})
	router.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) {
		session, err := store.Get(r, "session-name")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		uVal := session.Values["user"]
		var u, ok = uVal.(*dnews.User)
		if !ok {
			uVal = &dnews.User{}
			session.Values["user"] = &uVal
			session.Save(r, w)
		}

		var data = &response{}
		data.User = &u

		if ok {
			if u.Admin {
				t, err := dnews.GetAllTags(db)
				if err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					return
				}

				us, err := dnews.GetAllUsers(db)
				if err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					return
				}

				data.Data = struct {
					*dnews.Tags
					*dnews.Users
				}{
					&t,
					&us,
				}

				renderTemplate(w, r, data, "admin.html")
			} else {
				renderTemplate(w, r, data, "perm_denied.html")
			}
		} else {
			http.Error(w, "Invalid User!", http.StatusInternalServerError)
			return
		}

	})
	router.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
		session, err := store.Get(r, "session-name")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		session.Options = &sessions.Options{
			MaxAge: -1,
		}
		session.Save(r, w)
		http.Redirect(w, r, "/", http.StatusFound)

	})
	router.HandleFunc("/advocacy", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		bugs, err := dnews.GetBugs(db)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		data.Data = &bugs

		renderTemplate(w, r, data, "advocacy.html")
	})
	router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		data, err := grabUser(w, r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		a, err := dnews.GetNArticles(db, 10)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		data.Data = &a
		renderTemplate(w, r, data, "index.html")
	})

	loggedRouter := handlers.LoggingHandler(os.Stdout, router)

	// TODO change this secret
	if insecure {
		log.Fatal(http.ListenAndServe(listen,
			csrf.Protect([]byte("32-byte-long-auth-key"),
				csrf.Secure(false))(loggedRouter)))
	} else {
		log.Fatal(http.ListenAndServe(listen,
			csrf.Protect([]byte(crsfSecret))(loggedRouter)))

	}
}
<|endoftext|>


<commit_before>package main

import (
	"os"

	"github.com/srinandan/apigeecli/cmd"
)

//Version contains the git hash
var Version string

func main() {

	rootCmd := cmd.GetRootCmd()
	rootCmd.Version = "0.7, Git: " + Version

	if err := rootCmd.Execute(); err != nil {
		os.Exit(-1)
	}
}
<commit_msg>change to 0.9<commit_after>package main

import (
	"os"

	"github.com/srinandan/apigeecli/cmd"
)

//Version contains the git hash
var Version string

func main() {

	rootCmd := cmd.GetRootCmd()
	rootCmd.Version = "0.9, Git: " + Version

	if err := rootCmd.Execute(); err != nil {
		os.Exit(-1)
	}
}
<|endoftext|>


<commit_before>package main

import (
    "os"
    "os/exec"

    "io/ioutil"

    "log"

//    "fmt"
    "strings"
    "runtime"
    "strconv" // For Itoa
//    "encoding/csv"
    "encoding/json"

    "github.com/go-martini/martini"
)

func main() {
    m := martini.Classic()

    // CPU count
    m.Get("/sh/numberofcores.php", func () ([]byte, error) {
        return json.Marshal(runtime.NumCPU())
    })

    // Server's hostname
    m.Get("/sh/hostname.php", func () ([]byte, error) {
        host, err := os.Hostname()

        if err != nil {
            return nil, err
        }

        return json.Marshal(host)
    })

    // PS
    m.Get("/sh/ps.php", func () ([]byte, error) {
        // Run uptime command
        rawOutput, err := exec.Command("ps", "aux").Output()

        if err != nil {
            return nil, err
        }

        return json.Marshal(parseCommandTable(rawOutput))
    })

    m.Get("/sh/df.php", func () ([]byte, error) {
        // Run uptime command
        rawOutput, err := exec.Command("df", "-Ph").Output()

        if err != nil {
            return nil, err
        }

        return json.Marshal(parseCommandTable(rawOutput))
    })

    m.Get("/sh/time.php", func () ([]byte, error) {
        raw, err := exec.Command("date").Output()

        if err != nil {
            return nil, err
        }

        return json.Marshal(string(raw[:]))
    })

    m.Get("/sh/issue.php", func () ([]byte, error) {
        raw, err := exec.Command("uname", "-rsm").Output()

        if err != nil {
            return nil, err
        }

        return json.Marshal(string(raw[:]))
    })

    m.Get("/sh/users.php", func () ([]byte, error) {
        data, err := ioutil.ReadFile("/etc/passwd")

        if err != nil {
            return nil, err
        }

        lines := strings.Split(string(data), "\n")

        // Output records
        var records [][]string

        for _, line := range lines {
            parts := strings.Split(line, ":")

            // Skip bad or empty lines
            if len(parts) != 7 {
                log.Println(len(parts))
                continue
            }

            // Parse base 10, 16 bit UID integer
            uid, err := strconv.ParseInt(parts[2], 10, 16)

            // Error parsing UID
            if err != nil {
                continue
            }

            userType := "user"

            // Check if system user
            if uid <= 499 {
                userType = "system"
            }

            user := []string{
                // User type
                userType,
                // Username
                parts[0],
                // Home directory
                parts[6],
            }

            records = append(records, user)
        }

        return json.Marshal(records)
    })

    m.Get("/sh/online.php", func () ([]byte, error) {
        raw, err := exec.Command("w").Output()

        if err != nil {
            return nil, err
        }

        lines := strings.Split(string(raw[:]), "\n")

        // We'll add all the parsed lines here
        var entries [][]string

        // Skip first and last line of output
        for _, str := range lines[2:len(lines)-1] {
            fields := strings.Fields(str)
            entries = append(entries, []string{
                // User
                fields[0],
                // From
                fields[2],
                // Login at
                fields[3],
                // Idle
                fields[4],
            })
        }

        return json.Marshal(entries)
    })

    // Serve static files
    m.Get("/.*", martini.Static(""))

    m.Run()
}

func parseCommandTable(rawOutput []byte) [][]string {
    // Convert output to a string (it's not binary data, so this is ok)
    output := string(rawOutput[:])

    // We'll add all the parsed lines here
    var entries [][]string

    // Lines of output
    lines := strings.Split(output, "\n")

    // Skip first and last line of output
    for _, str := range lines[1:len(lines)-1] {
        entries = append(entries, strings.Fields(str))
    }

    return entries
}
<commit_msg>Refactor parseCommandTable<commit_after>package main

import (
    "os"
    "os/exec"

    "io/ioutil"

    "log"

//    "fmt"
    "strings"
    "runtime"
    "strconv" // For Itoa
//    "encoding/csv"
    "encoding/json"

    "github.com/go-martini/martini"
)

func main() {
    m := martini.Classic()

    // CPU count
    m.Get("/sh/numberofcores.php", func () ([]byte, error) {
        return json.Marshal(runtime.NumCPU())
    })

    // Server's hostname
    m.Get("/sh/hostname.php", func () ([]byte, error) {
        host, err := os.Hostname()

        if err != nil {
            return nil, err
        }

        return json.Marshal(host)
    })

    // PS
    m.Get("/sh/ps.php", func () ([]byte, error) {
        // Run uptime command
        rawOutput, err := exec.Command("ps", "aux").Output()

        if err != nil {
            return nil, err
        }

        return json.Marshal(parseCommandTable(rawOutput, 1, 1))
    })

    m.Get("/sh/df.php", func () ([]byte, error) {
        // Run uptime command
        rawOutput, err := exec.Command("df", "-Ph").Output()

        if err != nil {
            return nil, err
        }

        return json.Marshal(parseCommandTable(rawOutput, 1, 1))
    })

    m.Get("/sh/time.php", func () ([]byte, error) {
        raw, err := exec.Command("date").Output()

        if err != nil {
            return nil, err
        }

        return json.Marshal(string(raw[:]))
    })

    m.Get("/sh/issue.php", func () ([]byte, error) {
        raw, err := exec.Command("uname", "-rsm").Output()

        if err != nil {
            return nil, err
        }

        return json.Marshal(string(raw[:]))
    })

    m.Get("/sh/users.php", func () ([]byte, error) {
        data, err := ioutil.ReadFile("/etc/passwd")

        if err != nil {
            return nil, err
        }

        lines := strings.Split(string(data), "\n")

        // Output records
        var records [][]string

        for _, line := range lines {
            parts := strings.Split(line, ":")

            // Skip bad or empty lines
            if len(parts) != 7 {
                log.Println(len(parts))
                continue
            }

            // Parse base 10, 16 bit UID integer
            uid, err := strconv.ParseInt(parts[2], 10, 16)

            // Error parsing UID
            if err != nil {
                continue
            }

            userType := "user"

            // Check if system user
            if uid <= 499 {
                userType = "system"
            }

            user := []string{
                // User type
                userType,
                // Username
                parts[0],
                // Home directory
                parts[6],
            }

            records = append(records, user)
        }

        return json.Marshal(records)
    })

    m.Get("/sh/online.php", func () ([]byte, error) {
        raw, err := exec.Command("w").Output()

        if err != nil {
            return nil, err
        }

        lines := strings.Split(string(raw[:]), "\n")

        // We'll add all the parsed lines here
        var entries [][]string

        // Skip first and last line of output
        for _, str := range lines[2:len(lines)-1] {
            fields := strings.Fields(str)
            entries = append(entries, []string{
                // User
                fields[0],
                // From
                fields[2],
                // Login at
                fields[3],
                // Idle
                fields[4],
            })
        }

        return json.Marshal(entries)
    })

    // Serve static files
    m.Get("/.*", martini.Static(""))

    m.Run()
}

func parseCommandTable(rawOutput []byte, headerCount int, footerCount int) [][]string {
    // Convert output to a string (it's not binary data, so this is ok)
    output := string(rawOutput[:])

    // We'll add all the parsed lines here
    var entries [][]string

    // Lines of output
    lines := strings.Split(output, "\n")

    // Skip first and last line of output
    for _, str := range lines[headerCount:len(lines)-footerCount] {
        entries = append(entries, strings.Fields(str))
    }

    return entries
}
<|endoftext|>


<commit_before><commit_msg>Initial Commit<commit_after><|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	stathat "github.com/stathat/go"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

func KelvinToFahr(kelvin float64) float64 {
	return (kelvin-273.15)*1.8 + 32
}

type TickList struct {
	Message string
	Code    string `json:"cod"`
	List    []*Tick
}

type Tick struct {
	Kelvin float64 `json:"temp"`
	Fahr   float64
	Dt     int64
	Date   time.Time
}

func NewTickList(stationID string) (list *TickList, err error) {
	url := "http://api.openweathermap.org/data/2.1/history/station/" +
		stationID + "?type=tick"
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	buf, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	err = json.Unmarshal(buf, &list)
	if err != nil {
		return nil, err
	}
	list.init()
	return list, nil
}

func (tick *Tick) init() {
	tick.Fahr = KelvinToFahr(tick.Kelvin)
	tick.Date = time.Unix(tick.Dt, 0)
}

func (list *TickList) init() {
	for _, tick := range list.List {
		tick.init()
	}
}

func (list *TickList) Len() int {
	return len(list.List)
}

func (list *TickList) Tick(i int) *Tick {
	return list.List[i]
}

func (tick *Tick) Print() {
	fmt.Printf("% 3.1f %v\n", tick.Fahr, tick.Date)
}

func (list *TickList) Print() {
	if len(list.List) < 0 {
		fmt.Println("no data from station!")
		return
	}
	fmt.Printf("%5v %v\n", "Temp", "Time")
	for _, tick := range list.List {
		tick.Print()
	}
}

type UsageError struct {
	error
}

func errMain() (err error) {
	help := false
	flag.BoolVar(&help, "help", false, "print this help message")
	verbose := false
	flag.BoolVar(&verbose, "verbose", false, "print actions to stdout")
	email := ""
	flag.StringVar(&email, "email", "",
		"email address registered with StatHat")
	id := ""
	flag.StringVar(&id, "station", "", "station id. ex: 1348")
	name := ""
	flag.StringVar(&name, "stat", "",
		`stat name. defaults to "weather-" + station. ex: weather-temp-KBGR`)
	interval := time.Minute * 10
	flag.DurationVar(&interval, "interval", time.Minute*10,
		"time since last run")
	flag.Parse()
	if help {
		flag.Usage()
		return nil
	}
	if email == "" {
		return UsageError{errors.New("email required")}
	}
	if id == "" {
		return UsageError{errors.New("station id required")}
	}
	if name == "" {
		name = "weather-" + id
	}

	prev := time.Now().Add(-interval)

	list, err := NewTickList(id)
	if err != nil {
		return err
	}

	if list.Len() < 1 {
		return fmt.Errorf("no weather data returned: %v: %v", list.Code,
			list.Message)
	}

	recent := list.Tick(list.Len() - 1)
	if recent.Date.Before(prev) {
		if verbose {
			fmt.Println("already reported latest data")
		}
		return nil
	}

	if verbose {
		fmt.Println(name, email, recent.Fahr)
	}
	// the return value is a farce
	_ = stathat.PostEZValue(name, email, recent.Fahr)
	// if you make anything print in here, convert the whole thing to use
	// log instead of fmt.
	finished := stathat.WaitUntilFinished(time.Second * 5)
	if !finished {
		return errors.New("stathat timed out")
	}

	return nil
}

func main() {
	err := errMain()
	if err == nil {
		os.Exit(0)
	}
	if _, ok := err.(UsageError); ok {
		flag.Usage()
	}
	fmt.Fprintf(os.Stderr, "%v: Error: %v\n", filepath.Base(os.Args[0]), err)
	os.Exit(1)
}
<commit_msg>Only request one tick<commit_after>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	stathat "github.com/stathat/go"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

func KelvinToFahr(kelvin float64) float64 {
	return (kelvin-273.15)*1.8 + 32
}

type TickList struct {
	Message string
	Code    string `json:"cod"`
	List    []*Tick
}

type Tick struct {
	Kelvin float64 `json:"temp"`
	Fahr   float64
	Dt     int64
	Date   time.Time
}

func NewTickList(stationID string) (list *TickList, err error) {
	url := "http://api.openweathermap.org/data/2.1/history/station/" +
		stationID + "?type=tick&cnt=1"
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	buf, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	err = json.Unmarshal(buf, &list)
	if err != nil {
		return nil, err
	}
	list.init()
	return list, nil
}

func (tick *Tick) init() {
	tick.Fahr = KelvinToFahr(tick.Kelvin)
	tick.Date = time.Unix(tick.Dt, 0)
}

func (list *TickList) init() {
	for _, tick := range list.List {
		tick.init()
	}
}

func (list *TickList) Len() int {
	return len(list.List)
}

func (list *TickList) Tick(i int) *Tick {
	return list.List[i]
}

func (tick *Tick) Print() {
	fmt.Printf("% 3.1f %v\n", tick.Fahr, tick.Date)
}

func (list *TickList) Print() {
	if len(list.List) < 0 {
		fmt.Println("no data from station!")
		return
	}
	fmt.Printf("%5v %v\n", "Temp", "Time")
	for _, tick := range list.List {
		tick.Print()
	}
}

type UsageError struct {
	error
}

func errMain() (err error) {
	help := false
	flag.BoolVar(&help, "help", false, "print this help message")
	verbose := false
	flag.BoolVar(&verbose, "verbose", false, "print actions to stdout")
	email := ""
	flag.StringVar(&email, "email", "",
		"email address registered with StatHat")
	id := ""
	flag.StringVar(&id, "station", "", "station id. ex: 1348")
	name := ""
	flag.StringVar(&name, "stat", "",
		`stat name. defaults to "weather-" + station. ex: weather-temp-KBGR`)
	interval := time.Minute * 10
	flag.DurationVar(&interval, "interval", time.Minute*10,
		"time since last run")
	flag.Parse()
	if help {
		flag.Usage()
		return nil
	}
	if email == "" {
		return UsageError{errors.New("email required")}
	}
	if id == "" {
		return UsageError{errors.New("station id required")}
	}
	if name == "" {
		name = "weather-" + id
	}

	prev := time.Now().Add(-interval)

	list, err := NewTickList(id)
	if err != nil {
		return err
	}

	if list.Len() < 1 {
		return fmt.Errorf("no weather data returned: %v: %v", list.Code,
			list.Message)
	}
	if list.Len() > 1 {
		return fmt.Errorf("list too long: %v", list.Len())
	}
	recent := list.Tick(0)
	if recent.Date.Before(prev) {
		if verbose {
			fmt.Println("already reported latest data")
		}
		return nil
	}

	if verbose {
		fmt.Println(name, email, recent.Fahr)
	}
	// the return value is a farce
	_ = stathat.PostEZValue(name, email, recent.Fahr)
	// if you make anything print in here, convert the whole thing to use
	// log instead of fmt.
	finished := stathat.WaitUntilFinished(time.Second * 5)
	if !finished {
		return errors.New("stathat timed out")
	}

	return nil
}

func main() {
	err := errMain()
	if err == nil {
		os.Exit(0)
	}
	if _, ok := err.(UsageError); ok {
		flag.Usage()
	}
	fmt.Fprintf(os.Stderr, "%v: Error: %v\n", filepath.Base(os.Args[0]), err)
	os.Exit(1)
}
<|endoftext|>


<commit_before>/* Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package main

import (
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"
	"os"
	"path/filepath"
	"strings"
)

var (
	verbose = flag.Bool("v", false, "print the names of packages as they are checked")
)

func init() {
	if err := typesInit(); err != nil {
		errExit(err)
	}
}

func main() {
	flag.Parse()
	if err := checkPaths(flag.Args(), os.Stdout); err != nil {
		errExit(err)
	}
}

func errExit(err error) {
	fmt.Fprintf(os.Stderr, "%v\n", err)
	os.Exit(1)
}

type call struct {
	params  []interface{}
	results []interface{}
}

var toToken = map[string]token.Token{
	"byte":    token.INT,
	"int":     token.INT,
	"int8":    token.INT,
	"int16":   token.INT,
	"int32":   token.INT,
	"int64":   token.INT,
	"uint":    token.INT,
	"uint8":   token.INT,
	"uint16":  token.INT,
	"uint32":  token.INT,
	"uint64":  token.INT,
	"string":  token.STRING,
	"float32": token.FLOAT,
	"float64": token.FLOAT,
}

func paramEqual(t1 types.Type, a2 interface{}) bool {
	switch x := a2.(type) {
	case string:
		return t1.String() == x
	case token.Token:
		return toToken[t1.String()] == x
	case nil:
		switch t1.(type) {
		case *types.Slice:
			return true
		case *types.Map:
			return true
		default:
			return false
		}
	default:
		return false
	}
}

func paramsMatch(types1 []types.Type, args2 []interface{}) bool {
	if len(types1) != len(args2) {
		return false
	}
	for i, t1 := range types1 {
		a2 := args2[i]
		if !paramEqual(t1, a2) {
			return false
		}
	}
	return true
}

func resultEqual(t1 types.Type, e2 interface{}) bool {
	switch x := e2.(type) {
	case string:
		return t1.String() == x
	case nil:
		// assigning to _
		return true
	default:
		return false
	}
}

func resultsMatch(types1 []types.Type, exps2 []interface{}) bool {
	if len(exps2) == 0 {
		return true
	}
	if len(types1) != len(exps2) {
		return false
	}
	for i, t1 := range types1 {
		e2 := exps2[i]
		if !resultEqual(t1, e2) {
			return false
		}
	}
	return true
}

func interfaceMatching(calls map[string]call) string {
	matchesIface := func(decls map[string]funcDecl) bool {
		if len(calls) > len(decls) {
			return false
		}
		for n, d := range decls {
			c, e := calls[n]
			if !e {
				return false
			}
			if !paramsMatch(d.params, c.params) {
				return false
			}
			if !resultsMatch(d.results, c.results) {
				return false
			}
		}
		return true
	}
	for name, decls := range parsed {
		if matchesIface(decls) {
			return name
		}
	}
	return ""
}

func getDirs(d string) ([]string, error) {
	var dirs []string
	walkFn := func(path string, info os.FileInfo, err error) error {
		if info.IsDir() {
			dirs = append(dirs, path)
		}
		return nil
	}
	if err := filepath.Walk(d, walkFn); err != nil {
		return nil, err
	}
	return dirs, nil
}

func getPkgs(p string) ([]*build.Package, []string, error) {
	recursive := filepath.Base(p) == "..."
	if !recursive {
		info, err := os.Stat(p)
		if err != nil {
			return nil, nil, err
		}
		if !info.IsDir() {
			pkg := &build.Package{
				Name:    "stdin",
				GoFiles: []string{p},
			}
			return []*build.Package{pkg}, []string{"."}, nil
		}
	}
	d := p
	if recursive {
		d = p[:len(p)-4]
	}
	dirs, err := getDirs(d)
	if err != nil {
		return nil, nil, err
	}
	wd, err := os.Getwd()
	if err != nil {
		return nil, nil, err
	}
	var pkgs []*build.Package
	var basedirs []string
	for _, d := range dirs {
		pkg, err := build.Import("./"+d, wd, 0)
		if _, ok := err.(*build.NoGoError); ok {
			continue
		}
		if err != nil {
			return nil, nil, err
		}
		pkgs = append(pkgs, pkg)
		basedirs = append(basedirs, d)
	}
	return pkgs, basedirs, nil
}

func checkPaths(paths []string, w io.Writer) error {
	conf := &types.Config{Importer: importer.Default()}
	for _, p := range paths {
		pkgs, basedirs, err := getPkgs(p)
		if err != nil {
			return err
		}
		for i, pkg := range pkgs {
			basedir := basedirs[i]
			if err := checkPkg(conf, pkg, basedir, w); err != nil {
				return err
			}
		}
	}
	return nil
}

func checkPkg(conf *types.Config, pkg *build.Package, basedir string, w io.Writer) error {
	if *verbose {
		fmt.Fprintln(w, basedir)
	}
	gp := &goPkg{
		Package: pkg,
		fset:    token.NewFileSet(),
	}
	for _, p := range pkg.GoFiles {
		fp := filepath.Join(basedir, p)
		if err := gp.parsePath(fp); err != nil {
			return err
		}
	}
	if err := gp.check(conf, w); err != nil {
		return err
	}
	return nil
}

type goPkg struct {
	*build.Package

	fset  *token.FileSet
	files []*ast.File
}

func (gp *goPkg) parsePath(fp string) error {
	f, err := os.Open(fp)
	if err != nil {
		return err
	}
	defer f.Close()
	if err := gp.parseReader(fp, f); err != nil {
		return err
	}
	return nil
}

func (gp *goPkg) parseReader(name string, r io.Reader) error {
	f, err := parser.ParseFile(gp.fset, name, r, 0)
	if err != nil {
		return err
	}
	gp.files = append(gp.files, f)
	return nil
}

func (gp *goPkg) check(conf *types.Config, w io.Writer) error {
	pkg, err := conf.Check(gp.Name, gp.fset, gp.files, nil)
	if err != nil {
		return err
	}

	v := &Visitor{
		pkg:    gp.Package,
		w:      w,
		fset:   gp.fset,
		scopes: []*types.Scope{pkg.Scope()},
	}
	for _, f := range gp.files {
		ast.Walk(v, f)
	}
	return nil
}

type Visitor struct {
	pkg *build.Package

	w      io.Writer
	fset   *token.FileSet
	scopes []*types.Scope

	nodes []ast.Node

	params map[string]types.Type
	used   map[string]map[string]call

	// TODO: don't just discard params with untracked usage
	unknown       map[string]struct{}
	recordUnknown bool
}

func (v *Visitor) scope() *types.Scope {
	return v.scopes[len(v.scopes)-1]
}

func scopeName(e ast.Expr) string {
	switch x := e.(type) {
	case *ast.Ident:
		return x.Name
	case *ast.StarExpr:
		return scopeName(x.X)
	default:
		return ""
	}
}

func (v *Visitor) recvFuncType(tname, fname string) *types.Func {
	parts := strings.Split(tname, ".")
	var obj types.Object
	if len(parts) == 2 && parts[0] == v.pkg.Name {
		parts = parts[1:]
	}
	if len(parts) == 1 {
		_, obj = v.scope().LookupParent(parts[0], token.NoPos)
	}
	st, ok := obj.(*types.TypeName)
	if !ok {
		return nil
	}
	named, ok := st.Type().(*types.Named)
	if !ok {
		return nil
	}
	for i := 0; i < named.NumMethods(); i++ {
		f := named.Method(i)
		if f.Name() == fname {
			return f
		}
	}
	return nil
}

func (v *Visitor) funcType(fd *ast.FuncDecl) *types.Func {
	fname := fd.Name.Name
	if fd.Recv == nil {
		f, ok := v.scope().Lookup(fname).(*types.Func)
		if !ok {
			return nil
		}
		return f
	}
	if len(fd.Recv.List) > 1 {
		return nil
	}
	tname := scopeName(fd.Recv.List[0].Type)
	return v.recvFuncType(tname, fname)
}

func typeMap(t *types.Tuple) map[string]types.Type {
	m := make(map[string]types.Type, t.Len())
	for i := 0; i < t.Len(); i++ {
		p := t.At(i)
		m[p.Name()] = p.Type()
	}
	return m
}

func (v *Visitor) Visit(node ast.Node) ast.Visitor {
	var top ast.Node
	if len(v.nodes) > 0 {
		top = v.nodes[len(v.nodes)-1]
	}
	switch x := node.(type) {
	case *ast.File:
	case *ast.FuncDecl:
		f := v.funcType(x)
		if f == nil {
			return nil
		}
		v.scopes = append(v.scopes, f.Scope())
		sign := f.Type().(*types.Signature)
		v.params = typeMap(sign.Params())
		v.used = make(map[string]map[string]call, 0)
		v.unknown = make(map[string]struct{})
	case *ast.CallExpr:
		if wasParamCall := v.onCall(x); wasParamCall {
			return nil
		}
	case *ast.BlockStmt:
		v.recordUnknown = true
	case *ast.Ident:
		if !v.recordUnknown {
			break
		}
		if _, e := v.params[x.Name]; e {
			v.unknown[x.Name] = struct{}{}
		}
	case nil:
		v.nodes = v.nodes[:len(v.nodes)-1]
		fd, ok := top.(*ast.FuncDecl)
		if !ok {
			return nil
		}
		v.scopes = v.scopes[:len(v.scopes)-1]
		v.funcEnded(fd)
		v.params = nil
		v.used = nil
		v.unknown = nil
		v.recordUnknown = false
	}
	if node != nil {
		v.nodes = append(v.nodes, node)
	}
	return v
}

func (v *Visitor) getType(name string) interface{} {
	_, obj := v.scope().LookupParent(name, token.NoPos)
	if obj == nil {
		return nil
	}
	switch x := obj.(type) {
	case *types.Var:
		return x.Type().String()
	default:
		return nil
	}
}

func (v *Visitor) descType(e ast.Expr) interface{} {
	switch x := e.(type) {
	case *ast.Ident:
		return v.getType(x.Name)
	case *ast.BasicLit:
		return x.Kind
	default:
		return nil
	}
}

func (v *Visitor) onCall(ce *ast.CallExpr) bool {
	if v.used == nil {
		return false
	}
	sel, ok := ce.Fun.(*ast.SelectorExpr)
	if !ok {
		return false
	}
	left, ok := sel.X.(*ast.Ident)
	if !ok {
		return false
	}
	right := sel.Sel
	vname := left.Name
	if _, e := v.params[vname]; !e {
		return false
	}
	tname, _ := v.descType(left).(string)
	fname := right.Name
	c := call{}
	f := v.recvFuncType(tname, fname)
	if f != nil {
		sign := f.Type().(*types.Signature)
		results := sign.Results()
		for i := 0; i < results.Len(); i++ {
			v := results.At(i)
			c.results = append(c.results, v.Type().String())
		}
	}
	for _, a := range ce.Args {
		c.params = append(c.params, v.descType(a))
	}
	if _, e := v.used[vname]; !e {
		v.used[vname] = make(map[string]call)
	}
	v.used[vname][fname] = c
	return true
}

func (v *Visitor) funcEnded(fd *ast.FuncDecl) {
	for name, methods := range v.used {
		if _, e := v.unknown[name]; e {
			continue
		}
		iface := interfaceMatching(methods)
		if iface == "" {
			continue
		}
		param, e := v.params[name]
		if !e {
			continue
		}
		if iface == param.String() {
			continue
		}
		pos := v.fset.Position(fd.Pos())
		fmt.Fprintf(v.w, "%s:%d: %s can be %s\n",
			pos.Filename, pos.Line, name, iface)
	}
}
<commit_msg>Get arg types directly instead of via global scope<commit_after>/* Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package main

import (
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"
	"os"
	"path/filepath"
)

var (
	verbose = flag.Bool("v", false, "print the names of packages as they are checked")
)

func init() {
	if err := typesInit(); err != nil {
		errExit(err)
	}
}

func main() {
	flag.Parse()
	if err := checkPaths(flag.Args(), os.Stdout); err != nil {
		errExit(err)
	}
}

func errExit(err error) {
	fmt.Fprintf(os.Stderr, "%v\n", err)
	os.Exit(1)
}

type call struct {
	params  []interface{}
	results []interface{}
}

var toToken = map[string]token.Token{
	"byte":    token.INT,
	"int":     token.INT,
	"int8":    token.INT,
	"int16":   token.INT,
	"int32":   token.INT,
	"int64":   token.INT,
	"uint":    token.INT,
	"uint8":   token.INT,
	"uint16":  token.INT,
	"uint32":  token.INT,
	"uint64":  token.INT,
	"string":  token.STRING,
	"float32": token.FLOAT,
	"float64": token.FLOAT,
}

func paramEqual(t1 types.Type, a2 interface{}) bool {
	switch x := a2.(type) {
	case string:
		return t1.String() == x
	case token.Token:
		return toToken[t1.String()] == x
	case nil:
		switch t1.(type) {
		case *types.Slice:
			return true
		case *types.Map:
			return true
		default:
			return false
		}
	default:
		return false
	}
}

func paramsMatch(types1 []types.Type, args2 []interface{}) bool {
	if len(types1) != len(args2) {
		return false
	}
	for i, t1 := range types1 {
		a2 := args2[i]
		if !paramEqual(t1, a2) {
			return false
		}
	}
	return true
}

func resultEqual(t1 types.Type, e2 interface{}) bool {
	switch x := e2.(type) {
	case string:
		return t1.String() == x
	case nil:
		// assigning to _
		return true
	default:
		return false
	}
}

func resultsMatch(types1 []types.Type, exps2 []interface{}) bool {
	if len(exps2) == 0 {
		return true
	}
	if len(types1) != len(exps2) {
		return false
	}
	for i, t1 := range types1 {
		e2 := exps2[i]
		if !resultEqual(t1, e2) {
			return false
		}
	}
	return true
}

func interfaceMatching(calls map[string]call) string {
	matchesIface := func(decls map[string]funcDecl) bool {
		if len(calls) > len(decls) {
			return false
		}
		for n, d := range decls {
			c, e := calls[n]
			if !e {
				return false
			}
			if !paramsMatch(d.params, c.params) {
				return false
			}
			if !resultsMatch(d.results, c.results) {
				return false
			}
		}
		return true
	}
	for name, decls := range parsed {
		if matchesIface(decls) {
			return name
		}
	}
	return ""
}

func getDirs(d string) ([]string, error) {
	var dirs []string
	walkFn := func(path string, info os.FileInfo, err error) error {
		if info.IsDir() {
			dirs = append(dirs, path)
		}
		return nil
	}
	if err := filepath.Walk(d, walkFn); err != nil {
		return nil, err
	}
	return dirs, nil
}

func getPkgs(p string) ([]*build.Package, []string, error) {
	recursive := filepath.Base(p) == "..."
	if !recursive {
		info, err := os.Stat(p)
		if err != nil {
			return nil, nil, err
		}
		if !info.IsDir() {
			pkg := &build.Package{
				Name:    "stdin",
				GoFiles: []string{p},
			}
			return []*build.Package{pkg}, []string{"."}, nil
		}
	}
	d := p
	if recursive {
		d = p[:len(p)-4]
	}
	dirs, err := getDirs(d)
	if err != nil {
		return nil, nil, err
	}
	wd, err := os.Getwd()
	if err != nil {
		return nil, nil, err
	}
	var pkgs []*build.Package
	var basedirs []string
	for _, d := range dirs {
		pkg, err := build.Import("./"+d, wd, 0)
		if _, ok := err.(*build.NoGoError); ok {
			continue
		}
		if err != nil {
			return nil, nil, err
		}
		pkgs = append(pkgs, pkg)
		basedirs = append(basedirs, d)
	}
	return pkgs, basedirs, nil
}

func checkPaths(paths []string, w io.Writer) error {
	conf := &types.Config{Importer: importer.Default()}
	for _, p := range paths {
		pkgs, basedirs, err := getPkgs(p)
		if err != nil {
			return err
		}
		for i, pkg := range pkgs {
			basedir := basedirs[i]
			if err := checkPkg(conf, pkg, basedir, w); err != nil {
				return err
			}
		}
	}
	return nil
}

func checkPkg(conf *types.Config, pkg *build.Package, basedir string, w io.Writer) error {
	if *verbose {
		fmt.Fprintln(w, basedir)
	}
	gp := &goPkg{
		Package: pkg,
		fset:    token.NewFileSet(),
	}
	for _, p := range pkg.GoFiles {
		fp := filepath.Join(basedir, p)
		if err := gp.parsePath(fp); err != nil {
			return err
		}
	}
	if err := gp.check(conf, w); err != nil {
		return err
	}
	return nil
}

type goPkg struct {
	*build.Package

	fset  *token.FileSet
	files []*ast.File
}

func (gp *goPkg) parsePath(fp string) error {
	f, err := os.Open(fp)
	if err != nil {
		return err
	}
	defer f.Close()
	if err := gp.parseReader(fp, f); err != nil {
		return err
	}
	return nil
}

func (gp *goPkg) parseReader(name string, r io.Reader) error {
	f, err := parser.ParseFile(gp.fset, name, r, 0)
	if err != nil {
		return err
	}
	gp.files = append(gp.files, f)
	return nil
}

func (gp *goPkg) check(conf *types.Config, w io.Writer) error {
	pkg, err := conf.Check(gp.Name, gp.fset, gp.files, nil)
	if err != nil {
		return err
	}

	v := &Visitor{
		pkg:    gp.Package,
		w:      w,
		fset:   gp.fset,
		scopes: []*types.Scope{pkg.Scope()},
	}
	for _, f := range gp.files {
		ast.Walk(v, f)
	}
	return nil
}

type Visitor struct {
	pkg *build.Package

	w      io.Writer
	fset   *token.FileSet
	scopes []*types.Scope

	nodes []ast.Node

	params map[string]types.Type
	used   map[string]map[string]call

	// TODO: don't just discard params with untracked usage
	unknown       map[string]struct{}
	recordUnknown bool
}

func (v *Visitor) scope() *types.Scope {
	return v.scopes[len(v.scopes)-1]
}

func scopeName(e ast.Expr) string {
	switch x := e.(type) {
	case *ast.Ident:
		return x.Name
	case *ast.StarExpr:
		return scopeName(x.X)
	default:
		return ""
	}
}

func (v *Visitor) funcType(fd *ast.FuncDecl) *types.Func {
	fname := fd.Name.Name
	f, ok := v.scope().Lookup(fname).(*types.Func)
	if !ok {
		return nil
	}
	return f
}

func typeMap(t *types.Tuple) map[string]types.Type {
	m := make(map[string]types.Type, t.Len())
	for i := 0; i < t.Len(); i++ {
		p := t.At(i)
		m[p.Name()] = p.Type()
	}
	return m
}

func (v *Visitor) Visit(node ast.Node) ast.Visitor {
	var top ast.Node
	if len(v.nodes) > 0 {
		top = v.nodes[len(v.nodes)-1]
	}
	switch x := node.(type) {
	case *ast.File:
	case *ast.FuncDecl:
		f := v.funcType(x)
		if f == nil {
			return nil
		}
		v.scopes = append(v.scopes, f.Scope())
		sign := f.Type().(*types.Signature)
		v.params = typeMap(sign.Params())
		v.used = make(map[string]map[string]call, 0)
		v.unknown = make(map[string]struct{})
	case *ast.CallExpr:
		if wasParamCall := v.onCall(x); wasParamCall {
			return nil
		}
	case *ast.BlockStmt:
		v.recordUnknown = true
	case *ast.Ident:
		if !v.recordUnknown {
			break
		}
		if _, e := v.params[x.Name]; e {
			v.unknown[x.Name] = struct{}{}
		}
	case nil:
		v.nodes = v.nodes[:len(v.nodes)-1]
		fd, ok := top.(*ast.FuncDecl)
		if !ok {
			return nil
		}
		v.scopes = v.scopes[:len(v.scopes)-1]
		v.funcEnded(fd)
		v.params = nil
		v.used = nil
		v.unknown = nil
		v.recordUnknown = false
	}
	if node != nil {
		v.nodes = append(v.nodes, node)
	}
	return v
}

func (v *Visitor) getType(name string) interface{} {
	_, obj := v.scope().LookupParent(name, token.NoPos)
	if obj == nil {
		return nil
	}
	switch x := obj.(type) {
	case *types.Var:
		return x.Type().String()
	default:
		return nil
	}
}

type methoder interface {
	NumMethods() int
	Method(i int) *types.Func
}

func extNamed(t types.Type) *types.Named {
	switch x := t.(type) {
	case *types.Named:
		return x
	case *types.Pointer:
		return extNamed(x.Elem())
	default:
		panic("Unexpected type")
	}
}

func methods(t types.Type) methoder {
	named := extNamed(t)
	if iface, ok := named.Underlying().(*types.Interface); ok {
		return iface
	}
	return named
}

func (v *Visitor) dType(t, f *ast.Ident) *types.Func {
	obj := v.scope().Lookup(t.Name)
	if obj == nil {
		return nil
	}
	found := methods(obj.Type())
	for i := 0; i < found.NumMethods(); i++ {
		m := found.Method(i)
		if m.Name() == f.Name {
			return m
		}
	}
	return nil
}

func (v *Visitor) descType(e ast.Expr) interface{} {
	switch x := e.(type) {
	case *ast.Ident:
		return v.getType(x.Name)
	case *ast.BasicLit:
		return x.Kind
	default:
		return nil
	}
}

func (v *Visitor) onCall(ce *ast.CallExpr) bool {
	if v.used == nil {
		return false
	}
	sel, ok := ce.Fun.(*ast.SelectorExpr)
	if !ok {
		return false
	}
	left, ok := sel.X.(*ast.Ident)
	if !ok {
		return false
	}
	right := sel.Sel
	vname := left.Name
	if _, e := v.params[vname]; !e {
		return false
	}
	c := call{}
	f := v.dType(left, right)
	if f == nil {
		return false
	}
	sign := f.Type().(*types.Signature)
	results := sign.Results()
	for i := 0; i < results.Len(); i++ {
		v := results.At(i)
		c.results = append(c.results, v.Type().String())
	}
	for _, a := range ce.Args {
		c.params = append(c.params, v.descType(a))
	}
	if _, e := v.used[vname]; !e {
		v.used[vname] = make(map[string]call)
	}
	fname := right.Name
	v.used[vname][fname] = c
	return true
}

func (v *Visitor) funcEnded(fd *ast.FuncDecl) {
	for name, methods := range v.used {
		if _, e := v.unknown[name]; e {
			continue
		}
		iface := interfaceMatching(methods)
		if iface == "" {
			continue
		}
		param, e := v.params[name]
		if !e {
			continue
		}
		if iface == param.String() {
			continue
		}
		pos := v.fset.Position(fd.Pos())
		fmt.Fprintf(v.w, "%s:%d: %s can be %s\n",
			pos.Filename, pos.Line, name, iface)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"github.com/docker/go-plugins-helpers/volume"
)

const (
	sshfsID       = "_sshfs"
	socketAddress = "/run/docker/plugins/sshfs.sock"
)

var (
	defaultDir = filepath.Join(volume.DefaultDockerRootDirectory, sshfsID)
	root       = flag.String("root", defaultDir, "SshFS volumes root directory")
)

type volumeName struct {
	name        string
	connections int
}

type sshfsDriver struct {
	sync.Mutex

	root    string
	volumes map[string]*volumeName
}

func newSshfsDriver(root string) sshfsDriver {
	d := sshfsDriver{
		root:    root,
		volumes: map[string]*volumeName{},
	}

	return d
}

func (d sshfsDriver) Create(r volume.Request) volume.Response {
	return volume.Response{}
}

func (d sshfsDriver) Remove(r volume.Request) volume.Response {
	d.Lock()
	defer d.Unlock()
	m := d.mountpoint(r.Name)

	if s, ok := d.volumes[m]; ok {
		if s.connections <= 1 {
			delete(d.volumes, m)
		}
	}
	return volume.Response{}
}

func (d sshfsDriver) Path(r volume.Request) volume.Response {
	return volume.Response{Mountpoint: d.mountpoint(r.Name)}
}

func (d sshfsDriver) Mount(r volume.MountRequest) volume.Response {
	d.Lock()
	defer d.Unlock()
	m := d.mountpoint(r.Name)

	s, ok := d.volumes[m]
	if ok && s.connections > 0 {
		log.Printf("Volume %s already mounted on %s\n", r.Name, m)
		s.connections++
		return volume.Response{Mountpoint: m}
	}

	fi, err := os.Lstat(m)

	if os.IsNotExist(err) {
		if err := os.MkdirAll(m, 0755); err != nil {
			return volume.Response{Err: err.Error()}
		}
	} else if err != nil {
		return volume.Response{Err: err.Error()}
	}

	if fi != nil && !fi.IsDir() {
		return volume.Response{Err: fmt.Sprintf("%v already exist and it's not a directory", m)}
	}

	if err := d.mountVolume(r.Name, m); err != nil {
		return volume.Response{Err: err.Error()}
	}

	d.volumes[m] = &volumeName{name: r.Name, connections: 1}

	log.Printf("Mounting volume %s on %s\n", r.Name, m)
	return volume.Response{Mountpoint: m}
}

func (d sshfsDriver) Unmount(r volume.UnmountRequest) volume.Response {
	d.Lock()
	defer d.Unlock()
	m := d.mountpoint(r.Name)

	if s, ok := d.volumes[m]; ok {
		if s.connections == 1 {
			log.Printf("Unmounting volume %s from %s\n", r.Name, m)
			if err := d.unmountVolume(m); err != nil {
				return volume.Response{Err: err.Error()}
			}
		}
		s.connections--
	} else {
		return volume.Response{Err: fmt.Sprintf("Unable to find volume mounted on %s", m)}
	}

	return volume.Response{}
}

func (d sshfsDriver) Get(r volume.Request) volume.Response {
	d.Lock()
	defer d.Unlock()
	m := d.mountpoint(r.Name)
	if s, ok := d.volumes[m]; ok {
		return volume.Response{Volume: &volume.Volume{Name: s.name, Mountpoint: d.mountpoint(s.name)}}
	}

	return volume.Response{Err: fmt.Sprintf("Unable to find volume mounted on %s", m)}
}

func (d sshfsDriver) List(r volume.Request) volume.Response {
	d.Lock()
	defer d.Unlock()
	var vols []*volume.Volume
	for _, v := range d.volumes {
		vols = append(vols, &volume.Volume{Name: v.name, Mountpoint: d.mountpoint(v.name)})
	}
	return volume.Response{Volumes: vols}
}

func (d sshfsDriver) Capabilities(r volume.Request) volume.Response {
	return volume.Response{Capabilities: volume.Capability{Scope: "global"}}
}

func (d *sshfsDriver) mountpoint(name string) string {
	return filepath.Join(d.root, name)
}

func (d *sshfsDriver) mountVolume(name, destination string) error {
	parts := strings.Split(name, "#")
	if len(parts) == 1 {
		name = name + ":/"
	} else if len(parts) == 2 {
		name = parts[0] + ":" + parts[1]
	} else {
		return fmt.Errorf("invalid name, use [user@]host#[dir]")
	}
	cmd := fmt.Sprintf("sshfs %s  %s", name, destination)
	return exec.Command("sh", "-c", cmd).Run()
}

func (d *sshfsDriver) unmountVolume(target string) error {
	cmd := fmt.Sprintf("umount %s", target)
	return exec.Command("sh", "-c", cmd).Run()
}

func main() {
	flag.Parse()

	d := newSshfsDriver(*root)
	h := volume.NewHandler(d)
	fmt.Printf("listening on %s\n", socketAddress)
	fmt.Println(h.ServeUnix("root", socketAddress))
}
<commit_msg>fix vet<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"github.com/docker/go-plugins-helpers/volume"
)

const (
	sshfsID       = "_sshfs"
	socketAddress = "/run/docker/plugins/sshfs.sock"
)

var (
	defaultDir = filepath.Join(volume.DefaultDockerRootDirectory, sshfsID)
	root       = flag.String("root", defaultDir, "SshFS volumes root directory")
)

type volumeName struct {
	name        string
	connections int
}

type sshfsDriver struct {
	sync.Mutex

	root    string
	volumes map[string]*volumeName
}

func newSshfsDriver(root string) *sshfsDriver {
	d := &sshfsDriver{
		root:    root,
		volumes: map[string]*volumeName{},
	}

	return d
}

func (d *sshfsDriver) Create(r volume.Request) volume.Response {
	return volume.Response{}
}

func (d *sshfsDriver) Remove(r volume.Request) volume.Response {
	d.Lock()
	defer d.Unlock()
	m := d.mountpoint(r.Name)

	if s, ok := d.volumes[m]; ok {
		if s.connections <= 1 {
			delete(d.volumes, m)
		}
	}
	return volume.Response{}
}

func (d *sshfsDriver) Path(r volume.Request) volume.Response {
	return volume.Response{Mountpoint: d.mountpoint(r.Name)}
}

func (d *sshfsDriver) Mount(r volume.MountRequest) volume.Response {
	d.Lock()
	defer d.Unlock()
	m := d.mountpoint(r.Name)

	s, ok := d.volumes[m]
	if ok && s.connections > 0 {
		log.Printf("Volume %s already mounted on %s\n", r.Name, m)
		s.connections++
		return volume.Response{Mountpoint: m}
	}

	fi, err := os.Lstat(m)

	if os.IsNotExist(err) {
		if err := os.MkdirAll(m, 0755); err != nil {
			return volume.Response{Err: err.Error()}
		}
	} else if err != nil {
		return volume.Response{Err: err.Error()}
	}

	if fi != nil && !fi.IsDir() {
		return volume.Response{Err: fmt.Sprintf("%v already exist and it's not a directory", m)}
	}

	if err := d.mountVolume(r.Name, m); err != nil {
		return volume.Response{Err: err.Error()}
	}

	d.volumes[m] = &volumeName{name: r.Name, connections: 1}

	log.Printf("Mounting volume %s on %s\n", r.Name, m)
	return volume.Response{Mountpoint: m}
}

func (d *sshfsDriver) Unmount(r volume.UnmountRequest) volume.Response {
	d.Lock()
	defer d.Unlock()
	m := d.mountpoint(r.Name)

	if s, ok := d.volumes[m]; ok {
		if s.connections == 1 {
			log.Printf("Unmounting volume %s from %s\n", r.Name, m)
			if err := d.unmountVolume(m); err != nil {
				return volume.Response{Err: err.Error()}
			}
		}
		s.connections--
	} else {
		return volume.Response{Err: fmt.Sprintf("Unable to find volume mounted on %s", m)}
	}

	return volume.Response{}
}

func (d *sshfsDriver) Get(r volume.Request) volume.Response {
	d.Lock()
	defer d.Unlock()
	m := d.mountpoint(r.Name)
	if s, ok := d.volumes[m]; ok {
		return volume.Response{Volume: &volume.Volume{Name: s.name, Mountpoint: d.mountpoint(s.name)}}
	}

	return volume.Response{Err: fmt.Sprintf("Unable to find volume mounted on %s", m)}
}

func (d *sshfsDriver) List(r volume.Request) volume.Response {
	d.Lock()
	defer d.Unlock()
	var vols []*volume.Volume
	for _, v := range d.volumes {
		vols = append(vols, &volume.Volume{Name: v.name, Mountpoint: d.mountpoint(v.name)})
	}
	return volume.Response{Volumes: vols}
}

func (d *sshfsDriver) Capabilities(r volume.Request) volume.Response {
	return volume.Response{Capabilities: volume.Capability{Scope: "global"}}
}

func (d *sshfsDriver) mountpoint(name string) string {
	return filepath.Join(d.root, name)
}

func (d *sshfsDriver) mountVolume(name, destination string) error {
	parts := strings.Split(name, "#")
	if len(parts) == 1 {
		name = name + ":/"
	} else if len(parts) == 2 {
		name = parts[0] + ":" + parts[1]
	} else {
		return fmt.Errorf("invalid name, use [user@]host#[dir]")
	}
	cmd := fmt.Sprintf("sshfs %s  %s", name, destination)
	return exec.Command("sh", "-c", cmd).Run()
}

func (d *sshfsDriver) unmountVolume(target string) error {
	cmd := fmt.Sprintf("umount %s", target)
	return exec.Command("sh", "-c", cmd).Run()
}

func main() {
	flag.Parse()

	d := newSshfsDriver(*root)
	h := volume.NewHandler(d)
	fmt.Printf("listening on %s\n", socketAddress)
	fmt.Println(h.ServeUnix("root", socketAddress))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello Go!! Brought to you by Travis CI.")
	})

	log.Fatal(http.ListenAndServe(":8080", nil))
}
<commit_msg>Revert message to Hello Go<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello Go!!")
	})

	log.Fatal(http.ListenAndServe(":8080", nil))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"log/syslog"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"

	cors "gopkg.in/gin-contrib/cors.v1"

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

const version = "0.0.3"

var (
	// Commands mapping to control OMXPlayer, these are piped via STDIN to omxplayer process
	Commands = map[string]string{
		"pause":             "p",            // Pause/continue playback
		"stop":              "q",            // Stop playback and exit
		"volume_up":         "+",            // Change volume by +3dB
		"volume_down":       "-",            // Change volume by -3dB
		"subtitles":         "s",            // Enable/disable subtitles
		"seek_back":         "\x1b\x5b\x44", // Seek -30 seconds
		"seek_back_fast":    "\x1b\x5b\x42", // Seek -600 second
		"seek_forward":      "\x1b\x5b\x43", // Seek +30 second
		"seek_forward_fast": "\x1b\x5b\x41", // Seek +600 seconds
	}

	// OmxPath is path to omxplayer executable
	OmxPath string

	// Omx is a child process for spawning omxplayer
	Omx *exec.Cmd

	// OmxIn is a child process STDIN pipe to send commands
	OmxIn io.WriteCloser

	// Command is a channel to pass along commands to the player routine
	Command chan string

	// CurrentURL represents currently played media
	CurrentContent *Content

	// Syslog logger
	syslogger *syslog.Writer
)

// Content describes model of currently playable video.
type Content struct {
	Title      string                 `json:"title,omitempty"`
	URL        url.URL                `json:"url,omitempty"`
	Attributes map[string]interface{} `json:"attributes,omitempty"`
}

// APIErr is a generic structure for all errors returned from API
type APIErr struct {
	Message string `json:"message,omitempty"`
}

// Determine the full path to omxplayer executable. Returns error if not found.
func omxDetect() error {
	buff, err := exec.Command("which", "omxplayer").Output()
	if err != nil {
		return err
	}

	// Set path in global variable
	OmxPath = strings.TrimSpace(string(buff))

	return nil
}

// Start command listener. Commands are coming in through a channel.
func omxListen() {
	Command = make(chan string)

	for {
		command := <-Command

		// Skip command handling of omx player is not active
		if Omx == nil {
			continue
		}

		// Send command to the player
		omxWrite(command)

		// Attempt to kill the process if stop command is requested
		if command == "stop" {
			Omx.Process.Kill()
		}
	}
}

// Start omxplayer playback for a given video file. Returns error if start fails.
func omxPlay(c Content) error {
	Omx = exec.Command(
		OmxPath,        // path to omxplayer executable
		"--blank",      // set background to black
		"--adev",       // audio out device
		"hdmi",         // using hdmi for audio/video
		c.URL.String(), // path to video file
	)

	// Grab child process STDIN
	stdin, err := Omx.StdinPipe()
	if err != nil {
		return err
	}

	defer stdin.Close()

	// Redirect output for debugging purposes
	// Omx.Stdout = os.Stdout

	// Start omxplayer execution.
	// If successful, something will appear on HDMI display.
	err = Omx.Start()
	if err != nil {
		return err
	}

	// Set current file
	CurrentContent = &c

	// Make child's STDIN globally available
	OmxIn = stdin

	// Wait until child process is finished
	err = Omx.Wait()
	if err != nil {
		syslogger.Err(fmt.Sprintln("Process exited with error:", err))
	}

	omxCleanup()

	return nil
}

// Write a command string to the omxplayer process's STDIN
func omxWrite(command string) {
	if OmxIn != nil {
		io.WriteString(OmxIn, Commands[command])
	}
}

// Terminate any running omxplayer processes. Fixes random hangs.
func omxKill() {
	exec.Command("killall", "omxplayer.bin").Output()
	exec.Command("killall", "omxplayer").Output()
}

// Reset internal state and stop any running processes
func omxCleanup() {
	Omx = nil
	OmxIn = nil
	CurrentContent = nil

	omxKill()
}

// Check if player is currently active
func omxIsActive() bool {
	return Omx != nil
}

func httpCommand(c *gin.Context) {
	val := c.Params.ByName("command")

	if _, ok := Commands[val]; !ok {
		c.JSON(http.StatusBadRequest, APIErr{"Invalid command"})
		return
	}

	// Handle requested commmand
	Command <- val

	c.Status(http.StatusAccepted)
}

func httpPlay(c *gin.Context) {
	if omxIsActive() {
		c.JSON(http.StatusBadRequest, APIErr{"Player is already running"})
		return
	}

	content := Content{}
	err := c.BindJSON(&content)
	if err != nil {
		c.JSON(http.StatusBadRequest, APIErr{err.Error()})
		return
	}

	go omxPlay(content)

	c.Status(http.StatusAccepted)
}

func httpStatus(c *gin.Context) {
	result := struct {
		Running        bool     `json:"running"`
		CurrentContent *Content `json:"currentContent,omitempty"`
	}{
		Running:        omxIsActive(),
		CurrentContent: CurrentContent,
	}

	c.JSON(http.StatusOK, result)
}

func toString(url *url.URL) string {
	if url == nil {
		return ""
	}

	return url.String()
}

func terminate(message string, code int) {
	fmt.Println(message)
	os.Exit(code)
}

func main() {
	fmt.Printf("omx-remote-api v%v\n", version)

	syslogger, _ = syslog.New(syslog.LOG_NOTICE, "omx-remote-api")

	// Check if player is installed
	if omxDetect() != nil {
		terminate("omxplayer is not installed", 1)
	}

	// Make sure nothing is running
	omxCleanup()

	// Start a remote command listener
	go omxListen()

	// Disable debugging mode
	gin.SetMode("release")
	gin.LoggerWithWriter(syslogger)

	// Setup HTTP server
	router := gin.Default()

	// CORS
	router.Use(cors.Default())

	router.GET("/status", httpStatus)
	router.POST("/play", httpPlay)
	router.POST("/commands/:command", httpCommand)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	fmt.Println("Starting server on 0.0.0.0:" + port)
	router.Run(":" + port)
}
<commit_msg>accept media info on play<commit_after>package main

import (
	"fmt"
	"io"
	"log/syslog"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"

	cors "gopkg.in/gin-contrib/cors.v1"

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

const version = "0.0.3"

var (
	// Commands mapping to control OMXPlayer, these are piped via STDIN to omxplayer process
	Commands = map[string]string{
		"pause":             "p",            // Pause/continue playback
		"stop":              "q",            // Stop playback and exit
		"volume_up":         "+",            // Change volume by +3dB
		"volume_down":       "-",            // Change volume by -3dB
		"subtitles":         "s",            // Enable/disable subtitles
		"seek_back":         "\x1b\x5b\x44", // Seek -30 seconds
		"seek_back_fast":    "\x1b\x5b\x42", // Seek -600 second
		"seek_forward":      "\x1b\x5b\x43", // Seek +30 second
		"seek_forward_fast": "\x1b\x5b\x41", // Seek +600 seconds
	}

	// OmxPath is path to omxplayer executable
	OmxPath string

	// Omx is a child process for spawning omxplayer
	Omx *exec.Cmd

	// OmxIn is a child process STDIN pipe to send commands
	OmxIn io.WriteCloser

	// Command is a channel to pass along commands to the player routine
	Command chan string

	// CurrentURL represents currently played media
	CurrentContent *Content

	// Syslog logger
	syslogger *syslog.Writer
)

// Content describes model of currently playable video.
type Content struct {
	RawURL    string                 `json:"url,omitempty"`
	MediaInfo map[string]interface{} `json:"mediaInfo,omitempty"`
}

// APIErr is a generic structure for all errors returned from API
type APIErr struct {
	Message string `json:"message,omitempty"`
}

// Determine the full path to omxplayer executable. Returns error if not found.
func omxDetect() error {
	buff, err := exec.Command("which", "omxplayer").Output()
	if err != nil {
		return err
	}

	// Set path in global variable
	OmxPath = strings.TrimSpace(string(buff))

	return nil
}

// Start command listener. Commands are coming in through a channel.
func omxListen() {
	Command = make(chan string)

	for {
		command := <-Command

		// Skip command handling of omx player is not active
		if Omx == nil {
			continue
		}

		// Send command to the player
		omxWrite(command)

		// Attempt to kill the process if stop command is requested
		if command == "stop" {
			Omx.Process.Kill()
		}
	}
}

// Start omxplayer playback for a given video file. Returns error if start fails.
func omxPlay(c Content) error {
	contentURL, err := url.Parse(c.RawURL)
	if err != nil {
		return err
	}

	Omx = exec.Command(
		OmxPath,             // path to omxplayer executable
		"--blank",           // set background to black
		"--adev",            // audio out device
		"hdmi",              // using hdmi for audio/video
		contentURL.String(), // path to video file
	)

	// Grab child process STDIN
	stdin, err := Omx.StdinPipe()
	if err != nil {
		return err
	}

	defer stdin.Close()

	// Redirect output for debugging purposes
	// Omx.Stdout = os.Stdout

	// Start omxplayer execution.
	// If successful, something will appear on HDMI display.
	err = Omx.Start()
	if err != nil {
		return err
	}

	// Set current file
	CurrentContent = &c

	// Make child's STDIN globally available
	OmxIn = stdin

	// Wait until child process is finished
	err = Omx.Wait()
	if err != nil {
		syslogger.Err(fmt.Sprintln("Process exited with error:", err))
	}

	omxCleanup()

	return nil
}

// Write a command string to the omxplayer process's STDIN
func omxWrite(command string) {
	if OmxIn != nil {
		io.WriteString(OmxIn, Commands[command])
	}
}

// Terminate any running omxplayer processes. Fixes random hangs.
func omxKill() {
	exec.Command("killall", "omxplayer.bin").Output()
	exec.Command("killall", "omxplayer").Output()
}

// Reset internal state and stop any running processes
func omxCleanup() {
	Omx = nil
	OmxIn = nil
	CurrentContent = nil

	omxKill()
}

// Check if player is currently active
func omxIsActive() bool {
	return Omx != nil
}

func httpCommand(c *gin.Context) {
	val := c.Params.ByName("command")

	if _, ok := Commands[val]; !ok {
		c.JSON(http.StatusBadRequest, APIErr{"Invalid command"})
		return
	}

	// Handle requested commmand
	Command <- val

	c.Status(http.StatusAccepted)
}

func httpPlay(c *gin.Context) {
	if omxIsActive() {
		c.JSON(http.StatusBadRequest, APIErr{"Player is already running"})
		return
	}

	content := Content{}
	err := c.BindJSON(&content)
	if err != nil {
		c.JSON(http.StatusBadRequest, APIErr{err.Error()})
		return
	}

	go omxPlay(content)

	c.Status(http.StatusAccepted)
}

func httpStatus(c *gin.Context) {
	result := struct {
		Running        bool     `json:"running"`
		CurrentContent *Content `json:"currentContent,omitempty"`
	}{
		Running:        omxIsActive(),
		CurrentContent: CurrentContent,
	}

	c.JSON(http.StatusOK, result)
}

func toString(url *url.URL) string {
	if url == nil {
		return ""
	}

	return url.String()
}

func terminate(message string, code int) {
	fmt.Println(message)
	os.Exit(code)
}

func main() {
	fmt.Printf("omx-remote-api v%v\n", version)

	syslogger, _ = syslog.New(syslog.LOG_NOTICE, "omx-remote-api")

	// Check if player is installed
	if omxDetect() != nil {
		terminate("omxplayer is not installed", 1)
	}

	// Make sure nothing is running
	omxCleanup()

	// Start a remote command listener
	go omxListen()

	// Disable debugging mode
	gin.SetMode("release")
	gin.LoggerWithWriter(syslogger)

	// Setup HTTP server
	router := gin.Default()

	// CORS
	router.Use(cors.Default())

	router.GET("/status", httpStatus)
	router.POST("/play", httpPlay)
	router.POST("/commands/:command", httpCommand)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	fmt.Println("Starting server on 0.0.0.0:" + port)
	router.Run(":" + port)
}
<|endoftext|>


<commit_before><commit_msg>clean up<commit_after><|endoftext|>